From 8415f805d9d44c2368fdbc49ea126e89cb1ec45e Mon Sep 17 00:00:00 2001 From: Pascal de Bruijn Date: Wed, 27 Apr 2022 18:52:21 +0200 Subject: [PATCH 001/169] Fix Ender-3 Pro Commit 1ad8460a3cfbd840d374fe32c0e01749effcb39e had several errors. --- resources/profiles/Creality.ini | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 0fa14d8a8..84ec0f37a 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -970,10 +970,7 @@ renamed_from = "Creality ENDER-3 BLTouch" printer_model = ENDER3BLTOUCH [printer:Creality Ender-3 Pro] -inherits = *common*; *pauseprint* -renamed_from = "Creality Ender-3 Pro" -bed_shape = 5x0,215x0,215x220,5x220 -max_print_height = 250 +inherits = Creality Ender-3; *pauseprint* printer_model = ENDER3PRO printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_ENDER3PRO\nPRINTER_HAS_BOWDEN From d0c08ec5c19e33d13ba8723393e8188207959406 Mon Sep 17 00:00:00 2001 From: PavelMikus Date: Thu, 12 May 2022 12:55:10 +0200 Subject: [PATCH 002/169] Feature: AABB tree for lines Small refactoring of AABB tree distance query function, to allow different primitives (apart from triangles) Implemented Distancer and functions to create AABB tree from lines and use closest point query Added test for the AABBTree with lines Added Benchmark comparing EdgeGrid with AABBTree on line contours (Inside AABBTree test file, disabled under compilation flag) --- src/libslic3r/AABBTreeIndirect.hpp | 143 +++++++------ src/libslic3r/AABBTreeLines.hpp | 112 ++++++++++ tests/libslic3r/test_aabbindirect.cpp | 285 ++++++++++++++++++++++++++ 3 files changed, 473 insertions(+), 67 deletions(-) create mode 100644 src/libslic3r/AABBTreeLines.hpp diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index 014b49af1..f2847579c 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -446,6 +446,57 @@ namespace detail { } } + // Real-time collision detection, Ericson, Chapter 5 + template + static inline Vector closest_point_to_triangle(const Vector &p, const Vector &a, const Vector &b, const Vector &c) + { + using Scalar = typename Vector::Scalar; + // Check if P in vertex region outside A + Vector ab = b - a; + Vector ac = c - a; + Vector ap = p - a; + Scalar d1 = ab.dot(ap); + Scalar d2 = ac.dot(ap); + if (d1 <= 0 && d2 <= 0) + return a; + // Check if P in vertex region outside B + Vector bp = p - b; + Scalar d3 = ab.dot(bp); + Scalar d4 = ac.dot(bp); + if (d3 >= 0 && d4 <= d3) + return b; + // Check if P in edge region of AB, if so return projection of P onto AB + Scalar vc = d1*d4 - d3*d2; + if (a != b && vc <= 0 && d1 >= 0 && d3 <= 0) { + Scalar v = d1 / (d1 - d3); + return a + v * ab; + } + // Check if P in vertex region outside C + Vector cp = p - c; + Scalar d5 = ab.dot(cp); + Scalar d6 = ac.dot(cp); + if (d6 >= 0 && d5 <= d6) + return c; + // Check if P in edge region of AC, if so return projection of P onto AC + Scalar vb = d5*d2 - d1*d6; + if (vb <= 0 && d2 >= 0 && d6 <= 0) { + Scalar w = d2 / (d2 - d6); + return a + w * ac; + } + // Check if P in edge region of BC, if so return projection of P onto BC + Scalar va = d3*d6 - d5*d4; + if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) { + Scalar w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + return b + w * (c - b); + } + // P inside face region. Compute Q through its barycentric coordinates (u,v,w) + Scalar denom = Scalar(1.0) / (va + vb + vc); + Scalar v = vb * denom; + Scalar w = vc * denom; + return a + ab * v + ac * w; // = u*a + v*b + w*c, u = va * denom = 1.0-v-w + }; + + // Nothing to do with COVID-19 social distancing. template struct IndexedTriangleSetDistancer { @@ -453,74 +504,36 @@ namespace detail { using IndexedFaceType = AIndexedFaceType; using TreeType = ATreeType; using VectorType = AVectorType; + using ScalarType = typename VectorType::Scalar; const std::vector &vertices; const std::vector &faces; const TreeType &tree; const VectorType origin; + + inline VectorType closest_point_to_origin(size_t primitive_index, + ScalarType& squared_distance){ + const auto &triangle = this->faces[primitive_index]; + VectorType closest_point = closest_point_to_triangle(origin, + this->vertices[triangle(0)].template cast(), + this->vertices[triangle(1)].template cast(), + this->vertices[triangle(2)].template cast()); + squared_distance = (origin - closest_point).squaredNorm(); + return closest_point; + } }; - // Real-time collision detection, Ericson, Chapter 5 - template - static inline Vector closest_point_to_triangle(const Vector &p, const Vector &a, const Vector &b, const Vector &c) - { - using Scalar = typename Vector::Scalar; - // Check if P in vertex region outside A - Vector ab = b - a; - Vector ac = c - a; - Vector ap = p - a; - Scalar d1 = ab.dot(ap); - Scalar d2 = ac.dot(ap); - if (d1 <= 0 && d2 <= 0) - return a; - // Check if P in vertex region outside B - Vector bp = p - b; - Scalar d3 = ab.dot(bp); - Scalar d4 = ac.dot(bp); - if (d3 >= 0 && d4 <= d3) - return b; - // Check if P in edge region of AB, if so return projection of P onto AB - Scalar vc = d1*d4 - d3*d2; - if (a != b && vc <= 0 && d1 >= 0 && d3 <= 0) { - Scalar v = d1 / (d1 - d3); - return a + v * ab; - } - // Check if P in vertex region outside C - Vector cp = p - c; - Scalar d5 = ab.dot(cp); - Scalar d6 = ac.dot(cp); - if (d6 >= 0 && d5 <= d6) - return c; - // Check if P in edge region of AC, if so return projection of P onto AC - Scalar vb = d5*d2 - d1*d6; - if (vb <= 0 && d2 >= 0 && d6 <= 0) { - Scalar w = d2 / (d2 - d6); - return a + w * ac; - } - // Check if P in edge region of BC, if so return projection of P onto BC - Scalar va = d3*d6 - d5*d4; - if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) { - Scalar w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); - return b + w * (c - b); - } - // P inside face region. Compute Q through its barycentric coordinates (u,v,w) - Scalar denom = Scalar(1.0) / (va + vb + vc); - Scalar v = vb * denom; - Scalar w = vc * denom; - return a + ab * v + ac * w; // = u*a + v*b + w*c, u = va * denom = 1.0-v-w - }; - - template - static inline Scalar squared_distance_to_indexed_triangle_set_recursive( - IndexedTriangleSetDistancerType &distancer, + template + static inline Scalar squared_distance_to_indexed_primitives_recursive( + IndexedPrimitivesDistancerType &distancer, size_t node_idx, Scalar low_sqr_d, Scalar up_sqr_d, size_t &i, - Eigen::PlainObjectBase &c) + Eigen::PlainObjectBase &c) { - using Vector = typename IndexedTriangleSetDistancerType::VectorType; + using Vector = typename IndexedPrimitivesDistancerType::VectorType; if (low_sqr_d > up_sqr_d) return low_sqr_d; @@ -538,13 +551,9 @@ namespace detail { assert(node.is_valid()); if (node.is_leaf()) { - const auto &triangle = distancer.faces[node.idx]; - Vector c_candidate = closest_point_to_triangle( - distancer.origin, - distancer.vertices[triangle(0)].template cast(), - distancer.vertices[triangle(1)].template cast(), - distancer.vertices[triangle(2)].template cast()); - set_min((c_candidate - distancer.origin).squaredNorm(), node.idx, c_candidate); + Scalar sqr_dist; + Vector c_candidate = distancer.closest_point_to_origin(node.idx, sqr_dist); + set_min(sqr_dist, node.idx, c_candidate); } else { @@ -561,7 +570,7 @@ namespace detail { { size_t i_left; Vector c_left = c; - Scalar sqr_d_left = squared_distance_to_indexed_triangle_set_recursive(distancer, left_node_idx, low_sqr_d, up_sqr_d, i_left, c_left); + Scalar sqr_d_left = squared_distance_to_indexed_primitives_recursive(distancer, left_node_idx, low_sqr_d, up_sqr_d, i_left, c_left); set_min(sqr_d_left, i_left, c_left); looked_left = true; }; @@ -569,13 +578,13 @@ namespace detail { { size_t i_right; Vector c_right = c; - Scalar sqr_d_right = squared_distance_to_indexed_triangle_set_recursive(distancer, right_node_idx, low_sqr_d, up_sqr_d, i_right, c_right); + Scalar sqr_d_right = squared_distance_to_indexed_primitives_recursive(distancer, right_node_idx, low_sqr_d, up_sqr_d, i_right, c_right); set_min(sqr_d_right, i_right, c_right); looked_right = true; }; // must look left or right if in box - using BBoxScalar = typename IndexedTriangleSetDistancerType::TreeType::BoundingBox::Scalar; + using BBoxScalar = typename IndexedPrimitivesDistancerType::TreeType::BoundingBox::Scalar; if (node_left.bbox.contains(distancer.origin.template cast())) look_left(); if (node_right.bbox.contains(distancer.origin.template cast())) @@ -747,7 +756,7 @@ inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set( auto distancer = detail::IndexedTriangleSetDistancer { vertices, faces, tree, point }; return tree.empty() ? Scalar(-1) : - detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits::infinity(), hit_idx_out, hit_point_out); + detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits::infinity(), hit_idx_out, hit_point_out); } // Decides if exists some triangle in defined radius on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree. @@ -779,7 +788,7 @@ inline bool is_any_triangle_in_radius( return false; } - detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), max_distance_squared, hit_idx, hit_point); + detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0), max_distance_squared, hit_idx, hit_point); return hit_point.allFinite(); } diff --git a/src/libslic3r/AABBTreeLines.hpp b/src/libslic3r/AABBTreeLines.hpp new file mode 100644 index 000000000..7b9595419 --- /dev/null +++ b/src/libslic3r/AABBTreeLines.hpp @@ -0,0 +1,112 @@ +#ifndef SRC_LIBSLIC3R_AABBTREELINES_HPP_ +#define SRC_LIBSLIC3R_AABBTREELINES_HPP_ + +#include "libslic3r/Point.hpp" +#include "libslic3r/EdgeGrid.hpp" +#include "libslic3r/AABBTreeIndirect.hpp" +#include "libslic3r/Line.hpp" + +namespace Slic3r { + +namespace AABBTreeLines { + +namespace detail { + +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 VectorType origin; + + inline VectorType closest_point_to_origin(size_t primitive_index, + ScalarType &squared_distance) { + VectorType nearest_point; + const LineType &line = lines[primitive_index]; + squared_distance = line_alg::distance_to_squared(line, origin, &nearest_point); + return nearest_point; + } +}; + +} + +// Build a balanced AABB Tree over a vector of float 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 float eps = 0) + { + using TreeType = AABBTreeIndirect::Tree<2, typename LineType::Scalar>; +// 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; + } + + size_t m_idx; + BoundingBox m_bbox; + 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; + n.m_centroid = (line.a + line.b) * 0.5; + 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); + } + + TreeType out; + out.build(std::move(input)); + return out; +} + +// Finding a closest line, its closest point and squared distance to the closest point +// Returns squared distance to the closest point or -1 if the input is empty. +template +inline typename VectorType::Scalar squared_distance_to_indexed_lines( + const std::vector &lines, + const TreeType &tree, + const VectorType &point, + size_t &hit_idx_out, + Eigen::PlainObjectBase &hit_point_out) + { + using Scalar = typename VectorType::Scalar; + auto distancer = detail::IndexedLinesDistancer + { lines, tree, point }; + return tree.empty() ? + Scalar(-1) : + AABBTreeIndirect::detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0), + std::numeric_limits::infinity(), hit_idx_out, hit_point_out); +} + +} + +} + +#endif /* SRC_LIBSLIC3R_AABBTREELINES_HPP_ */ diff --git a/tests/libslic3r/test_aabbindirect.cpp b/tests/libslic3r/test_aabbindirect.cpp index 3b834c442..87cf80943 100644 --- a/tests/libslic3r/test_aabbindirect.cpp +++ b/tests/libslic3r/test_aabbindirect.cpp @@ -3,6 +3,7 @@ #include #include +#include using namespace Slic3r; @@ -58,3 +59,287 @@ TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndi REQUIRE(closest_point.y() == Approx(0.5)); REQUIRE(closest_point.z() == Approx(1.)); } + +TEST_CASE("Creating a several 2d lines, testing closest point query", "[AABBIndirect]") +{ + std::vector lines { }; + lines.push_back(Linef(Vec2d(0.0, 0.0), Vec2d(1.0, 0.0))); + lines.push_back(Linef(Vec2d(1.0, 0.0), Vec2d(1.0, 1.0))); + lines.push_back(Linef(Vec2d(1.0, 1.0), Vec2d(0.0, 1.0))); + lines.push_back(Linef(Vec2d(0.0, 1.0), Vec2d(0.0, 0.0))); + + auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); + + size_t hit_idx_out; + Vec2d hit_point_out; + auto sqr_dist = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, Vec2d(0.0, 0.0), hit_idx_out, + hit_point_out); + REQUIRE(sqr_dist == Approx(0.0)); + REQUIRE((hit_idx_out == 0 || hit_idx_out == 3)); + REQUIRE(hit_point_out.x() == Approx(0.0)); + REQUIRE(hit_point_out.y() == Approx(0.0)); + + sqr_dist = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, Vec2d(1.5, 0.5), hit_idx_out, + hit_point_out); + REQUIRE(sqr_dist == Approx(0.25)); + REQUIRE(hit_idx_out == 1); + REQUIRE(hit_point_out.x() == Approx(1.0)); + REQUIRE(hit_point_out.y() == Approx(0.5)); +} + +#if 0 +#include "libslic3r/EdgeGrid.hpp" +#include +#include +#include +#include +TEST_CASE("AABBTreeLines vs SignedDistanceGrid time Benchmark", "[AABBIndirect]") +{ + std::vector lines { Points { } }; + std::vector linesf { }; + Vec2d prevf { }; + + // NOTE: max coord value of the lines is approx 83 mm + for (int r = 1; r < 1000; ++r) { + lines[0].push_back(Point::new_scale(Vec2d(exp(0.005f * r) * cos(r), exp(0.005f * r) * cos(r)))); + linesf.emplace_back(prevf, Vec2d(exp(0.005f * r) * cos(r), exp(0.005f * r) * cos(r))); + prevf = linesf.back().b; + } + + int build_num = 10000; + using namespace std::chrono; + { + std::cout << "building the tree " << build_num << " times..." << std::endl; + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + for (int i = 0; i < build_num; ++i) { + volatile auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(linesf); + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + + duration time_span = duration_cast>(t2 - t1); + std::cout << "It took " << time_span.count() << " seconds." << std::endl << std::endl; + + } + { + std::cout << "building the grid res 1mm ONLY " << build_num/100 << " !!! times..." << std::endl; + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + for (int i = 0; i < build_num/100; ++i) { + EdgeGrid::Grid grid { }; + grid.create(lines, scaled(1.0), true); + grid.calculate_sdf(); + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + duration time_span = duration_cast>(t2 - t1); + std::cout << "It took " << time_span.count() << " seconds." << std::endl << std::endl; + } + { + std::cout << "building the grid res 10mm " << build_num << " times..." << std::endl; + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + for (int i = 0; i < build_num; ++i) { + EdgeGrid::Grid grid { }; + grid.create(lines, scaled(10.0), true); + grid.calculate_sdf(); + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + duration time_span = duration_cast>(t2 - t1); + std::cout << "It took " << time_span.count() << " seconds." << std::endl << std::endl; + } + + EdgeGrid::Grid grid10 { }; + grid10.create(lines, scaled(10.0), true); + coord_t query10_res = scaled(10.0); + grid10.calculate_sdf(); + + EdgeGrid::Grid grid1 { }; + grid1.create(lines, scaled(1.0), true); + coord_t query1_res = scaled(1.0); + grid1.calculate_sdf(); + + auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(linesf); + + int query_num = 10000; + Points query_points { }; + std::vector query_pointsf { }; + for (int x = 0; x < query_num; ++x) { + Vec2d qp { rand() / (double(RAND_MAX) + 1.0f) * 200.0 - 100.0, rand() / (double(RAND_MAX) + 1.0f) * 200.0 + - 100.0 }; + query_pointsf.push_back(qp); + query_points.push_back(Point::new_scale(qp)); + } + + { + std::cout << "querying tree " << query_num << " times..." << std::endl; + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + for (const Vec2d &qp : query_pointsf) { + size_t hit_idx_out; + Vec2d hit_point_out; + AABBTreeLines::squared_distance_to_indexed_lines(linesf, tree, qp, hit_idx_out, hit_point_out); + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + duration time_span = duration_cast>(t2 - t1); + std::cout << "It took " << time_span.count() << " seconds." << std::endl << std::endl; + } + + { + std::cout << "querying grid res 1mm " << query_num << " times..." << std::endl; + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + for (const Point &qp : query_points) { + volatile auto dist = grid1.closest_point_signed_distance(qp, query1_res); + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + duration time_span = duration_cast>(t2 - t1); + std::cout << "It took " << time_span.count() << " seconds." << std::endl << std::endl; + } + + { + std::cout << "querying grid res 10mm " << query_num << " times..." << std::endl; + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + for (const Point &qp : query_points) { + volatile auto dist = grid10.closest_point_signed_distance(qp, query10_res); + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + duration time_span = duration_cast>(t2 - t1); + std::cout << "It took " << time_span.count() << " seconds." << std::endl << std::endl; + } + + std::cout << "Test build and queries together - same number of contour points and query points" << std::endl << std::endl; + + std::vector point_counts { 100, 300, 500, 1000, 3000 }; + for (auto count : point_counts) { + + std::vector lines { Points { } }; + std::vector linesf { }; + Vec2d prevf { }; + Points query_points { }; + std::vector query_pointsf { }; + + for (int x = 0; x < count; ++x) { + Vec2d cp { rand() / (double(RAND_MAX) + 1.0f) * 200.0 - 100.0, rand() / (double(RAND_MAX) + 1.0f) * 200.0 + - 100.0 }; + lines[0].push_back(Point::new_scale(cp)); + linesf.emplace_back(prevf, cp); + prevf = linesf.back().b; + + Vec2d qp { rand() / (double(RAND_MAX) + 1.0f) * 200.0 - 100.0, rand() / (double(RAND_MAX) + 1.0f) * 200.0 + - 100.0 }; + query_pointsf.push_back(qp); + query_points.push_back(Point::new_scale(qp)); + } + + std::cout << "Test for point count: " << count << std::endl; + { + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(linesf); + for (const Vec2d &qp : query_pointsf) { + size_t hit_idx_out; + Vec2d hit_point_out; + AABBTreeLines::squared_distance_to_indexed_lines(linesf, tree, qp, hit_idx_out, hit_point_out); + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + duration time_span = duration_cast>(t2 - t1); + std::cout << " Tree took " << time_span.count() << " seconds." << std::endl; + } + + { + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + EdgeGrid::Grid grid1 { }; + grid1.create(lines, scaled(1.0), true); + coord_t query1_res = scaled(1.0); + grid1.calculate_sdf(); + for (const Point &qp : query_points) { + volatile auto dist = grid1.closest_point_signed_distance(qp, query1_res); + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + duration time_span = duration_cast>(t2 - t1); + std::cout << " Grid 1mm took " << time_span.count() << " seconds." << std::endl; + } + + { + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + EdgeGrid::Grid grid10 { }; + grid10.create(lines, scaled(10.0), true); + coord_t query10_res = scaled(10.0); + grid10.calculate_sdf(); + for (const Point &qp : query_points) { + volatile auto dist = grid10.closest_point_signed_distance(qp, query10_res); + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + duration time_span = duration_cast>(t2 - t1); + std::cout << " Grid 10mm took " << time_span.count() << " seconds." << std::endl; + } + + } + + + + std::cout << "Test build and queries together - same number of contour points and query points" << std::endl << + "And with limited contour edge length to 4mm " << std::endl; + for (auto count : point_counts) { + + std::vector lines { Points { } }; + std::vector linesf { }; + Vec2d prevf { }; + Points query_points { }; + std::vector query_pointsf { }; + + for (int x = 0; x < count; ++x) { + Vec2d cp { rand() / (double(RAND_MAX) + 1.0f) * 200.0 - 100.0, rand() / (double(RAND_MAX) + 1.0f) * 200.0 + - 100.0 }; + Vec2d contour = prevf + cp.normalized()*4.0; // limits the cnotour edge len to 4mm + lines[0].push_back(Point::new_scale(contour)); + linesf.emplace_back(prevf, contour); + prevf = linesf.back().b; + + Vec2d qp { rand() / (double(RAND_MAX) + 1.0f) * 200.0 - 100.0, rand() / (double(RAND_MAX) + 1.0f) * 200.0 + - 100.0 }; + query_pointsf.push_back(qp); + query_points.push_back(Point::new_scale(qp)); + } + + std::cout << "Test for point count: " << count << std::endl; + { + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(linesf); + for (const Vec2d &qp : query_pointsf) { + size_t hit_idx_out; + Vec2d hit_point_out; + AABBTreeLines::squared_distance_to_indexed_lines(linesf, tree, qp, hit_idx_out, hit_point_out); + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + duration time_span = duration_cast>(t2 - t1); + std::cout << " Tree took " << time_span.count() << " seconds." << std::endl; + } + + { + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + EdgeGrid::Grid grid1 { }; + grid1.create(lines, scaled(1.0), true); + coord_t query1_res = scaled(1.0); + grid1.calculate_sdf(); + for (const Point &qp : query_points) { + volatile auto dist = grid1.closest_point_signed_distance(qp, query1_res); + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + duration time_span = duration_cast>(t2 - t1); + std::cout << " Grid 1mm took " << time_span.count() << " seconds." << std::endl; + } + + { + high_resolution_clock::time_point t1 = high_resolution_clock::now(); + EdgeGrid::Grid grid10 { }; + grid10.create(lines, scaled(10.0), true); + coord_t query10_res = scaled(10.0); + grid10.calculate_sdf(); + for (const Point &qp : query_points) { + volatile auto dist = grid10.closest_point_signed_distance(qp, query10_res); + } + high_resolution_clock::time_point t2 = high_resolution_clock::now(); + duration time_span = duration_cast>(t2 - t1); + std::cout << " Grid 10mm took " << time_span.count() << " seconds." << std::endl; + } + + } + +} +#endif + From e82ef1094b836d9334a26c0dabc1a6f274690b09 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 12 May 2022 13:29:13 +0200 Subject: [PATCH 003/169] Fix crash when optimizing rotation probably helps to solve #8319 --- src/slic3r/GUI/Jobs/RotoptimizeJob.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index e88d24fcd..b277aa206 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -101,16 +101,18 @@ void RotoptimizeJob::finalize(bool canceled, std::exception_ptr &eptr) auto trmatrix = oi->get_transformation().get_matrix(); Polygon trchull = o->convex_hull_2d(trmatrix); - - MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); - double phi = rotbb.angle_to_X(); - - // The box should be landscape - if(rotbb.width() < rotbb.height()) phi += PI / 2; - - Vec3d rt = oi->get_rotation(); rt(Z) += phi; - - oi->set_rotation(rt); + + if (!trchull.empty()) { + MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); + double phi = rotbb.angle_to_X(); + + // The box should be landscape + if(rotbb.width() < rotbb.height()) phi += PI / 2; + + Vec3d rt = oi->get_rotation(); rt(Z) += phi; + + oi->set_rotation(rt); + } } // Correct the z offset of the object which was corrupted be From bff32c9cdbcf30603d392777f02365d1bf009a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 12 May 2022 09:49:07 +0200 Subject: [PATCH 004/169] Added cancellation to Lightning infill. --- src/libslic3r/Fill/FillLightning.cpp | 4 ++-- src/libslic3r/Fill/FillLightning.hpp | 2 +- src/libslic3r/Fill/Lightning/Generator.cpp | 20 ++++++++++++-------- src/libslic3r/Fill/Lightning/Generator.hpp | 7 +++---- src/libslic3r/Fill/Lightning/Layer.cpp | 5 ++++- src/libslic3r/Fill/Lightning/Layer.hpp | 3 ++- src/libslic3r/PrintObject.cpp | 2 +- 7 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/libslic3r/Fill/FillLightning.cpp b/src/libslic3r/Fill/FillLightning.cpp index 447fd8057..2ba6fe017 100644 --- a/src/libslic3r/Fill/FillLightning.cpp +++ b/src/libslic3r/Fill/FillLightning.cpp @@ -21,9 +21,9 @@ void GeneratorDeleter::operator()(Generator *p) { delete p; } -GeneratorPtr build_generator(const PrintObject &print_object) +GeneratorPtr build_generator(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { - return GeneratorPtr(new Generator(print_object)); + return GeneratorPtr(new Generator(print_object, throw_on_cancel_callback)); } } // namespace Slic3r::FillAdaptive diff --git a/src/libslic3r/Fill/FillLightning.hpp b/src/libslic3r/Fill/FillLightning.hpp index b4e7e35f1..941392103 100644 --- a/src/libslic3r/Fill/FillLightning.hpp +++ b/src/libslic3r/Fill/FillLightning.hpp @@ -14,7 +14,7 @@ class Generator; struct GeneratorDeleter { void operator()(Generator *p); }; using GeneratorPtr = std::unique_ptr; -GeneratorPtr build_generator(const PrintObject &print_object); +GeneratorPtr build_generator(const PrintObject &print_object, const std::function &throw_on_cancel_callback); class Filler : public Slic3r::Fill { diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index 0bdd1c7e8..b4c07a338 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -25,7 +25,7 @@ namespace Slic3r::FillLightning { -Generator::Generator(const PrintObject &print_object) +Generator::Generator(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { const PrintConfig &print_config = print_object.print()->config(); const PrintObjectConfig &object_config = print_object.config(); @@ -47,11 +47,11 @@ Generator::Generator(const PrintObject &print_object) m_prune_length = coord_t(layer_thickness * std::tan(lightning_infill_prune_angle)); m_straightening_max_distance = coord_t(layer_thickness * std::tan(lightning_infill_straightening_angle)); - generateInitialInternalOverhangs(print_object); - generateTrees(print_object); + generateInitialInternalOverhangs(print_object, throw_on_cancel_callback); + generateTrees(print_object, throw_on_cancel_callback); } -void Generator::generateInitialInternalOverhangs(const PrintObject &print_object) +void Generator::generateInitialInternalOverhangs(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { m_overhang_per_layer.resize(print_object.layers().size()); // FIXME: It can be adjusted to improve bonding between infill and perimeters. @@ -59,7 +59,8 @@ void Generator::generateInitialInternalOverhangs(const PrintObject &print_object Polygons infill_area_above; //Iterate from top to bottom, to subtract the overhang areas above from the overhang areas on the layer below, to get only overhang in the top layer where it is overhanging. - for (int layer_nr = int(print_object.layers().size()) - 1; layer_nr >= 0; layer_nr--) { + for (int layer_nr = int(print_object.layers().size()) - 1; layer_nr >= 0; --layer_nr) { + throw_on_cancel_callback(); Polygons infill_area_here; for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions()) for (const Surface& surface : layerm->fill_surfaces.surfaces) @@ -80,7 +81,7 @@ const Layer& Generator::getTreesForLayer(const size_t& layer_id) const return m_lightning_layers[layer_id]; } -void Generator::generateTrees(const PrintObject &print_object) +void Generator::generateTrees(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { m_lightning_layers.resize(print_object.layers().size()); // FIXME: It can be adjusted to improve bonding between infill and perimeters. @@ -89,11 +90,13 @@ void Generator::generateTrees(const PrintObject &print_object) std::vector infill_outlines(print_object.layers().size(), Polygons()); // For-each layer from top to bottom: - for (int layer_id = int(print_object.layers().size()) - 1; layer_id >= 0; layer_id--) + for (int layer_id = int(print_object.layers().size()) - 1; layer_id >= 0; layer_id--) { + throw_on_cancel_callback(); for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions()) for (const Surface &surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) append(infill_outlines[layer_id], infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset)); + } // For various operations its beneficial to quickly locate nearby features on the polygon: const size_t top_layer_id = print_object.layers().size() - 1; @@ -102,6 +105,7 @@ void Generator::generateTrees(const PrintObject &print_object) // For-each layer from top to bottom: for (int layer_id = int(top_layer_id); layer_id >= 0; layer_id--) { + throw_on_cancel_callback(); Layer ¤t_lightning_layer = m_lightning_layers[layer_id]; const Polygons ¤t_outlines = infill_outlines[layer_id]; const BoundingBox ¤t_outlines_bbox = get_extents(current_outlines); @@ -109,7 +113,7 @@ void Generator::generateTrees(const PrintObject &print_object) // register all trees propagated from the previous layer as to-be-reconnected std::vector to_be_reconnected_tree_roots = current_lightning_layer.tree_roots; - current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, current_outlines_bbox, outlines_locator, m_supporting_radius, m_wall_supporting_radius); + current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, current_outlines_bbox, outlines_locator, m_supporting_radius, m_wall_supporting_radius, throw_on_cancel_callback); current_lightning_layer.reconnectRoots(to_be_reconnected_tree_roots, current_outlines, current_outlines_bbox, outlines_locator, m_supporting_radius, m_wall_supporting_radius); // Initialize trees for next lower layer from the current one. diff --git a/src/libslic3r/Fill/Lightning/Generator.hpp b/src/libslic3r/Fill/Lightning/Generator.hpp index b538c4dbe..d7e5f63f9 100644 --- a/src/libslic3r/Fill/Lightning/Generator.hpp +++ b/src/libslic3r/Fill/Lightning/Generator.hpp @@ -43,9 +43,8 @@ public: * This generator will pre-compute things in preparation of generating * Lightning Infill for the infill areas in that mesh. The infill areas must * already be calculated at this point. - * \param mesh The mesh to generate infill for. */ - explicit Generator(const PrintObject &print_object); + explicit Generator(const PrintObject &print_object, const std::function &throw_on_cancel_callback); /*! * Get a tree of paths generated for a certain layer of the mesh. @@ -69,12 +68,12 @@ protected: * only when support is generated. For this pattern, we also need to * generate overhang areas for the inside of the model. */ - void generateInitialInternalOverhangs(const PrintObject &print_object); + void generateInitialInternalOverhangs(const PrintObject &print_object, const std::function &throw_on_cancel_callback); /*! * Calculate the tree structure of all layers. */ - void generateTrees(const PrintObject &print_object); + void generateTrees(const PrintObject &print_object, const std::function &throw_on_cancel_callback); float m_infill_extrusion_width; diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index c996b9b7b..c381d5505 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -44,10 +44,12 @@ void Layer::generateNewTrees const BoundingBox& current_outlines_bbox, const EdgeGrid::Grid& outlines_locator, const coord_t supporting_radius, - const coord_t wall_supporting_radius + const coord_t wall_supporting_radius, + const std::function &throw_on_cancel_callback ) { DistanceField distance_field(supporting_radius, current_outlines, current_outlines_bbox, current_overhang); + throw_on_cancel_callback(); SparseNodeGrid tree_node_locator; fillLocator(tree_node_locator, current_outlines_bbox); @@ -56,6 +58,7 @@ void Layer::generateNewTrees // Determine next point from tree/outline areas via distance-field Point unsupported_location; while (distance_field.tryGetNextPoint(&unsupported_location)) { + throw_on_cancel_callback(); GroundingLocation grounding_loc = getBestGroundingLocation( unsupported_location, current_outlines, current_outlines_bbox, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator); diff --git a/src/libslic3r/Fill/Lightning/Layer.hpp b/src/libslic3r/Fill/Lightning/Layer.hpp index cc79c15b5..87431fb1c 100644 --- a/src/libslic3r/Fill/Lightning/Layer.hpp +++ b/src/libslic3r/Fill/Lightning/Layer.hpp @@ -44,7 +44,8 @@ public: const BoundingBox& current_outlines_bbox, const EdgeGrid::Grid& outline_locator, coord_t supporting_radius, - coord_t wall_supporting_radius + coord_t wall_supporting_radius, + const std::function &throw_on_cancel_callback ); /*! Determine & connect to connection point in tree/outline. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 9c6178f8b..eeaf1b13c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -464,7 +464,7 @@ FillLightning::GeneratorPtr PrintObject::prepare_lightning_infill_data() break; } - return has_lightning_infill ? FillLightning::build_generator(std::as_const(*this)) : FillLightning::GeneratorPtr(); + return has_lightning_infill ? FillLightning::build_generator(std::as_const(*this), [this]() -> void { this->throw_if_canceled(); }) : FillLightning::GeneratorPtr(); } void PrintObject::clear_layers() From 7b28bdc41f4b0820b3de637e3f880d3a899ab1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 12 May 2022 14:16:32 +0200 Subject: [PATCH 005/169] Fixed function lineSegmentPolygonsIntersection() in the Lightning infill that wasn't returns the intersection point. It fixed the issue that sometimes the Lightning infill produced extrusions that hung in the air without proper support. --- src/libslic3r/Fill/Lightning/TreeNode.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Fill/Lightning/TreeNode.cpp b/src/libslic3r/Fill/Lightning/TreeNode.cpp index 822550fc4..9ef509611 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.cpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.cpp @@ -180,7 +180,11 @@ bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeG } visitor { outline_locator, a.cast(), b.cast() }; outline_locator.visit_cells_intersecting_line(a, b, visitor); - return visitor.d2min < double(within_max_dist) * double(within_max_dist); + if (visitor.d2min < double(within_max_dist) * double(within_max_dist)) { + result = Point(visitor.intersection_pt); + return true; + } + return false; } bool Node::realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector& rerooted_parts) From ae14f677c2a72aa2611e110421b29aefe4af9e17 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 13 May 2022 15:34:30 +0200 Subject: [PATCH 006/169] Fix of a crash when using selection rectangle with a gizmo open --- src/slic3r/GUI/GLCanvas3D.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8a8ccfe1c..adab374f5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7347,12 +7347,13 @@ bool GLCanvas3D::_is_any_volume_outside() const void GLCanvas3D::_update_selection_from_hover() { bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); + bool selection_changed = false; if (m_hover_volume_idxs.empty()) { - if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) + if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) { + selection_changed = ! m_selection.is_empty(); m_selection.remove_all(); - - return; + } } GLSelectionRectangle::EState state = m_rectangle_selection.get_state(); @@ -7365,7 +7366,6 @@ void GLCanvas3D::_update_selection_from_hover() } } - bool selection_changed = false; #if ENABLE_NEW_RECTANGLE_SELECTION if (!m_rectangle_selection.is_empty()) { #endif // ENABLE_NEW_RECTANGLE_SELECTION From ca3df3926e60ca8dca569139f5978db735564da2 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 13 May 2022 16:54:57 +0200 Subject: [PATCH 007/169] typo in notification text --- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 00bfd8ceb..43169df10 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -147,7 +147,7 @@ void GLGizmoSimplify::add_simplify_suggestion_notification( for (size_t object_id : big_ids) { std::string t = GUI::format(_L( "Processing model '%1%' with more than 1M triangles " - "could be slow. It is highly recommend to reduce " + "could be slow. It is highly recommended to reduce " "amount of triangles."), objects[object_id]->name); std::string hypertext = _u8L("Simplify model"); From de84fbf23d501acb8638c44692f97c61d0ba8ef4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 16 May 2022 10:28:43 +0200 Subject: [PATCH 008/169] Remove junk output to stdout from various tests --- tests/libslic3r/test_astar.cpp | 9 ------- tests/libslic3r/test_marchingsquares.cpp | 34 ++++++++++++------------ 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/tests/libslic3r/test_astar.cpp b/tests/libslic3r/test_astar.cpp index 176d7694a..f673ad9fe 100644 --- a/tests/libslic3r/test_astar.cpp +++ b/tests/libslic3r/test_astar.cpp @@ -54,18 +54,9 @@ TEST_CASE("astar algorithm test over 3D point grid", "[AStar]") { auto pgrid = point_grid(ex_seq, vol, {0.1f, 0.1f, 0.1f}); - size_t target = pgrid.point_count() - 1; - - std::cout << "Tracing route to " << pgrid.get_coord(target).transpose() << std::endl; PointGridTracer pgt{pgrid, pgrid.point_count() - 1}; std::vector out; bool found = astar::search_route(pgt, size_t(0), std::back_inserter(out)); - std::cout << "Route taken: "; - for (size_t i : out) { - std::cout << "(" << pgrid.get_coord(i).transpose() << ") "; - } - std::cout << std::endl; - REQUIRE(found); } diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index 32b137175..0fbe6a5e3 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -325,9 +325,9 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) { double disp_w = 120.96; double disp_h = 68.04; -#ifndef NDEBUG - size_t cntr = 0; -#endif +//#ifndef NDEBUG +// size_t cntr = 0; +//#endif for (ExPolygons &layer : layers) { auto rst = create_raster(res, disp_w, disp_h); @@ -335,11 +335,11 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) { rst.draw(island); } -#ifndef NDEBUG - std::fstream out(objname + std::to_string(cntr) + ".png", std::ios::out); - out << rst.encode(sla::PNGRasterEncoder{}); - out.close(); -#endif +//#ifndef NDEBUG +// std::fstream out(objname + std::to_string(cntr) + ".png", std::ios::out); +// out << rst.encode(sla::PNGRasterEncoder{}); +// out.close(); +//#endif ExPolygons layer_ = sla::raster_to_polygons(rst); // float delta = scaled(std::min(rst.pixel_dimensions().h_mm, @@ -347,19 +347,19 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) { // layer_ = expolygons_simplify(layer_, delta); -#ifndef NDEBUG - SVG svg(objname + std::to_string(cntr) + ".svg", BoundingBox(Point{0, 0}, Point{scaled(disp_w), scaled(disp_h)})); - svg.draw(layer_); - svg.draw(layer, "green"); - svg.Close(); -#endif +//#ifndef NDEBUG +// SVG svg(objname + std::to_string(cntr) + ".svg", BoundingBox(Point{0, 0}, Point{scaled(disp_w), scaled(disp_h)})); +// svg.draw(layer_); +// svg.draw(layer, "green"); +// svg.Close(); +//#endif double layera = 0., layera_ = 0.; for (auto &p : layer) layera += p.area(); for (auto &p : layer_) layera_ += p.area(); -#ifndef NDEBUG - std::cout << cntr++ << std::endl; -#endif +//#ifndef NDEBUG +// std::cout << cntr++ << std::endl; +//#endif double diff = std::abs(layera_ - layera); REQUIRE((diff <= 0.1 * layera || diff < scaled(1.) * scaled(1.))); From fd0579d4a2cbb34c4f0637484dd85700f8ecdbfb Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 16 May 2022 10:30:22 +0200 Subject: [PATCH 009/169] Add missing includes to MutablePriorityQueue Mainly to prevent heuristic error reports inside IDE --- src/libslic3r/MutablePriorityQueue.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libslic3r/MutablePriorityQueue.hpp b/src/libslic3r/MutablePriorityQueue.hpp index 418d8deff..88fbf48a5 100644 --- a/src/libslic3r/MutablePriorityQueue.hpp +++ b/src/libslic3r/MutablePriorityQueue.hpp @@ -3,6 +3,9 @@ #include #include +#include +#include +#include // adds size_t (without std::) template class MutablePriorityQueue From 15a082b80b1d9ed3cfa70d3d8133c4cd1e5d699d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 16 May 2022 11:34:06 +0200 Subject: [PATCH 010/169] Fix of libslic3r "Mutable priority queue - first pop" test failure #8276 Improved readability by introducing invalid_id() getter. Made the ResetIndexWhenRemoved flag active in both debug and release mode, it used to be made active by Vojtech for release mode only for unknown reason. --- src/libslic3r/MutablePriorityQueue.hpp | 49 ++++++------------- .../libslic3r/test_mutable_priority_queue.cpp | 25 +++++----- 2 files changed, 25 insertions(+), 49 deletions(-) diff --git a/src/libslic3r/MutablePriorityQueue.hpp b/src/libslic3r/MutablePriorityQueue.hpp index 88fbf48a5..81f3cb782 100644 --- a/src/libslic3r/MutablePriorityQueue.hpp +++ b/src/libslic3r/MutablePriorityQueue.hpp @@ -41,6 +41,7 @@ public: bool empty() const { return m_heap.empty(); } T& operator[](std::size_t idx) noexcept { return m_heap[idx]; } const T& operator[](std::size_t idx) const noexcept { return m_heap[idx]; } + static constexpr size_t invalid_id() { return std::numeric_limits::max(); } using iterator = typename std::vector::iterator; using const_iterator = typename std::vector::const_iterator; @@ -69,14 +70,10 @@ MutablePriorityQueue make_ template inline void MutablePriorityQueue::clear() { -#ifdef NDEBUG - // Only mark as removed from the queue in release mode, if configured so. - if (ResetIndexWhenRemoved) -#endif /* NDEBUG */ - { + if constexpr (ResetIndexWhenRemoved) { for (size_t idx = 0; idx < m_heap.size(); ++ idx) // Mark as removed from the queue. - m_index_setter(m_heap[idx], std::numeric_limits::max()); + m_index_setter(m_heap[idx], this->invalid_id()); } m_heap.clear(); } @@ -103,13 +100,9 @@ template::pop() { assert(! m_heap.empty()); -#ifdef NDEBUG - // Only mark as removed from the queue in release mode, if configured so. - if (ResetIndexWhenRemoved) -#endif /* NDEBUG */ - { + if constexpr (ResetIndexWhenRemoved) { // Mark as removed from the queue. - m_index_setter(m_heap.front(), std::numeric_limits::max()); + m_index_setter(m_heap.front(), this->invalid_id()); } if (m_heap.size() > 1) { m_heap.front() = m_heap.back(); @@ -124,13 +117,10 @@ template::remove(size_t idx) { assert(idx < m_heap.size()); -#ifdef NDEBUG // Only mark as removed from the queue in release mode, if configured so. - if (ResetIndexWhenRemoved) -#endif /* NDEBUG */ - { + if constexpr (ResetIndexWhenRemoved) { // Mark as removed from the queue. - m_index_setter(m_heap[idx], std::numeric_limits::max()); + m_index_setter(m_heap[idx], this->invalid_id()); } if (idx + 1 == m_heap.size()) { m_heap.pop_back(); @@ -291,6 +281,7 @@ public: bool empty() const { return m_heap.empty(); } T& operator[](std::size_t idx) noexcept { assert(! address::is_padding(idx)); return m_heap[idx]; } const T& operator[](std::size_t idx) const noexcept { assert(! address::is_padding(idx)); return m_heap[idx]; } + static constexpr size_t invalid_id() { return std::numeric_limits::max(); } protected: void update_heap_up(size_t top, size_t bottom); @@ -320,15 +311,11 @@ MutableSkipHeapPriorityQueue inline void MutableSkipHeapPriorityQueue::clear() { -#ifdef NDEBUG - // Only mark as removed from the queue in release mode, if configured so. - if (ResetIndexWhenRemoved) -#endif /* NDEBUG */ - { + if constexpr (ResetIndexWhenRemoved) { for (size_t idx = 0; idx < m_heap.size(); ++ idx) // Mark as removed from the queue. if (! address::is_padding(idx)) - m_index_setter(m_heap[idx], std::numeric_limits::max()); + m_index_setter(m_heap[idx], this->invalid_id()); } m_heap.clear(); } @@ -359,13 +346,9 @@ template::pop() { assert(! m_heap.empty()); -#ifdef NDEBUG - // Only mark as removed from the queue in release mode, if configured so. - if (ResetIndexWhenRemoved) -#endif /* NDEBUG */ - { + if constexpr (ResetIndexWhenRemoved) { // Mark as removed from the queue. - m_index_setter(m_heap[1], std::numeric_limits::max()); + m_index_setter(m_heap[1], this->invalid_id()); } // Zero'th element is padding, thus non-empty queue must have at least two elements. if (m_heap.size() > 2) { @@ -382,13 +365,9 @@ inline void MutableSkipHeapPriorityQueue::max()); + m_index_setter(m_heap[idx], this->invalid_id()); } if (idx + 1 == m_heap.size()) { this->pop_back(); diff --git a/tests/libslic3r/test_mutable_priority_queue.cpp b/tests/libslic3r/test_mutable_priority_queue.cpp index 78c97bc7e..5ea3db276 100644 --- a/tests/libslic3r/test_mutable_priority_queue.cpp +++ b/tests/libslic3r/test_mutable_priority_queue.cpp @@ -346,25 +346,22 @@ TEST_CASE("Mutable priority queue - first pop", "[MutableSkipHeapPriorityQueue]" size_t id; float val; }; - size_t count = 50000; - std::vector idxs(count, {0}); - std::vector dels(count, false); + static constexpr const size_t count = 50000; auto q = make_miniheap_mutable_priority_queue( - [&](MyValue &v, size_t idx) { - idxs[v.id] = idx; - }, + [](MyValue &v, size_t idx) { v.id = idx; }, [](MyValue &l, MyValue &r) { return l.val < r.val; }); + using QueueType = decltype(q); q.reserve(count); - for (size_t id = 0; id < count; id++) { - MyValue mv{ id, rand() / 100.f }; - q.push(mv); - } - MyValue it = q.top(); // copy - q.pop(); + for (size_t id = 0; id < count; ++ id) + q.push({ id, rand() / 100.f }); + MyValue v = q.top(); // copy // is valid id (no initial value default value) - CHECK(it.id != 0); + CHECK(QueueType::address::is_padding(0)); + CHECK(!QueueType::address::is_padding(1)); + q.pop(); + CHECK(v.id == 1); // is first item in queue valid value - CHECK(idxs[0] != std::numeric_limits::max()); + CHECK(q.top().id == 1); } TEST_CASE("Mutable priority queue complex", "[MutableSkipHeapPriorityQueue]") From 3a7cdeeae4d695521405c5a52b1b436ca3928fff Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 16 May 2022 14:02:46 +0200 Subject: [PATCH 011/169] Revamp CURL integration. Exclude it from APPLE --- CMakeLists.txt | 29 ++++++++++++----------------- deps/CMakeLists.txt | 9 +++++++-- deps/CURL/CURL.cmake | 14 ++++++++------ deps/deps-macos.cmake | 5 +++++ src/slic3r/CMakeLists.txt | 4 ---- 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a486000f..d5f96acd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,14 @@ set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux") set(IS_CROSS_COMPILE FALSE) +if (SLIC3R_STATIC) + # Prefer config scripts over find modules. This is helpful when building with + # the static dependencies. Many libraries have their own export scripts + # while having a Find module in standard cmake installation. + # (e.g. CURL) + set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) +endif () + if (APPLE) set(CMAKE_FIND_FRAMEWORK LAST) set(CMAKE_FIND_APPBUNDLE LAST) @@ -438,23 +446,6 @@ else() target_link_libraries(libcurl INTERFACE crypt32) endif() -if (SLIC3R_STATIC AND NOT SLIC3R_STATIC_EXCLUDE_CURL) - if (NOT APPLE) - # libcurl is always linked dynamically to the system libcurl on OSX. - # On other systems, libcurl is linked statically if SLIC3R_STATIC is set. - target_compile_definitions(libcurl INTERFACE CURL_STATICLIB) - endif() - if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - # As of now, our build system produces a statically linked libcurl, - # which links the OpenSSL library dynamically. - find_package(OpenSSL REQUIRED) - message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}") - message("OpenSSL libraries: ${OPENSSL_LIBRARIES}") - target_include_directories(libcurl INTERFACE ${OPENSSL_INCLUDE_DIR}) - target_link_libraries(libcurl INTERFACE ${OPENSSL_LIBRARIES}) - endif() -endif() - ## OPTIONAL packages # Find eigen3 or use bundled version @@ -472,6 +463,10 @@ include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR}) find_package(EXPAT REQUIRED) +if (NOT TARGET EXPAT::EXPAT AND TARGET expat::expat) + add_library(EXPAT::EXPAT ALIAS expat::expat) +endif () + find_package(PNG REQUIRED) set(OpenGL_GL_PREFERENCE "LEGACY") diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 94daee85f..d129ff1c2 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -179,7 +179,12 @@ include(CGAL/CGAL.cmake) include(NLopt/NLopt.cmake) include(OpenSSL/OpenSSL.cmake) -include(CURL/CURL.cmake) + +set(CURL_PKG "") +if (NOT CURL_FOUND) + include(CURL/CURL.cmake) + set(CURL_PKG dep_CURL) +endif () include(JPEG/JPEG.cmake) include(TIFF/TIFF.cmake) @@ -188,7 +193,7 @@ include(wxWidgets/wxWidgets.cmake) set(_dep_list dep_Boost dep_TBB - dep_CURL + ${CURL_PKG} dep_wxWidgets dep_Cereal dep_NLopt diff --git a/deps/CURL/CURL.cmake b/deps/CURL/CURL.cmake index a05a4e97e..579a27f66 100644 --- a/deps/CURL/CURL.cmake +++ b/deps/CURL/CURL.cmake @@ -48,11 +48,13 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") ) endif () -if (BUILD_SHARED_LIBS) - set(_curl_static OFF) -else() - set(_curl_static ON) -endif() +set(_patch_command "") +if (UNIX AND NOT APPLE) + # On non-apple UNIX platforms, finding the location of OpenSSL certificates is necessary at runtime, as there is no standard location usable across platforms. + # The OPENSSL_CERT_OVERRIDE flag is understood by PrusaSlicer and will trigger the search of certificates at initial application launch. + # Then ask the user for consent about the correctness of the found location. + set (_patch_command echo set_target_properties(CURL::libcurl PROPERTIES INTERFACE_COMPILE_DEFINITIONS OPENSSL_CERT_OVERRIDE) >> CMake/curl-config.cmake.in) +endif () prusaslicer_add_cmake_project(CURL # GIT_REPOSITORY https://github.com/curl/curl.git @@ -62,10 +64,10 @@ prusaslicer_add_cmake_project(CURL DEPENDS ${ZLIB_PKG} # PATCH_COMMAND ${GIT_EXECUTABLE} checkout -f -- . && git clean -df && # ${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_LIST_DIR}/curl-mods.patch + PATCH_COMMAND "${_patch_command}" CMAKE_ARGS -DBUILD_TESTING:BOOL=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON - -DCURL_STATICLIB=${_curl_static} ${_curl_platform_flags} ) diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake index 42afc623d..d9e0ce377 100644 --- a/deps/deps-macos.cmake +++ b/deps/deps-macos.cmake @@ -15,6 +15,11 @@ set(DEP_CMAKE_OPTS include("deps-unix-common.cmake") +find_package(CURL QUIET) +if (NOT CURL_FOUND) + message(WARNING "No CURL dev package found in system, building static library. Mac SDK should include CURL from at least version 10.12. Check your SDK installation.") +endif () + # ExternalProject_Add(dep_boost # EXCLUDE_FROM_ALL 1 diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index ef7687f00..ed994be18 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -288,10 +288,6 @@ if (SLIC3R_STATIC) target_compile_definitions(libslic3r_gui PUBLIC -DwxDEBUG_LEVEL=0) endif() -if (SLIC3R_STATIC AND NOT SLIC3R_STATIC_EXCLUDE_CURL AND UNIX AND NOT APPLE) - target_compile_definitions(libslic3r_gui PRIVATE OPENSSL_CERT_OVERRIDE) -endif () - if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) endif () From dc3cf1f7b8db2a49888731fefe9129760ec7a85f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 16 May 2022 14:29:33 +0200 Subject: [PATCH 012/169] #8327 - Fixed gcode window disappearing when selecting volumetric speed in preview --- src/slic3r/GUI/GCodeViewer.cpp | 8 +- src/slic3r/GUI/GCodeViewer.hpp | 1818 ++++++++++++++++---------------- 2 files changed, 912 insertions(+), 914 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 652410589..2134a795f 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -386,14 +386,14 @@ void GCodeViewer::SequentialView::Marker::render() ImGui::PopStyleVar(); } -void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, std::vector &&lines_ends) +void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, const std::vector& lines_ends) { assert(! m_file.is_open()); if (m_file.is_open()) return; m_filename = filename; - m_lines_ends = std::move(lines_ends); + m_lines_ends = lines_ends; m_selected_line_id = 0; m_last_lines_size = 0; @@ -771,9 +771,7 @@ void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& pr // release gpu memory, if used reset(); - m_sequential_view.gcode_window.load_gcode(gcode_result.filename, - // Stealing out lines_ends should be safe because this gcode_result is processed only once (see the 1st if in this function). - std::move(const_cast&>(gcode_result.lines_ends))); + m_sequential_view.gcode_window.load_gcode(gcode_result.filename, gcode_result.lines_ends); if (wxGetApp().is_gcode_viewer()) m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z; diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index e2ee6dcd0..51adcab5a 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -1,909 +1,909 @@ -#ifndef slic3r_GCodeViewer_hpp_ -#define slic3r_GCodeViewer_hpp_ - -#include "3DScene.hpp" -#include "libslic3r/GCode/GCodeProcessor.hpp" -#include "GLModel.hpp" - -#include - -#include -#include -#include -#include - -namespace Slic3r { - -class Print; -class TriangleMesh; - -namespace GUI { - -class GCodeViewer -{ - using IBufferType = unsigned short; - using VertexBuffer = std::vector; - using MultiVertexBuffer = std::vector; - using IndexBuffer = std::vector; - using MultiIndexBuffer = std::vector; - using InstanceBuffer = std::vector; - using InstanceIdBuffer = std::vector; - using InstancesOffsets = std::vector; - - static const std::vector Extrusion_Role_Colors; - static const std::vector Options_Colors; - static const std::vector Travel_Colors; - static const std::vector Range_Colors; - static const ColorRGBA Wipe_Color; - static const ColorRGBA Neutral_Color; - - enum class EOptionsColors : unsigned char - { - Retractions, - Unretractions, - Seams, - ToolChanges, - ColorChanges, - PausePrints, - CustomGCodes - }; - - // vbo buffer containing vertices data used to render a specific toolpath type - struct VBuffer - { - enum class EFormat : unsigned char - { - // vertex format: 3 floats -> position.x|position.y|position.z - Position, - // vertex format: 4 floats -> position.x|position.y|position.z|normal.x - PositionNormal1, - // vertex format: 6 floats -> position.x|position.y|position.z|normal.x|normal.y|normal.z - PositionNormal3 - }; - - EFormat format{ EFormat::Position }; - // vbos id - std::vector vbos; - // sizes of the buffers, in bytes, used in export to obj - std::vector sizes; - // count of vertices, updated after data are sent to gpu - size_t count{ 0 }; - - size_t data_size_bytes() const { return count * vertex_size_bytes(); } - // We set 65536 as max count of vertices inside a vertex buffer to allow - // to use unsigned short in place of unsigned int for indices in the index buffer, to save memory - size_t max_size_bytes() const { return 65536 * vertex_size_bytes(); } - - size_t vertex_size_floats() const { return position_size_floats() + normal_size_floats(); } - size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); } - - size_t position_offset_floats() const { return 0; } - size_t position_offset_bytes() const { return position_offset_floats() * sizeof(float); } - - size_t position_size_floats() const { return 3; } - size_t position_size_bytes() const { return position_size_floats() * sizeof(float); } - - size_t normal_offset_floats() const { - assert(format == EFormat::PositionNormal1 || format == EFormat::PositionNormal3); - return position_size_floats(); - } - size_t normal_offset_bytes() const { return normal_offset_floats() * sizeof(float); } - - size_t normal_size_floats() const { - switch (format) - { - case EFormat::PositionNormal1: { return 1; } - case EFormat::PositionNormal3: { return 3; } - default: { return 0; } - } - } - size_t normal_size_bytes() const { return normal_size_floats() * sizeof(float); } - - void reset(); - }; - - // buffer containing instances data used to render a toolpaths using instanced or batched models - // instance record format: - // instanced models: 5 floats -> position.x|position.y|position.z|width|height (which are sent to the shader as -> vec3 (offset) + vec2 (scales) in GLModel::render_instanced()) - // batched models: 3 floats -> position.x|position.y|position.z - struct InstanceVBuffer - { - // ranges used to render only subparts of the intances - struct Ranges - { - struct Range - { - // offset in bytes of the 1st instance to render - unsigned int offset; - // count of instances to render - unsigned int count; - // vbo id - unsigned int vbo{ 0 }; - // Color to apply to the instances - ColorRGBA color; - }; - - std::vector ranges; - - void reset(); - }; - - enum class EFormat : unsigned char - { - InstancedModel, - BatchedModel - }; - - EFormat format; - - // cpu-side buffer containing all instances data - InstanceBuffer buffer; - // indices of the moves for all instances - std::vector s_ids; - // position offsets, used to show the correct value of the tool position - InstancesOffsets offsets; - Ranges render_ranges; - - size_t data_size_bytes() const { return s_ids.size() * instance_size_bytes(); } - - size_t instance_size_floats() const { - switch (format) - { - case EFormat::InstancedModel: { return 5; } - case EFormat::BatchedModel: { return 3; } - default: { return 0; } - } - } - size_t instance_size_bytes() const { return instance_size_floats() * sizeof(float); } - - void reset(); - }; - - // ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type - struct IBuffer - { - // id of the associated vertex buffer - unsigned int vbo{ 0 }; - // ibo id - unsigned int ibo{ 0 }; - // count of indices, updated after data are sent to gpu - size_t count{ 0 }; - - void reset(); - }; - - // Used to identify different toolpath sub-types inside a IBuffer - struct Path - { - struct Endpoint - { - // index of the buffer in the multibuffer vector - // the buffer type may change: - // it is the vertex buffer while extracting vertices data, - // the index buffer while extracting indices data - unsigned int b_id{ 0 }; - // index into the buffer - size_t i_id{ 0 }; - // move id - size_t s_id{ 0 }; - Vec3f position{ Vec3f::Zero() }; - }; - - struct Sub_Path - { - Endpoint first; - Endpoint last; - - bool contains(size_t s_id) const { - return first.s_id <= s_id && s_id <= last.s_id; - } - }; - - EMoveType type{ EMoveType::Noop }; - ExtrusionRole role{ erNone }; - float delta_extruder{ 0.0f }; - float height{ 0.0f }; - float width{ 0.0f }; - float feedrate{ 0.0f }; - float fan_speed{ 0.0f }; - float temperature{ 0.0f }; - float volumetric_rate{ 0.0f }; - unsigned char extruder_id{ 0 }; - unsigned char cp_color_id{ 0 }; - std::vector sub_paths; - -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - bool matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const; -#else - bool matches(const GCodeProcessorResult::MoveVertex& move) const; -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - size_t vertices_count() const { - return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1; - } - bool contains(size_t s_id) const { - return sub_paths.empty() ? false : sub_paths.front().first.s_id <= s_id && s_id <= sub_paths.back().last.s_id; - } - int get_id_of_sub_path_containing(size_t s_id) const { - if (sub_paths.empty()) - return -1; - else { - for (int i = 0; i < static_cast(sub_paths.size()); ++i) { - if (sub_paths[i].contains(s_id)) - return i; - } - return -1; - } - } - void add_sub_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) { - Endpoint endpoint = { b_id, i_id, s_id, move.position }; - sub_paths.push_back({ endpoint , endpoint }); - } - }; - - // Used to batch the indices needed to render the paths - struct RenderPath - { - // Index of the parent tbuffer - unsigned char tbuffer_id; - // Render path property - ColorRGBA color; - // Index of the buffer in TBuffer::indices - unsigned int ibuffer_id; - // Render path content - // Index of the path in TBuffer::paths - unsigned int path_id; - std::vector sizes; - std::vector offsets; // use size_t because we need an unsigned integer whose size matches pointer's size (used in the call glMultiDrawElements()) - bool contains(size_t offset) const { - for (size_t i = 0; i < offsets.size(); ++i) { - if (offsets[i] <= offset && offset <= offsets[i] + static_cast(sizes[i] * sizeof(IBufferType))) - return true; - } - return false; - } - }; - struct RenderPathPropertyLower { - bool operator() (const RenderPath &l, const RenderPath &r) const { - if (l.tbuffer_id < r.tbuffer_id) - return true; - if (l.color < r.color) - return true; - else if (l.color > r.color) - return false; - return l.ibuffer_id < r.ibuffer_id; - } - }; - struct RenderPathPropertyEqual { - bool operator() (const RenderPath &l, const RenderPath &r) const { - return l.tbuffer_id == r.tbuffer_id && l.ibuffer_id == r.ibuffer_id && l.color == r.color; - } - }; - - // buffer containing data for rendering a specific toolpath type - struct TBuffer - { - enum class ERenderPrimitiveType : unsigned char - { - Line, - Triangle, - InstancedModel, - BatchedModel - }; - - ERenderPrimitiveType render_primitive_type; - - // buffers for point, line and triangle primitive types - VBuffer vertices; - std::vector indices; - - struct Model - { - GLModel model; - ColorRGBA color; - InstanceVBuffer instances; - GLModel::Geometry data; - - void reset(); - }; - - // contain the buffer for model primitive types - Model model; - - std::string shader; - std::vector paths; - std::vector render_paths; - bool visible{ false }; - - void reset(); - - // b_id index of buffer contained in this->indices - // i_id index of first index contained in this->indices[b_id] - // s_id index of first vertex contained in this->vertices - void add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id); - - unsigned int max_vertices_per_segment() const { - switch (render_primitive_type) - { - case ERenderPrimitiveType::Line: { return 2; } - case ERenderPrimitiveType::Triangle: { return 8; } - default: { return 0; } - } - } - - size_t max_vertices_per_segment_size_floats() const { return vertices.vertex_size_floats() * static_cast(max_vertices_per_segment()); } - size_t max_vertices_per_segment_size_bytes() const { return max_vertices_per_segment_size_floats() * sizeof(float); } - unsigned int indices_per_segment() const { - switch (render_primitive_type) - { - case ERenderPrimitiveType::Line: { return 2; } - case ERenderPrimitiveType::Triangle: { return 30; } // 3 indices x 10 triangles - default: { return 0; } - } - } - size_t indices_per_segment_size_bytes() const { return static_cast(indices_per_segment() * sizeof(IBufferType)); } - unsigned int max_indices_per_segment() const { - switch (render_primitive_type) - { - case ERenderPrimitiveType::Line: { return 2; } - case ERenderPrimitiveType::Triangle: { return 36; } // 3 indices x 12 triangles - default: { return 0; } - } - } - size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); } - - bool has_data() const { - switch (render_primitive_type) - { - case ERenderPrimitiveType::Line: - case ERenderPrimitiveType::Triangle: { - return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; - } - case ERenderPrimitiveType::InstancedModel: { return model.model.is_initialized() && !model.instances.buffer.empty(); } - case ERenderPrimitiveType::BatchedModel: { -#if ENABLE_LEGACY_OPENGL_REMOVAL - return !model.data.vertices.empty() && !model.data.indices.empty() && -#else - return model.data.vertices_count() > 0 && model.data.indices_count() && -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; - } - default: { return false; } - } - } - }; - - // helper to render shells - struct Shells - { - GLVolumeCollection volumes; - bool visible{ false }; - }; - -#if ENABLE_SHOW_TOOLPATHS_COG - // helper to render center of gravity - class COG - { - GLModel m_model; - bool m_visible{ false }; - // whether or not to render the model with fixed screen size - bool m_fixed_size{ true }; - double m_total_mass{ 0.0 }; - Vec3d m_position{ Vec3d::Zero() }; - - public: - void render(); - - void reset() { - m_position = Vec3d::Zero(); - m_total_mass = 0.0; - } - - bool is_visible() const { return m_visible; } - void set_visible(bool visible) { m_visible = visible; } - - void add_segment(const Vec3d& v1, const Vec3d& v2, double mass) { - assert(mass > 0.0); - m_position += mass * 0.5 * (v1 + v2); - m_total_mass += mass; - } - - Vec3d cog() const { return (m_total_mass > 0.0) ? (Vec3d)(m_position / m_total_mass) : Vec3d::Zero(); } - - private: - void init() { - if (m_model.is_initialized()) - return; - - const float radius = m_fixed_size ? 10.0f : 1.0f; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_model.init_from(smooth_sphere(32, radius)); -#else - m_model.init_from(its_make_sphere(radius, PI / 32.0)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - }; -#endif // ENABLE_SHOW_TOOLPATHS_COG - - // helper to render extrusion paths - struct Extrusions - { - struct Range - { -#if ENABLE_PREVIEW_LAYER_TIME - enum class EType : unsigned char - { - Linear, - Logarithmic - }; -#endif // ENABLE_PREVIEW_LAYER_TIME - - float min; - float max; - unsigned int count; - - Range() { reset(); } - - void update_from(const float value) { - if (value != max && value != min) - ++count; - min = std::min(min, value); - max = std::max(max, value); - } - void reset() { min = FLT_MAX; max = -FLT_MAX; count = 0; } - -#if ENABLE_PREVIEW_LAYER_TIME - float step_size(EType type = EType::Linear) const; - ColorRGBA get_color_at(float value, EType type = EType::Linear) const; -#else - float step_size() const { return (max - min) / (static_cast(Range_Colors.size()) - 1.0f); } - ColorRGBA get_color_at(float value) const; -#endif // ENABLE_PREVIEW_LAYER_TIME - }; - - struct Ranges - { - // Color mapping by layer height. - Range height; - // Color mapping by extrusion width. - Range width; - // Color mapping by feedrate. - Range feedrate; - // Color mapping by fan speed. - Range fan_speed; - // Color mapping by volumetric extrusion rate. - Range volumetric_rate; - // Color mapping by extrusion temperature. - Range temperature; -#if ENABLE_PREVIEW_LAYER_TIME - // Color mapping by layer time. - std::array(PrintEstimatedStatistics::ETimeMode::Count)> layer_time; -#endif // ENABLE_PREVIEW_LAYER_TIME - - void reset() { - height.reset(); - width.reset(); - feedrate.reset(); - fan_speed.reset(); - volumetric_rate.reset(); - temperature.reset(); -#if ENABLE_PREVIEW_LAYER_TIME - for (auto& range : layer_time) { - range.reset(); - } -#endif // ENABLE_PREVIEW_LAYER_TIME - } - }; - - unsigned int role_visibility_flags{ 0 }; - Ranges ranges; - - void reset_role_visibility_flags() { - role_visibility_flags = 0; - for (unsigned int i = 0; i < erCount; ++i) { - role_visibility_flags |= 1 << i; - } - } - - void reset_ranges() { ranges.reset(); } - }; - - class Layers - { - public: - struct Range - { - size_t first{ 0 }; - size_t last{ 0 }; - - bool operator == (const Range& other) const { return first == other.first && last == other.last; } - bool operator != (const Range& other) const { return !operator==(other); } - bool contains(size_t id) const { return first <= id && id <= last; } - }; - - private: - std::vector m_zs; - std::vector m_ranges; - - public: - void append(double z, const Range& range) { - m_zs.emplace_back(z); - m_ranges.emplace_back(range); - } - - void reset() { - m_zs = std::vector(); - m_ranges = std::vector(); - } - - size_t size() const { return m_zs.size(); } - bool empty() const { return m_zs.empty(); } - const std::vector& get_zs() const { return m_zs; } - const std::vector& get_ranges() const { return m_ranges; } - std::vector& get_ranges() { return m_ranges; } - double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; } - Range get_range_at(unsigned int id) const { return (id < m_ranges.size()) ? m_ranges[id] : Range(); } - - bool operator != (const Layers& other) const { - if (m_zs != other.m_zs) - return true; - if (m_ranges != other.m_ranges) - return true; - return false; - } - }; - - // used to render the toolpath caps of the current sequential range - // (i.e. when sliding on the horizontal slider) - struct SequentialRangeCap - { - TBuffer* buffer{ nullptr }; - unsigned int ibo{ 0 }; - unsigned int vbo{ 0 }; - ColorRGBA color; - - ~SequentialRangeCap(); - bool is_renderable() const { return buffer != nullptr; } - void reset(); - size_t indices_count() const { return 6; } - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - struct Statistics - { - // time - int64_t results_time{ 0 }; - int64_t load_time{ 0 }; - int64_t load_vertices{ 0 }; - int64_t smooth_vertices{ 0 }; - int64_t load_indices{ 0 }; - int64_t refresh_time{ 0 }; - int64_t refresh_paths_time{ 0 }; - // opengl calls - int64_t gl_multi_lines_calls_count{ 0 }; - int64_t gl_multi_triangles_calls_count{ 0 }; - int64_t gl_triangles_calls_count{ 0 }; - int64_t gl_instanced_models_calls_count{ 0 }; - int64_t gl_batched_models_calls_count{ 0 }; - // memory - int64_t results_size{ 0 }; - int64_t total_vertices_gpu_size{ 0 }; - int64_t total_indices_gpu_size{ 0 }; - int64_t total_instances_gpu_size{ 0 }; - int64_t max_vbuffer_gpu_size{ 0 }; - int64_t max_ibuffer_gpu_size{ 0 }; - int64_t paths_size{ 0 }; - int64_t render_paths_size{ 0 }; - int64_t models_instances_size{ 0 }; - // other - int64_t travel_segments_count{ 0 }; - int64_t wipe_segments_count{ 0 }; - int64_t extrude_segments_count{ 0 }; - int64_t instances_count{ 0 }; - int64_t batched_count{ 0 }; - int64_t vbuffers_count{ 0 }; - int64_t ibuffers_count{ 0 }; - - void reset_all() { - reset_times(); - reset_opengl(); - reset_sizes(); - reset_others(); - } - - void reset_times() { - results_time = 0; - load_time = 0; - load_vertices = 0; - smooth_vertices = 0; - load_indices = 0; - refresh_time = 0; - refresh_paths_time = 0; - } - - void reset_opengl() { - gl_multi_lines_calls_count = 0; - gl_multi_triangles_calls_count = 0; - gl_triangles_calls_count = 0; - gl_instanced_models_calls_count = 0; - gl_batched_models_calls_count = 0; - } - - void reset_sizes() { - results_size = 0; - total_vertices_gpu_size = 0; - total_indices_gpu_size = 0; - total_instances_gpu_size = 0; - max_vbuffer_gpu_size = 0; - max_ibuffer_gpu_size = 0; - paths_size = 0; - render_paths_size = 0; - models_instances_size = 0; - } - - void reset_others() { - travel_segments_count = 0; - wipe_segments_count = 0; - extrude_segments_count = 0; - instances_count = 0; - batched_count = 0; - vbuffers_count = 0; - ibuffers_count = 0; - } - }; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - -public: - struct SequentialView - { - class Marker - { - GLModel m_model; - Vec3f m_world_position; - Transform3f m_world_transform; - // for seams, the position of the marker is on the last endpoint of the toolpath containing it - // the offset is used to show the correct value of tool position in the "ToolPosition" window - // see implementation of render() method - Vec3f m_world_offset; - float m_z_offset{ 0.5f }; - bool m_visible{ true }; - - public: - void init(); - - const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); } - - void set_world_position(const Vec3f& position); - void set_world_offset(const Vec3f& offset) { m_world_offset = offset; } - - bool is_visible() const { return m_visible; } - void set_visible(bool visible) { m_visible = visible; } - - void render(); - }; - - class GCodeWindow - { - struct Line - { - std::string command; - std::string parameters; - std::string comment; - }; - bool m_visible{ true }; - uint64_t m_selected_line_id{ 0 }; - size_t m_last_lines_size{ 0 }; - std::string m_filename; - boost::iostreams::mapped_file_source m_file; - // map for accessing data in file by line number - std::vector m_lines_ends; - // current visible lines - std::vector m_lines; - - public: - GCodeWindow() = default; - ~GCodeWindow() { stop_mapping_file(); } - void load_gcode(const std::string& filename, std::vector &&lines_ends); - void reset() { - stop_mapping_file(); - m_lines_ends.clear(); - m_lines.clear(); - m_filename.clear(); - } - - void toggle_visibility() { m_visible = !m_visible; } - - void render(float top, float bottom, uint64_t curr_line_id) const; - - void stop_mapping_file(); - }; - - struct Endpoints - { - size_t first{ 0 }; - size_t last{ 0 }; - }; - - bool skip_invisible_moves{ false }; - Endpoints endpoints; - Endpoints current; - Endpoints last_current; - Endpoints global; - Vec3f current_position{ Vec3f::Zero() }; - Vec3f current_offset{ Vec3f::Zero() }; - Marker marker; - GCodeWindow gcode_window; - std::vector gcode_ids; - - void render(float legend_height); - }; - - enum class EViewType : unsigned char - { - FeatureType, - Height, - Width, - Feedrate, - FanSpeed, - Temperature, - VolumetricRate, -#if ENABLE_PREVIEW_LAYER_TIME - LayerTimeLinear, - LayerTimeLogarithmic, -#endif // ENABLE_PREVIEW_LAYER_TIME - Tool, - ColorPrint, - Count - }; - -private: - bool m_gl_data_initialized{ false }; - unsigned int m_last_result_id{ 0 }; -#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - EViewType m_last_view_type{ EViewType::Count }; -#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC - size_t m_moves_count{ 0 }; - std::vector m_buffers{ static_cast(EMoveType::Extrude) }; - // bounding box of toolpaths - BoundingBoxf3 m_paths_bounding_box; - // bounding box of toolpaths + marker tools - BoundingBoxf3 m_max_bounding_box; - float m_max_print_height{ 0.0f }; - std::vector m_tool_colors; - Layers m_layers; - std::array m_layers_z_range; - std::vector m_roles; - size_t m_extruders_count; - std::vector m_extruder_ids; - std::vector m_filament_diameters; - std::vector m_filament_densities; - Extrusions m_extrusions; - SequentialView m_sequential_view; - Shells m_shells; -#if ENABLE_SHOW_TOOLPATHS_COG - COG m_cog; -#endif // ENABLE_SHOW_TOOLPATHS_COG - EViewType m_view_type{ EViewType::FeatureType }; - bool m_legend_enabled{ true }; -#if ENABLE_PREVIEW_LAYOUT - struct LegendResizer - { - bool dirty{ true }; - void reset() { dirty = true; } - }; - LegendResizer m_legend_resizer; -#endif // ENABLE_PREVIEW_LAYOUT - PrintEstimatedStatistics m_print_statistics; - PrintEstimatedStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedStatistics::ETimeMode::Normal }; -#if ENABLE_GCODE_VIEWER_STATISTICS - Statistics m_statistics; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - std::array m_detected_point_sizes = { 0.0f, 0.0f }; - GCodeProcessorResult::SettingsIds m_settings_ids; - std::array m_sequential_range_caps; -#if ENABLE_PREVIEW_LAYER_TIME - std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> m_layers_times; -#endif // ENABLE_PREVIEW_LAYER_TIME - - std::vector m_custom_gcode_per_print_z; - - bool m_contained_in_bed{ true }; - -public: - GCodeViewer(); - ~GCodeViewer() { reset(); } - - void init(); - - // extract rendering data from the given parameters -#if ENABLE_LEGACY_OPENGL_REMOVAL - void load(const GCodeProcessorResult& gcode_result, const Print& print); -#else - void load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - // recalculate ranges in dependence of what is visible and sets tool/print colors - void refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); -#if ENABLE_PREVIEW_LAYOUT - void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; -#else - void refresh_render_paths(); -#endif // ENABLE_PREVIEW_LAYOUT - void update_shells_color_by_extruder(const DynamicPrintConfig* config); - - void reset(); - void render(); -#if ENABLE_SHOW_TOOLPATHS_COG - void render_cog() { m_cog.render(); } -#endif // ENABLE_SHOW_TOOLPATHS_COG - - bool has_data() const { return !m_roles.empty(); } - bool can_export_toolpaths() const; - - const BoundingBoxf3& get_paths_bounding_box() const { return m_paths_bounding_box; } - const BoundingBoxf3& get_max_bounding_box() const { return m_max_bounding_box; } - const std::vector& get_layers_zs() const { return m_layers.get_zs(); } - - const SequentialView& get_sequential_view() const { return m_sequential_view; } - void update_sequential_view_current(unsigned int first, unsigned int last); - - bool is_contained_in_bed() const { return m_contained_in_bed; } - - EViewType get_view_type() const { return m_view_type; } - void set_view_type(EViewType type) { - if (type == EViewType::Count) - type = EViewType::FeatureType; - - m_view_type = type; - } - - bool is_toolpath_move_type_visible(EMoveType type) const; - void set_toolpath_move_type_visible(EMoveType type, bool visible); - unsigned int get_toolpath_role_visibility_flags() const { return m_extrusions.role_visibility_flags; } - void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } - unsigned int get_options_visibility_flags() const; - void set_options_visibility_from_flags(unsigned int flags); - void set_layers_z_range(const std::array& layers_z_range); - - bool is_legend_enabled() const { return m_legend_enabled; } - void enable_legend(bool enable) { m_legend_enabled = enable; } - - void export_toolpaths_to_obj(const char* filename) const; - - void toggle_gcode_window_visibility() { m_sequential_view.gcode_window.toggle_visibility(); } - - std::vector& get_custom_gcode_per_print_z() { return m_custom_gcode_per_print_z; } - size_t get_extruders_count() { return m_extruders_count; } - -#if ENABLE_PREVIEW_LAYOUT - void invalidate_legend() { m_legend_resizer.reset(); } -#endif // ENABLE_PREVIEW_LAYOUT - -private: - void load_toolpaths(const GCodeProcessorResult& gcode_result); -#if ENABLE_LEGACY_OPENGL_REMOVAL - void load_shells(const Print& print); -#else - void load_shells(const Print& print, bool initialized); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if !ENABLE_PREVIEW_LAYOUT - void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; -#endif // !ENABLE_PREVIEW_LAYOUT - void render_toolpaths(); - void render_shells(); - void render_legend(float& legend_height); -#if ENABLE_GCODE_VIEWER_STATISTICS - void render_statistics(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - bool is_visible(ExtrusionRole role) const { - return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0; - } - bool is_visible(const Path& path) const { return is_visible(path.role); } - void log_memory_used(const std::string& label, int64_t additional = 0) const; - ColorRGBA option_color(EMoveType move_type) const; -}; - -} // namespace GUI -} // namespace Slic3r - -#endif // slic3r_GCodeViewer_hpp_ - +#ifndef slic3r_GCodeViewer_hpp_ +#define slic3r_GCodeViewer_hpp_ + +#include "3DScene.hpp" +#include "libslic3r/GCode/GCodeProcessor.hpp" +#include "GLModel.hpp" + +#include + +#include +#include +#include +#include + +namespace Slic3r { + +class Print; +class TriangleMesh; + +namespace GUI { + +class GCodeViewer +{ + using IBufferType = unsigned short; + using VertexBuffer = std::vector; + using MultiVertexBuffer = std::vector; + using IndexBuffer = std::vector; + using MultiIndexBuffer = std::vector; + using InstanceBuffer = std::vector; + using InstanceIdBuffer = std::vector; + using InstancesOffsets = std::vector; + + static const std::vector Extrusion_Role_Colors; + static const std::vector Options_Colors; + static const std::vector Travel_Colors; + static const std::vector Range_Colors; + static const ColorRGBA Wipe_Color; + static const ColorRGBA Neutral_Color; + + enum class EOptionsColors : unsigned char + { + Retractions, + Unretractions, + Seams, + ToolChanges, + ColorChanges, + PausePrints, + CustomGCodes + }; + + // vbo buffer containing vertices data used to render a specific toolpath type + struct VBuffer + { + enum class EFormat : unsigned char + { + // vertex format: 3 floats -> position.x|position.y|position.z + Position, + // vertex format: 4 floats -> position.x|position.y|position.z|normal.x + PositionNormal1, + // vertex format: 6 floats -> position.x|position.y|position.z|normal.x|normal.y|normal.z + PositionNormal3 + }; + + EFormat format{ EFormat::Position }; + // vbos id + std::vector vbos; + // sizes of the buffers, in bytes, used in export to obj + std::vector sizes; + // count of vertices, updated after data are sent to gpu + size_t count{ 0 }; + + size_t data_size_bytes() const { return count * vertex_size_bytes(); } + // We set 65536 as max count of vertices inside a vertex buffer to allow + // to use unsigned short in place of unsigned int for indices in the index buffer, to save memory + size_t max_size_bytes() const { return 65536 * vertex_size_bytes(); } + + size_t vertex_size_floats() const { return position_size_floats() + normal_size_floats(); } + size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); } + + size_t position_offset_floats() const { return 0; } + size_t position_offset_bytes() const { return position_offset_floats() * sizeof(float); } + + size_t position_size_floats() const { return 3; } + size_t position_size_bytes() const { return position_size_floats() * sizeof(float); } + + size_t normal_offset_floats() const { + assert(format == EFormat::PositionNormal1 || format == EFormat::PositionNormal3); + return position_size_floats(); + } + size_t normal_offset_bytes() const { return normal_offset_floats() * sizeof(float); } + + size_t normal_size_floats() const { + switch (format) + { + case EFormat::PositionNormal1: { return 1; } + case EFormat::PositionNormal3: { return 3; } + default: { return 0; } + } + } + size_t normal_size_bytes() const { return normal_size_floats() * sizeof(float); } + + void reset(); + }; + + // buffer containing instances data used to render a toolpaths using instanced or batched models + // instance record format: + // instanced models: 5 floats -> position.x|position.y|position.z|width|height (which are sent to the shader as -> vec3 (offset) + vec2 (scales) in GLModel::render_instanced()) + // batched models: 3 floats -> position.x|position.y|position.z + struct InstanceVBuffer + { + // ranges used to render only subparts of the intances + struct Ranges + { + struct Range + { + // offset in bytes of the 1st instance to render + unsigned int offset; + // count of instances to render + unsigned int count; + // vbo id + unsigned int vbo{ 0 }; + // Color to apply to the instances + ColorRGBA color; + }; + + std::vector ranges; + + void reset(); + }; + + enum class EFormat : unsigned char + { + InstancedModel, + BatchedModel + }; + + EFormat format; + + // cpu-side buffer containing all instances data + InstanceBuffer buffer; + // indices of the moves for all instances + std::vector s_ids; + // position offsets, used to show the correct value of the tool position + InstancesOffsets offsets; + Ranges render_ranges; + + size_t data_size_bytes() const { return s_ids.size() * instance_size_bytes(); } + + size_t instance_size_floats() const { + switch (format) + { + case EFormat::InstancedModel: { return 5; } + case EFormat::BatchedModel: { return 3; } + default: { return 0; } + } + } + size_t instance_size_bytes() const { return instance_size_floats() * sizeof(float); } + + void reset(); + }; + + // ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type + struct IBuffer + { + // id of the associated vertex buffer + unsigned int vbo{ 0 }; + // ibo id + unsigned int ibo{ 0 }; + // count of indices, updated after data are sent to gpu + size_t count{ 0 }; + + void reset(); + }; + + // Used to identify different toolpath sub-types inside a IBuffer + struct Path + { + struct Endpoint + { + // index of the buffer in the multibuffer vector + // the buffer type may change: + // it is the vertex buffer while extracting vertices data, + // the index buffer while extracting indices data + unsigned int b_id{ 0 }; + // index into the buffer + size_t i_id{ 0 }; + // move id + size_t s_id{ 0 }; + Vec3f position{ Vec3f::Zero() }; + }; + + struct Sub_Path + { + Endpoint first; + Endpoint last; + + bool contains(size_t s_id) const { + return first.s_id <= s_id && s_id <= last.s_id; + } + }; + + EMoveType type{ EMoveType::Noop }; + ExtrusionRole role{ erNone }; + float delta_extruder{ 0.0f }; + float height{ 0.0f }; + float width{ 0.0f }; + float feedrate{ 0.0f }; + float fan_speed{ 0.0f }; + float temperature{ 0.0f }; + float volumetric_rate{ 0.0f }; + unsigned char extruder_id{ 0 }; + unsigned char cp_color_id{ 0 }; + std::vector sub_paths; + +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + bool matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const; +#else + bool matches(const GCodeProcessorResult::MoveVertex& move) const; +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + size_t vertices_count() const { + return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1; + } + bool contains(size_t s_id) const { + return sub_paths.empty() ? false : sub_paths.front().first.s_id <= s_id && s_id <= sub_paths.back().last.s_id; + } + int get_id_of_sub_path_containing(size_t s_id) const { + if (sub_paths.empty()) + return -1; + else { + for (int i = 0; i < static_cast(sub_paths.size()); ++i) { + if (sub_paths[i].contains(s_id)) + return i; + } + return -1; + } + } + void add_sub_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) { + Endpoint endpoint = { b_id, i_id, s_id, move.position }; + sub_paths.push_back({ endpoint , endpoint }); + } + }; + + // Used to batch the indices needed to render the paths + struct RenderPath + { + // Index of the parent tbuffer + unsigned char tbuffer_id; + // Render path property + ColorRGBA color; + // Index of the buffer in TBuffer::indices + unsigned int ibuffer_id; + // Render path content + // Index of the path in TBuffer::paths + unsigned int path_id; + std::vector sizes; + std::vector offsets; // use size_t because we need an unsigned integer whose size matches pointer's size (used in the call glMultiDrawElements()) + bool contains(size_t offset) const { + for (size_t i = 0; i < offsets.size(); ++i) { + if (offsets[i] <= offset && offset <= offsets[i] + static_cast(sizes[i] * sizeof(IBufferType))) + return true; + } + return false; + } + }; + struct RenderPathPropertyLower { + bool operator() (const RenderPath &l, const RenderPath &r) const { + if (l.tbuffer_id < r.tbuffer_id) + return true; + if (l.color < r.color) + return true; + else if (l.color > r.color) + return false; + return l.ibuffer_id < r.ibuffer_id; + } + }; + struct RenderPathPropertyEqual { + bool operator() (const RenderPath &l, const RenderPath &r) const { + return l.tbuffer_id == r.tbuffer_id && l.ibuffer_id == r.ibuffer_id && l.color == r.color; + } + }; + + // buffer containing data for rendering a specific toolpath type + struct TBuffer + { + enum class ERenderPrimitiveType : unsigned char + { + Line, + Triangle, + InstancedModel, + BatchedModel + }; + + ERenderPrimitiveType render_primitive_type; + + // buffers for point, line and triangle primitive types + VBuffer vertices; + std::vector indices; + + struct Model + { + GLModel model; + ColorRGBA color; + InstanceVBuffer instances; + GLModel::Geometry data; + + void reset(); + }; + + // contain the buffer for model primitive types + Model model; + + std::string shader; + std::vector paths; + std::vector render_paths; + bool visible{ false }; + + void reset(); + + // b_id index of buffer contained in this->indices + // i_id index of first index contained in this->indices[b_id] + // s_id index of first vertex contained in this->vertices + void add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id); + + unsigned int max_vertices_per_segment() const { + switch (render_primitive_type) + { + case ERenderPrimitiveType::Line: { return 2; } + case ERenderPrimitiveType::Triangle: { return 8; } + default: { return 0; } + } + } + + size_t max_vertices_per_segment_size_floats() const { return vertices.vertex_size_floats() * static_cast(max_vertices_per_segment()); } + size_t max_vertices_per_segment_size_bytes() const { return max_vertices_per_segment_size_floats() * sizeof(float); } + unsigned int indices_per_segment() const { + switch (render_primitive_type) + { + case ERenderPrimitiveType::Line: { return 2; } + case ERenderPrimitiveType::Triangle: { return 30; } // 3 indices x 10 triangles + default: { return 0; } + } + } + size_t indices_per_segment_size_bytes() const { return static_cast(indices_per_segment() * sizeof(IBufferType)); } + unsigned int max_indices_per_segment() const { + switch (render_primitive_type) + { + case ERenderPrimitiveType::Line: { return 2; } + case ERenderPrimitiveType::Triangle: { return 36; } // 3 indices x 12 triangles + default: { return 0; } + } + } + size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); } + + bool has_data() const { + switch (render_primitive_type) + { + case ERenderPrimitiveType::Line: + case ERenderPrimitiveType::Triangle: { + return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; + } + case ERenderPrimitiveType::InstancedModel: { return model.model.is_initialized() && !model.instances.buffer.empty(); } + case ERenderPrimitiveType::BatchedModel: { +#if ENABLE_LEGACY_OPENGL_REMOVAL + return !model.data.vertices.empty() && !model.data.indices.empty() && +#else + return model.data.vertices_count() > 0 && model.data.indices_count() && +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; + } + default: { return false; } + } + } + }; + + // helper to render shells + struct Shells + { + GLVolumeCollection volumes; + bool visible{ false }; + }; + +#if ENABLE_SHOW_TOOLPATHS_COG + // helper to render center of gravity + class COG + { + GLModel m_model; + bool m_visible{ false }; + // whether or not to render the model with fixed screen size + bool m_fixed_size{ true }; + double m_total_mass{ 0.0 }; + Vec3d m_position{ Vec3d::Zero() }; + + public: + void render(); + + void reset() { + m_position = Vec3d::Zero(); + m_total_mass = 0.0; + } + + bool is_visible() const { return m_visible; } + void set_visible(bool visible) { m_visible = visible; } + + void add_segment(const Vec3d& v1, const Vec3d& v2, double mass) { + assert(mass > 0.0); + m_position += mass * 0.5 * (v1 + v2); + m_total_mass += mass; + } + + Vec3d cog() const { return (m_total_mass > 0.0) ? (Vec3d)(m_position / m_total_mass) : Vec3d::Zero(); } + + private: + void init() { + if (m_model.is_initialized()) + return; + + const float radius = m_fixed_size ? 10.0f : 1.0f; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_model.init_from(smooth_sphere(32, radius)); +#else + m_model.init_from(its_make_sphere(radius, PI / 32.0)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + }; +#endif // ENABLE_SHOW_TOOLPATHS_COG + + // helper to render extrusion paths + struct Extrusions + { + struct Range + { +#if ENABLE_PREVIEW_LAYER_TIME + enum class EType : unsigned char + { + Linear, + Logarithmic + }; +#endif // ENABLE_PREVIEW_LAYER_TIME + + float min; + float max; + unsigned int count; + + Range() { reset(); } + + void update_from(const float value) { + if (value != max && value != min) + ++count; + min = std::min(min, value); + max = std::max(max, value); + } + void reset() { min = FLT_MAX; max = -FLT_MAX; count = 0; } + +#if ENABLE_PREVIEW_LAYER_TIME + float step_size(EType type = EType::Linear) const; + ColorRGBA get_color_at(float value, EType type = EType::Linear) const; +#else + float step_size() const { return (max - min) / (static_cast(Range_Colors.size()) - 1.0f); } + ColorRGBA get_color_at(float value) const; +#endif // ENABLE_PREVIEW_LAYER_TIME + }; + + struct Ranges + { + // Color mapping by layer height. + Range height; + // Color mapping by extrusion width. + Range width; + // Color mapping by feedrate. + Range feedrate; + // Color mapping by fan speed. + Range fan_speed; + // Color mapping by volumetric extrusion rate. + Range volumetric_rate; + // Color mapping by extrusion temperature. + Range temperature; +#if ENABLE_PREVIEW_LAYER_TIME + // Color mapping by layer time. + std::array(PrintEstimatedStatistics::ETimeMode::Count)> layer_time; +#endif // ENABLE_PREVIEW_LAYER_TIME + + void reset() { + height.reset(); + width.reset(); + feedrate.reset(); + fan_speed.reset(); + volumetric_rate.reset(); + temperature.reset(); +#if ENABLE_PREVIEW_LAYER_TIME + for (auto& range : layer_time) { + range.reset(); + } +#endif // ENABLE_PREVIEW_LAYER_TIME + } + }; + + unsigned int role_visibility_flags{ 0 }; + Ranges ranges; + + void reset_role_visibility_flags() { + role_visibility_flags = 0; + for (unsigned int i = 0; i < erCount; ++i) { + role_visibility_flags |= 1 << i; + } + } + + void reset_ranges() { ranges.reset(); } + }; + + class Layers + { + public: + struct Range + { + size_t first{ 0 }; + size_t last{ 0 }; + + bool operator == (const Range& other) const { return first == other.first && last == other.last; } + bool operator != (const Range& other) const { return !operator==(other); } + bool contains(size_t id) const { return first <= id && id <= last; } + }; + + private: + std::vector m_zs; + std::vector m_ranges; + + public: + void append(double z, const Range& range) { + m_zs.emplace_back(z); + m_ranges.emplace_back(range); + } + + void reset() { + m_zs = std::vector(); + m_ranges = std::vector(); + } + + size_t size() const { return m_zs.size(); } + bool empty() const { return m_zs.empty(); } + const std::vector& get_zs() const { return m_zs; } + const std::vector& get_ranges() const { return m_ranges; } + std::vector& get_ranges() { return m_ranges; } + double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; } + Range get_range_at(unsigned int id) const { return (id < m_ranges.size()) ? m_ranges[id] : Range(); } + + bool operator != (const Layers& other) const { + if (m_zs != other.m_zs) + return true; + if (m_ranges != other.m_ranges) + return true; + return false; + } + }; + + // used to render the toolpath caps of the current sequential range + // (i.e. when sliding on the horizontal slider) + struct SequentialRangeCap + { + TBuffer* buffer{ nullptr }; + unsigned int ibo{ 0 }; + unsigned int vbo{ 0 }; + ColorRGBA color; + + ~SequentialRangeCap(); + bool is_renderable() const { return buffer != nullptr; } + void reset(); + size_t indices_count() const { return 6; } + }; + +#if ENABLE_GCODE_VIEWER_STATISTICS + struct Statistics + { + // time + int64_t results_time{ 0 }; + int64_t load_time{ 0 }; + int64_t load_vertices{ 0 }; + int64_t smooth_vertices{ 0 }; + int64_t load_indices{ 0 }; + int64_t refresh_time{ 0 }; + int64_t refresh_paths_time{ 0 }; + // opengl calls + int64_t gl_multi_lines_calls_count{ 0 }; + int64_t gl_multi_triangles_calls_count{ 0 }; + int64_t gl_triangles_calls_count{ 0 }; + int64_t gl_instanced_models_calls_count{ 0 }; + int64_t gl_batched_models_calls_count{ 0 }; + // memory + int64_t results_size{ 0 }; + int64_t total_vertices_gpu_size{ 0 }; + int64_t total_indices_gpu_size{ 0 }; + int64_t total_instances_gpu_size{ 0 }; + int64_t max_vbuffer_gpu_size{ 0 }; + int64_t max_ibuffer_gpu_size{ 0 }; + int64_t paths_size{ 0 }; + int64_t render_paths_size{ 0 }; + int64_t models_instances_size{ 0 }; + // other + int64_t travel_segments_count{ 0 }; + int64_t wipe_segments_count{ 0 }; + int64_t extrude_segments_count{ 0 }; + int64_t instances_count{ 0 }; + int64_t batched_count{ 0 }; + int64_t vbuffers_count{ 0 }; + int64_t ibuffers_count{ 0 }; + + void reset_all() { + reset_times(); + reset_opengl(); + reset_sizes(); + reset_others(); + } + + void reset_times() { + results_time = 0; + load_time = 0; + load_vertices = 0; + smooth_vertices = 0; + load_indices = 0; + refresh_time = 0; + refresh_paths_time = 0; + } + + void reset_opengl() { + gl_multi_lines_calls_count = 0; + gl_multi_triangles_calls_count = 0; + gl_triangles_calls_count = 0; + gl_instanced_models_calls_count = 0; + gl_batched_models_calls_count = 0; + } + + void reset_sizes() { + results_size = 0; + total_vertices_gpu_size = 0; + total_indices_gpu_size = 0; + total_instances_gpu_size = 0; + max_vbuffer_gpu_size = 0; + max_ibuffer_gpu_size = 0; + paths_size = 0; + render_paths_size = 0; + models_instances_size = 0; + } + + void reset_others() { + travel_segments_count = 0; + wipe_segments_count = 0; + extrude_segments_count = 0; + instances_count = 0; + batched_count = 0; + vbuffers_count = 0; + ibuffers_count = 0; + } + }; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + +public: + struct SequentialView + { + class Marker + { + GLModel m_model; + Vec3f m_world_position; + Transform3f m_world_transform; + // for seams, the position of the marker is on the last endpoint of the toolpath containing it + // the offset is used to show the correct value of tool position in the "ToolPosition" window + // see implementation of render() method + Vec3f m_world_offset; + float m_z_offset{ 0.5f }; + bool m_visible{ true }; + + public: + void init(); + + const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); } + + void set_world_position(const Vec3f& position); + void set_world_offset(const Vec3f& offset) { m_world_offset = offset; } + + bool is_visible() const { return m_visible; } + void set_visible(bool visible) { m_visible = visible; } + + void render(); + }; + + class GCodeWindow + { + struct Line + { + std::string command; + std::string parameters; + std::string comment; + }; + bool m_visible{ true }; + uint64_t m_selected_line_id{ 0 }; + size_t m_last_lines_size{ 0 }; + std::string m_filename; + boost::iostreams::mapped_file_source m_file; + // map for accessing data in file by line number + std::vector m_lines_ends; + // current visible lines + std::vector m_lines; + + public: + GCodeWindow() = default; + ~GCodeWindow() { stop_mapping_file(); } + void load_gcode(const std::string& filename, const std::vector& lines_ends); + void reset() { + stop_mapping_file(); + m_lines_ends.clear(); + m_lines.clear(); + m_filename.clear(); + } + + void toggle_visibility() { m_visible = !m_visible; } + + void render(float top, float bottom, uint64_t curr_line_id) const; + + void stop_mapping_file(); + }; + + struct Endpoints + { + size_t first{ 0 }; + size_t last{ 0 }; + }; + + bool skip_invisible_moves{ false }; + Endpoints endpoints; + Endpoints current; + Endpoints last_current; + Endpoints global; + Vec3f current_position{ Vec3f::Zero() }; + Vec3f current_offset{ Vec3f::Zero() }; + Marker marker; + GCodeWindow gcode_window; + std::vector gcode_ids; + + void render(float legend_height); + }; + + enum class EViewType : unsigned char + { + FeatureType, + Height, + Width, + Feedrate, + FanSpeed, + Temperature, + VolumetricRate, +#if ENABLE_PREVIEW_LAYER_TIME + LayerTimeLinear, + LayerTimeLogarithmic, +#endif // ENABLE_PREVIEW_LAYER_TIME + Tool, + ColorPrint, + Count + }; + +private: + bool m_gl_data_initialized{ false }; + unsigned int m_last_result_id{ 0 }; +#if ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + EViewType m_last_view_type{ EViewType::Count }; +#endif // ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC + size_t m_moves_count{ 0 }; + std::vector m_buffers{ static_cast(EMoveType::Extrude) }; + // bounding box of toolpaths + BoundingBoxf3 m_paths_bounding_box; + // bounding box of toolpaths + marker tools + BoundingBoxf3 m_max_bounding_box; + float m_max_print_height{ 0.0f }; + std::vector m_tool_colors; + Layers m_layers; + std::array m_layers_z_range; + std::vector m_roles; + size_t m_extruders_count; + std::vector m_extruder_ids; + std::vector m_filament_diameters; + std::vector m_filament_densities; + Extrusions m_extrusions; + SequentialView m_sequential_view; + Shells m_shells; +#if ENABLE_SHOW_TOOLPATHS_COG + COG m_cog; +#endif // ENABLE_SHOW_TOOLPATHS_COG + EViewType m_view_type{ EViewType::FeatureType }; + bool m_legend_enabled{ true }; +#if ENABLE_PREVIEW_LAYOUT + struct LegendResizer + { + bool dirty{ true }; + void reset() { dirty = true; } + }; + LegendResizer m_legend_resizer; +#endif // ENABLE_PREVIEW_LAYOUT + PrintEstimatedStatistics m_print_statistics; + PrintEstimatedStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedStatistics::ETimeMode::Normal }; +#if ENABLE_GCODE_VIEWER_STATISTICS + Statistics m_statistics; +#endif // ENABLE_GCODE_VIEWER_STATISTICS + std::array m_detected_point_sizes = { 0.0f, 0.0f }; + GCodeProcessorResult::SettingsIds m_settings_ids; + std::array m_sequential_range_caps; +#if ENABLE_PREVIEW_LAYER_TIME + std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> m_layers_times; +#endif // ENABLE_PREVIEW_LAYER_TIME + + std::vector m_custom_gcode_per_print_z; + + bool m_contained_in_bed{ true }; + +public: + GCodeViewer(); + ~GCodeViewer() { reset(); } + + void init(); + + // extract rendering data from the given parameters +#if ENABLE_LEGACY_OPENGL_REMOVAL + void load(const GCodeProcessorResult& gcode_result, const Print& print); +#else + void load(const GCodeProcessorResult& gcode_result, const Print& print, bool initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + // recalculate ranges in dependence of what is visible and sets tool/print colors + void refresh(const GCodeProcessorResult& gcode_result, const std::vector& str_tool_colors); +#if ENABLE_PREVIEW_LAYOUT + void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; +#else + void refresh_render_paths(); +#endif // ENABLE_PREVIEW_LAYOUT + void update_shells_color_by_extruder(const DynamicPrintConfig* config); + + void reset(); + void render(); +#if ENABLE_SHOW_TOOLPATHS_COG + void render_cog() { m_cog.render(); } +#endif // ENABLE_SHOW_TOOLPATHS_COG + + bool has_data() const { return !m_roles.empty(); } + bool can_export_toolpaths() const; + + const BoundingBoxf3& get_paths_bounding_box() const { return m_paths_bounding_box; } + const BoundingBoxf3& get_max_bounding_box() const { return m_max_bounding_box; } + const std::vector& get_layers_zs() const { return m_layers.get_zs(); } + + const SequentialView& get_sequential_view() const { return m_sequential_view; } + void update_sequential_view_current(unsigned int first, unsigned int last); + + bool is_contained_in_bed() const { return m_contained_in_bed; } + + EViewType get_view_type() const { return m_view_type; } + void set_view_type(EViewType type) { + if (type == EViewType::Count) + type = EViewType::FeatureType; + + m_view_type = type; + } + + bool is_toolpath_move_type_visible(EMoveType type) const; + void set_toolpath_move_type_visible(EMoveType type, bool visible); + unsigned int get_toolpath_role_visibility_flags() const { return m_extrusions.role_visibility_flags; } + void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; } + unsigned int get_options_visibility_flags() const; + void set_options_visibility_from_flags(unsigned int flags); + void set_layers_z_range(const std::array& layers_z_range); + + bool is_legend_enabled() const { return m_legend_enabled; } + void enable_legend(bool enable) { m_legend_enabled = enable; } + + void export_toolpaths_to_obj(const char* filename) const; + + void toggle_gcode_window_visibility() { m_sequential_view.gcode_window.toggle_visibility(); } + + std::vector& get_custom_gcode_per_print_z() { return m_custom_gcode_per_print_z; } + size_t get_extruders_count() { return m_extruders_count; } + +#if ENABLE_PREVIEW_LAYOUT + void invalidate_legend() { m_legend_resizer.reset(); } +#endif // ENABLE_PREVIEW_LAYOUT + +private: + void load_toolpaths(const GCodeProcessorResult& gcode_result); +#if ENABLE_LEGACY_OPENGL_REMOVAL + void load_shells(const Print& print); +#else + void load_shells(const Print& print, bool initialized); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if !ENABLE_PREVIEW_LAYOUT + void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; +#endif // !ENABLE_PREVIEW_LAYOUT + void render_toolpaths(); + void render_shells(); + void render_legend(float& legend_height); +#if ENABLE_GCODE_VIEWER_STATISTICS + void render_statistics(); +#endif // ENABLE_GCODE_VIEWER_STATISTICS + bool is_visible(ExtrusionRole role) const { + return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0; + } + bool is_visible(const Path& path) const { return is_visible(path.role); } + void log_memory_used(const std::string& label, int64_t additional = 0) const; + ColorRGBA option_color(EMoveType move_type) const; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GCodeViewer_hpp_ + From 1554d6a2c7bc03cb47c4147286eb9c64439716b7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 16 May 2022 14:58:17 +0200 Subject: [PATCH 013/169] Try to fix linking of expat --- CMakeLists.txt | 8 ++++++-- src/CMakeLists.txt | 2 +- src/libslic3r/CMakeLists.txt | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d5f96acd9..002cd3456 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -463,8 +463,12 @@ include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR}) find_package(EXPAT REQUIRED) -if (NOT TARGET EXPAT::EXPAT AND TARGET expat::expat) - add_library(EXPAT::EXPAT ALIAS expat::expat) +add_library(libexpat INTERFACE) + +if (TARGET EXPAT::EXPAT ) + target_link_libraries(libexpat INTERFACE EXPAT::EXPAT) +elseif(TARGET expat::expat) + target_link_libraries(libexpat INTERFACE expat::expat) endif () find_package(PNG REQUIRED) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f8430e968..801760b8c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -92,7 +92,7 @@ if (SLIC3R_GUI) string(REGEX MATCH "wxexpat" WX_EXPAT_BUILTIN ${wxWidgets_LIBRARIES}) if (EXPAT_FOUND AND NOT WX_EXPAT_BUILTIN) list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX expat) - list(APPEND wxWidgets_LIBRARIES EXPAT::EXPAT) + list(APPEND wxWidgets_LIBRARIES libexpat) endif () # This is an issue in the new wxWidgets cmake build, doesn't deal with librt diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index c9d8aa4fa..396eb0764 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -372,7 +372,7 @@ target_link_libraries(libslic3r boost_libs clipper nowide - EXPAT::EXPAT + libexpat glu-libtess qhull semver From f5ec76c2300095f23af6be9d37488b7f434df25d Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 16 May 2022 17:27:02 +0200 Subject: [PATCH 014/169] Follow-up to dc3931ec1f3955ce546fa51661aab56c96f38a5e: Fix mutable priority queue being wiped when moving out of function Without move constructor, the clean() gets called when returning an instance from a function. The above fix was applied also to MutableSkipHeapPriorityQueue. Follow-up to 15a082b80b1d9ed3cfa70d3d8133c4cd1e5d699d: Fixed TEST_CASE("Mutable priority queue - first pop", "[MutableSkipHeapPriorityQueue]") --- src/libslic3r/MutablePriorityQueue.hpp | 12 ++- .../libslic3r/test_mutable_priority_queue.cpp | 76 ++++++++++++------- 2 files changed, 59 insertions(+), 29 deletions(-) diff --git a/src/libslic3r/MutablePriorityQueue.hpp b/src/libslic3r/MutablePriorityQueue.hpp index 81f3cb782..b45b8cfff 100644 --- a/src/libslic3r/MutablePriorityQueue.hpp +++ b/src/libslic3r/MutablePriorityQueue.hpp @@ -24,7 +24,7 @@ public: MutablePriorityQueue& operator=(MutablePriorityQueue &&) = default; // This class modifies the outside data through the m_index_setter - // and thus it should not be copied. The semantics are similar to std::unique_ptr + // and thus it should not be copied. The semantics is similar to std::unique_ptr MutablePriorityQueue(const MutablePriorityQueue &) = delete; MutablePriorityQueue& operator=(const MutablePriorityQueue &) = delete; @@ -267,6 +267,14 @@ public: {} ~MutableSkipHeapPriorityQueue() { clear(); } + MutableSkipHeapPriorityQueue(MutableSkipHeapPriorityQueue &&) = default; + MutableSkipHeapPriorityQueue &operator=(MutableSkipHeapPriorityQueue &&) = default; + + // This class modifies the outside data through the m_index_setter + // and thus it should not be copied. The semantics is similar to std::unique_ptr + MutableSkipHeapPriorityQueue(const MutableSkipHeapPriorityQueue &) = delete; + MutableSkipHeapPriorityQueue &operator=(const MutableSkipHeapPriorityQueue &) = delete; + void clear(); // Reserve one unused element per miniheap. void reserve(size_t cnt) { m_heap.reserve(cnt + ((cnt + (address::block_size - 1)) / (address::block_size - 1))); } @@ -278,6 +286,8 @@ public: void update(size_t idx) { assert(! address::is_padding(idx)); T item = m_heap[idx]; remove(idx); push(item); } // There is one padding element storead at each miniheap, thus lower the number of elements by the number of miniheaps. size_t size() const noexcept { return m_heap.size() - (m_heap.size() + address::block_size - 1) / address::block_size; } + // Number of heap elements including padding. heap_size() >= size(). + size_t heap_size() const noexcept { return m_heap.size(); } bool empty() const { return m_heap.empty(); } T& operator[](std::size_t idx) noexcept { assert(! address::is_padding(idx)); return m_heap[idx]; } const T& operator[](std::size_t idx) const noexcept { assert(! address::is_padding(idx)); return m_heap[idx]; } diff --git a/tests/libslic3r/test_mutable_priority_queue.cpp b/tests/libslic3r/test_mutable_priority_queue.cpp index 5ea3db276..626b0388d 100644 --- a/tests/libslic3r/test_mutable_priority_queue.cpp +++ b/tests/libslic3r/test_mutable_priority_queue.cpp @@ -347,21 +347,35 @@ TEST_CASE("Mutable priority queue - first pop", "[MutableSkipHeapPriorityQueue]" float val; }; static constexpr const size_t count = 50000; + std::vector idxs(count, {0}); auto q = make_miniheap_mutable_priority_queue( - [](MyValue &v, size_t idx) { v.id = idx; }, + [&idxs](MyValue &v, size_t idx) { idxs[v.id] = idx; }, [](MyValue &l, MyValue &r) { return l.val < r.val; }); using QueueType = decltype(q); + THEN("Skip queue has 0th element unused, 1st element is the top of the queue.") { + CHECK(QueueType::address::is_padding(0)); + CHECK(!QueueType::address::is_padding(1)); + } q.reserve(count); for (size_t id = 0; id < count; ++ id) q.push({ id, rand() / 100.f }); MyValue v = q.top(); // copy - // is valid id (no initial value default value) - CHECK(QueueType::address::is_padding(0)); - CHECK(!QueueType::address::is_padding(1)); + THEN("Element at the top of the queue has a valid ID.") { + CHECK(v.id >= 0); + CHECK(v.id < count); + } + THEN("Element at the top of the queue has its position stored in idxs.") { + CHECK(idxs[v.id] == 1); + } q.pop(); - CHECK(v.id == 1); - // is first item in queue valid value - CHECK(q.top().id == 1); + THEN("Element removed from the queue has its position in idxs reset to invalid.") { + CHECK(idxs[v.id] == q.invalid_id()); + } + THEN("Element was removed from the queue, new top of the queue has its index set correctly.") { + CHECK(q.top().id >= 0); + CHECK(q.top().id < count); + CHECK(idxs[q.top().id] == 1); + } } TEST_CASE("Mutable priority queue complex", "[MutableSkipHeapPriorityQueue]") @@ -379,23 +393,23 @@ TEST_CASE("Mutable priority queue complex", "[MutableSkipHeapPriorityQueue]") q.reserve(count); auto rand_val = [&]()->float { return (rand() % 53) / 10.f; }; - size_t ord = 0; - for (size_t id = 0; id < count; id++) { - MyValue mv; - mv.id = ord++; - mv.val = rand_val(); - q.push(mv); - } + for (size_t id = 0; id < count; ++ id) + q.push({ id, rand_val() }); + auto check = [&]()->bool{ for (size_t i = 0; i < idxs.size(); ++i) { - if (dels[i]) continue; - size_t qid = idxs[i]; - if (qid > 3*count) { - return false; - } - MyValue &mv = q[qid]; - if (mv.id != i) { - return false; // ERROR + if (dels[i]) { + if (idxs[i] != q.invalid_id()) + return false; // ERROR + } else { + size_t qid = idxs[i]; + if (qid >= q.heap_size()) { + return false; // ERROR + } + MyValue &mv = q[qid]; + if (mv.id != i) { + return false; // ERROR + } } } return true; @@ -403,6 +417,7 @@ TEST_CASE("Mutable priority queue complex", "[MutableSkipHeapPriorityQueue]") CHECK(check()); // initial check + // Generate an element ID of an elmenet, which was not yet deleted, thus it is still valid. auto get_valid_id = [&]()->int { int id = 0; do { @@ -410,23 +425,28 @@ TEST_CASE("Mutable priority queue complex", "[MutableSkipHeapPriorityQueue]") } while (dels[id]); return id; }; + + // Remove first 100 elements from the queue of 5000 elements, cross-validate indices. + // Re-enter every 20th element back to the queue. for (size_t i = 0; i < 100; i++) { - MyValue it = q.top(); // copy + MyValue v = q.top(); // copy q.pop(); - dels[it.id] = true; + dels[v.id] = true; CHECK(check()); if (i % 20 == 0) { - it.val = rand_val(); - q.push(it); - dels[it.id] = false; + v.val = rand_val(); + q.push(v); + dels[v.id] = false; CHECK(check()); continue; } - + // Remove some valid element from the queue. int id = get_valid_id(); + CHECK(idxs[id] != q.invalid_id()); q.remove(idxs[id]); dels[id] = true; CHECK(check()); + // and change 5 random elements and reorder them in the queue. for (size_t j = 0; j < 5; j++) { int id = get_valid_id(); size_t qid = idxs[id]; From 8c6f67a1645c596189d6601c5ed758e70adfd172 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 17 May 2022 10:14:44 +0200 Subject: [PATCH 015/169] Fix Boost build not forwarding osx sdk flags to the compiler --- deps/Boost/Boost.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deps/Boost/Boost.cmake b/deps/Boost/Boost.cmake index fd2f202e9..d819e28cf 100644 --- a/deps/Boost/Boost.cmake +++ b/deps/Boost/Boost.cmake @@ -75,7 +75,9 @@ file(TO_NATIVE_PATH ${DESTDIR}/usr/local/ _prefix) set(_boost_flags "") if (UNIX) set(_boost_flags "cflags=-fPIC;cxxflags=-fPIC") -elseif(APPLE) +endif () + +if(APPLE) set(_boost_flags "cflags=-fPIC -mmacosx-version-min=${DEP_OSX_TARGET};" "cxxflags=-fPIC -mmacosx-version-min=${DEP_OSX_TARGET};" From a552a55cce5516ace081f86d9d5def9da25876a8 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 17 May 2022 11:56:51 +0200 Subject: [PATCH 016/169] Follow-up to f5ec76c2300095f23af6be9d37488b7f434df25d Compile-time instantiation of the MutablePriorityQueue with run-time resetting of indices when removing items from the queue active in debug mode only. --- src/libslic3r/ShortestPath.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index 449ff45b5..72bfe1f30 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -195,7 +195,15 @@ std::vector> chain_segments_greedy_constrained_reversals } // Initialize a heap of end points sorted by the lowest distance to the next valid point of a path. - auto queue = make_mutable_priority_queue( + auto queue = make_mutable_priority_queue( [](EndPoint *ep, size_t idx){ ep->heap_idx = idx; }, [](EndPoint *l, EndPoint *r){ return l->distance_out < r->distance_out; }); queue.reserve(end_points.size() * 2 - 1); @@ -213,7 +221,7 @@ std::vector> chain_segments_greedy_constrained_reversals assert(ep.chain_id == 0); } else { // End point is NOT on the heap, therefore it is part of the output path. - assert(ep.heap_idx == std::numeric_limits::max()); + assert(ep.heap_idx == queue.invalid_id()); assert(ep.chain_id != 0); if (&ep == first_point) { assert(ep.edge_out == nullptr); @@ -222,7 +230,7 @@ std::vector> chain_segments_greedy_constrained_reversals // Detect loops. for (EndPoint *pt = &ep; pt != nullptr;) { // Out of queue. It is a final point. - assert(pt->heap_idx == std::numeric_limits::max()); + assert(pt->heap_idx == queue.invalid_id()); EndPoint *pt_other = &end_points[(pt - &end_points.front()) ^ 1]; if (pt_other->heap_idx < queue.size()) // The other side of this segment is undecided yet. From b27264a8c9c9cd61529ff0c319f9476808019aa9 Mon Sep 17 00:00:00 2001 From: Slicer Date: Tue, 17 May 2022 11:58:43 +0200 Subject: [PATCH 017/169] Changed http::ca_file_supported function to return false for OSX. --- src/slic3r/Utils/Http.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 68ddda041..a6ad54b32 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -181,7 +181,7 @@ Http::priv::~priv() bool Http::priv::ca_file_supported(::CURL *curl) { -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) bool res = false; #else bool res = true; @@ -194,6 +194,7 @@ bool Http::priv::ca_file_supported(::CURL *curl) if (::curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &tls) == CURLE_OK) { if (tls->backend == CURLSSLBACKEND_SCHANNEL || tls->backend == CURLSSLBACKEND_DARWINSSL) { // With Windows and OS X native SSL support, cert files cannot be set + // DK: OSX is now not building CURL and links system one, thus we do not know which backend is installed. Still, false will be returned since the ifdef at the begining if this function. res = false; } } From 51211e265a0bfb6b7810e4152fd671a37b27fdb0 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 17 May 2022 12:13:59 +0200 Subject: [PATCH 018/169] SendSystemInfoDialog: fixed check of internet connection on Windows: S_FALSE is returned when COM interface is already initialized, it should be considered a success. --- src/slic3r/GUI/SendSystemInfoDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/SendSystemInfoDialog.cpp b/src/slic3r/GUI/SendSystemInfoDialog.cpp index 9a97532a7..03a38acbf 100644 --- a/src/slic3r/GUI/SendSystemInfoDialog.cpp +++ b/src/slic3r/GUI/SendSystemInfoDialog.cpp @@ -141,7 +141,7 @@ static bool check_internet_connection_win() { bool internet = true; // return true if COM object creation fails. - if (CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) == S_OK) { + if (SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED))) { { CComPtr pNLM; if (pNLM.CoCreateInstance(CLSID_NetworkListManager) == S_OK) { From 6df1b6d07406711908ef605c674b5bf28b577ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 17 May 2022 12:41:28 +0200 Subject: [PATCH 019/169] Fixed an issue that some trees in the Lightning infill weren't connected to perimeters. --- src/libslic3r/Fill/Lightning/Layer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index c381d5505..f3193afe4 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -129,9 +129,10 @@ GroundingLocation Layer::getBestGroundingLocation if (contour.size() > 2) { Point prev = contour.points.back(); for (const Point &p2 : contour.points) { - if (double d = Line::distance_to_squared(unsupported_location, prev, p2); d < d2) { + Point closest_point; + if (double d = line_alg::distance_to_squared(Line{prev, p2}, unsupported_location, &closest_point); d < d2) { d2 = d; - node_location = Geometry::foot_pt({ prev, p2 }, unsupported_location).cast(); + node_location = closest_point; } prev = p2; } From e6750a524f6d0c6832c167b4bf4821dc2f7ecba6 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 17 May 2022 13:14:13 +0200 Subject: [PATCH 020/169] Fix collision after arrange when 'complete Individual objects' is ON fixes #8335 --- src/libslic3r/Print.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e88f9093c..49fbec6b1 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -13,6 +13,7 @@ #include "GCode.hpp" #include "GCode/WipeTower.hpp" #include "Utils.hpp" +#include "BuildVolume.hpp" #include @@ -387,7 +388,7 @@ bool Print::sequential_print_horizontal_clearance_valid(const Print& print, Poly Geometry::assemble_transform({ 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror())), // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. - float(scale_(0.5 * print.config().extruder_clearance_radius.value - EPSILON)), + float(scale_(0.5 * print.config().extruder_clearance_radius.value - BuildVolume::BedEpsilon)), jtRound, scale_(0.1)).front()); } // Make a copy, so it may be rotated for instances. From 39cefdad89f34741b4c6b17a4e42823a57a76c9c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 17 May 2022 13:19:33 +0200 Subject: [PATCH 021/169] Tech ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - Disable association to 3mf and stl files if the application is run on Windows 8 or later --- src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/ConfigWizard.cpp | 6168 ++++++++++++------------ src/slic3r/GUI/GUI_App.cpp | 6687 +++++++++++++------------- src/slic3r/GUI/Preferences.cpp | 1927 ++++---- src/slic3r/Utils/FixModelByWin10.cpp | 898 ++-- 5 files changed, 7865 insertions(+), 7817 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a89a7c8b0..934d1b978 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -81,6 +81,8 @@ #define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1) // Enable gizmo grabbers to share common models #define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1) +// Disable association to 3mf and stl files if the application is run on Windows 8 or later +#define ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER (1 && ENABLE_2_5_0_ALPHA1) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 86768dfc5..b9dcc1a4f 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1,3075 +1,3093 @@ -// FIXME: extract absolute units -> em - -#include "ConfigWizard_private.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _MSW_DARK_MODE -#include -#endif // _MSW_DARK_MODE - -#include "libslic3r/Platform.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/Config.hpp" -#include "libslic3r/libslic3r.h" -#include "libslic3r/Model.hpp" -#include "libslic3r/Color.hpp" -#include "GUI.hpp" -#include "GUI_App.hpp" -#include "GUI_Utils.hpp" -#include "GUI_ObjectManipulation.hpp" -#include "Field.hpp" -#include "DesktopIntegrationDialog.hpp" -#include "slic3r/Config/Snapshot.hpp" -#include "slic3r/Utils/PresetUpdater.hpp" -#include "format.hpp" -#include "MsgDialog.hpp" -#include "UnsavedChangesDialog.hpp" - -#if defined(__linux__) && defined(__WXGTK3__) -#define wxLinux_gtk3 true -#else -#define wxLinux_gtk3 false -#endif //defined(__linux__) && defined(__WXGTK3__) - -namespace Slic3r { -namespace GUI { - - -using Config::Snapshot; -using Config::SnapshotDB; - - -// Configuration data structures extensions needed for the wizard - -bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle) -{ - this->preset_bundle = std::make_unique(); - this->is_in_resources = ais_in_resources; - this->is_prusa_bundle = ais_prusa_bundle; - - std::string path_string = source_path.string(); - // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air. - auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle( - path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable); - UNUSED(config_substitutions); - // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed. - assert(config_substitutions.empty()); - auto first_vendor = preset_bundle->vendors.begin(); - if (first_vendor == preset_bundle->vendors.end()) { - BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string; - return false; - } - if (presets_loaded == 0) { - BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string; - return false; - } - - BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded; - this->vendor_profile = &first_vendor->second; - return true; -} - -Bundle::Bundle(Bundle &&other) - : preset_bundle(std::move(other.preset_bundle)) - , vendor_profile(other.vendor_profile) - , is_in_resources(other.is_in_resources) - , is_prusa_bundle(other.is_prusa_bundle) -{ - other.vendor_profile = nullptr; -} - -BundleMap BundleMap::load() -{ - BundleMap res; - - const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred(); - const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); - - auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); - auto prusa_bundle_rsrc = false; - if (! boost::filesystem::exists(prusa_bundle_path)) { - prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); - prusa_bundle_rsrc = true; - } - { - Bundle prusa_bundle; - if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true)) - res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle)); - } - - // Load the other bundles in the datadir/vendor directory - // and then additionally from resources/profiles. - bool is_in_resources = false; - for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) { - for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) { - if (Slic3r::is_ini_file(dir_entry)) { - std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part - - // Don't load this bundle if we've already loaded it. - if (res.find(id) != res.end()) { continue; } - - Bundle bundle; - if (bundle.load(dir_entry.path(), is_in_resources)) - res.emplace(std::move(id), std::move(bundle)); - } - } - - is_in_resources = true; - } - - return res; -} - -Bundle& BundleMap::prusa_bundle() -{ - auto it = find(PresetBundle::PRUSA_BUNDLE); - if (it == end()) { - throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); - } - - return it->second; -} - -const Bundle& BundleMap::prusa_bundle() const -{ - return const_cast(this)->prusa_bundle(); -} - - -// Printer model picker GUI control - -struct PrinterPickerEvent : public wxEvent -{ - std::string vendor_id; - std::string model_id; - std::string variant_name; - bool enable; - - PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) - : wxEvent(winid, eventType) - , vendor_id(std::move(vendor_id)) - , model_id(std::move(model_id)) - , variant_name(std::move(variant_name)) - , enable(enable) - {} - - virtual wxEvent *Clone() const - { - return new PrinterPickerEvent(*this); - } -}; - -wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent); - -const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png"; - -PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter) - : wxPanel(parent) - , vendor_id(vendor.id) - , width(0) -{ - wxGetApp().UpdateDarkUI(this); - const auto &models = vendor.models; - - auto *sizer = new wxBoxSizer(wxVERTICAL); - - const auto font_title = GetFont().MakeBold().Scaled(1.3f); - const auto font_name = GetFont().MakeBold(); - const auto font_alt_nozzle = GetFont().Scaled(0.9f); - - // wxGrid appends widgets by rows, but we need to construct them in columns. - // These vectors are used to hold the elements so that they can be appended in the right order. - std::vector titles; - std::vector bitmaps; - std::vector variants_panels; - - int max_row_width = 0; - int current_row_width = 0; - - bool is_variants = false; - - for (const auto &model : models) { - if (! filter(model)) { continue; } - - wxBitmap bitmap; - int bitmap_width = 0; - auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool { - if (wxFileExists(bitmap_file)) { - bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG); - bitmap_width = bitmap.GetWidth(); - return true; - } - return false; - }; - if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { - if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { - BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") - % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png") - % vendor.id - % model.id; - load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); - } - } - auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - title->SetFont(font_name); - const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width); - title->Wrap(wrap_width); - - current_row_width += wrap_width; - if (titles.size() % max_cols == max_cols - 1) { - max_row_width = std::max(max_row_width, current_row_width); - current_row_width = 0; - } - - titles.push_back(title); - - auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap); - bitmaps.push_back(bitmap_widget); - - auto *variants_panel = new wxPanel(this); - wxGetApp().UpdateDarkUI(variants_panel); - auto *variants_sizer = new wxBoxSizer(wxVERTICAL); - variants_panel->SetSizer(variants_sizer); - const auto model_id = model.id; - - for (size_t i = 0; i < model.variants.size(); i++) { - const auto &variant = model.variants[i]; - - const auto label = model.technology == ptFFF - ? from_u8((boost::format("%1% %2% %3%") % variant.name % _utf8(L("mm")) % _utf8(L("nozzle"))).str()) - : from_u8(model.name); - - if (i == 1) { - auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _L("Alternate nozzles:")); - alt_label->SetFont(font_alt_nozzle); - variants_sizer->Add(alt_label, 0, wxBOTTOM, 3); - is_variants = true; - } - - auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name); - i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox); - - const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name); - cbox->SetValue(enabled); - - variants_sizer->Add(cbox, 0, wxBOTTOM, 3); - - cbox->Bind(wxEVT_CHECKBOX, [this, cbox](wxCommandEvent &event) { - on_checkbox(cbox, event.IsChecked()); - }); - } - - variants_panels.push_back(variants_panel); - } - - width = std::max(max_row_width, current_row_width); - - const size_t cols = std::min(max_cols, titles.size()); - - auto *printer_grid = new wxFlexGridSizer(cols, 0, 20); - printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL); - - if (titles.size() > 0) { - const size_t odd_items = titles.size() % cols; - - for (size_t i = 0; i < titles.size() - odd_items; i += cols) { - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(bitmaps[j], 0, wxBOTTOM, 20); } - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(titles[j], 0, wxBOTTOM, 3); } - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(variants_panels[j]); } - - // Add separator space to multiliners - if (titles.size() > cols) { - for (size_t j = i; j < i + cols; j++) { printer_grid->Add(1, 30); } - } - } - if (odd_items > 0) { - const size_t rem = titles.size() - odd_items; - - for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(bitmaps[i], 0, wxBOTTOM, 20); } - for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } - for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(titles[i], 0, wxBOTTOM, 3); } - for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } - for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(variants_panels[i]); } - } - } - - auto *title_sizer = new wxBoxSizer(wxHORIZONTAL); - if (! title.IsEmpty()) { - auto *title_widget = new wxStaticText(this, wxID_ANY, title); - title_widget->SetFont(font_title); - title_sizer->Add(title_widget); - } - title_sizer->AddStretchSpacer(); - - if (titles.size() > 1 || is_variants) { - // It only makes sense to add the All / None buttons if there's multiple printers - // All Standard button is added when there are more variants for at least one printer - auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _L("All standard") : _L("Standard")); - auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); - auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); - if (is_variants) - sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& event) { this->select_all(true, false); }); - sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, true); }); - sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); }); - if (is_variants) - title_sizer->Add(sel_all_std, 0, wxRIGHT, BTN_SPACING); - title_sizer->Add(sel_all, 0, wxRIGHT, BTN_SPACING); - title_sizer->Add(sel_none); - - wxGetApp().UpdateDarkUI(sel_all_std); - wxGetApp().UpdateDarkUI(sel_all); - wxGetApp().UpdateDarkUI(sel_none); - - // fill button indexes used later for buttons rescaling - if (is_variants) - m_button_indexes = { sel_all_std->GetId(), sel_all->GetId(), sel_none->GetId() }; - else { - sel_all_std->Destroy(); - m_button_indexes = { sel_all->GetId(), sel_none->GetId() }; - } - } - - sizer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, BTN_SPACING); - sizer->Add(printer_grid); - - SetSizer(sizer); -} - -PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig) - : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; }) -{} - -void PrinterPicker::select_all(bool select, bool alternates) -{ - for (const auto &cb : cboxes) { - if (cb->GetValue() != select) { - cb->SetValue(select); - on_checkbox(cb, select); - } - } - - if (! select) { alternates = false; } - - for (const auto &cb : cboxes_alt) { - if (cb->GetValue() != alternates) { - cb->SetValue(alternates); - on_checkbox(cb, alternates); - } - } -} - -void PrinterPicker::select_one(size_t i, bool select) -{ - if (i < cboxes.size() && cboxes[i]->GetValue() != select) { - cboxes[i]->SetValue(select); - on_checkbox(cboxes[i], select); - } -} - -bool PrinterPicker::any_selected() const -{ - for (const auto &cb : cboxes) { - if (cb->GetValue()) { return true; } - } - - for (const auto &cb : cboxes_alt) { - if (cb->GetValue()) { return true; } - } - - return false; -} - -std::set PrinterPicker::get_selected_models() const -{ - std::set ret_set; - - for (const auto& cb : cboxes) - if (cb->GetValue()) - ret_set.emplace(cb->model); - - for (const auto& cb : cboxes_alt) - if (cb->GetValue()) - ret_set.emplace(cb->model); - - return ret_set; -} - -void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) -{ - PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked); - AddPendingEvent(evt); -} - - -// Wizard page base - -ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent) - : wxPanel(parent->p->hscroll) - , parent(parent) - , shortname(std::move(shortname)) - , indent(indent) -{ - wxGetApp().UpdateDarkUI(this); - - auto *sizer = new wxBoxSizer(wxVERTICAL); - - auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - const auto font = GetFont().MakeBold().Scaled(1.5); - text->SetFont(font); - sizer->Add(text, 0, wxALIGN_LEFT, 0); - sizer->AddSpacer(10); - - content = new wxBoxSizer(wxVERTICAL); - sizer->Add(content, 1, wxEXPAND); - - SetSizer(sizer); - - // There is strange layout on Linux with GTK3, - // see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861 - // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages - if (!wxLinux_gtk3) - this->Hide(); - - Bind(wxEVT_SIZE, [this](wxSizeEvent &event) { - this->Layout(); - event.Skip(); - }); -} - -ConfigWizardPage::~ConfigWizardPage() {} - -wxStaticText* ConfigWizardPage::append_text(wxString text) -{ - auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); - widget->Wrap(WRAP_WIDTH); - widget->SetMinSize(wxSize(WRAP_WIDTH, -1)); - append(widget); - return widget; -} - -void ConfigWizardPage::append_spacer(int space) -{ - // FIXME: scaling - content->AddSpacer(space); -} - -// Wizard pages - -PageWelcome::PageWelcome(ConfigWizard *parent) - : ConfigWizardPage(parent, from_u8((boost::format( -#ifdef __APPLE__ - _utf8(L("Welcome to the %s Configuration Assistant")) -#else - _utf8(L("Welcome to the %s Configuration Wizard")) -#endif - ) % SLIC3R_APP_NAME).str()), _L("Welcome")) - , welcome_text(append_text(from_u8((boost::format( - _utf8(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print."))) - % SLIC3R_APP_NAME - % _utf8(ConfigWizard::name())).str()) - )) - , cbox_reset(append( - new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)")) - )) - , cbox_integrate(append( - new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system).")) - )) -{ - welcome_text->Hide(); - cbox_reset->Hide(); - cbox_integrate->Hide(); -} - -void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) -{ - const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY; - welcome_text->Show(data_empty); - cbox_reset->Show(!data_empty); -#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - if (!DesktopIntegrationDialog::is_integrated()) - cbox_integrate->Show(true); - else - cbox_integrate->Hide(); -#else - cbox_integrate->Hide(); -#endif -} - - -PagePrinters::PagePrinters(ConfigWizard *parent, - wxString title, - wxString shortname, - const VendorProfile &vendor, - unsigned indent, - Technology technology) - : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent) - , technology(technology) - , install(false) // only used for 3rd party vendors -{ - enum { - COL_SIZE = 200, - }; - - AppConfig *appconfig = &this->wizard_p()->appconfig_new; - - const auto families = vendor.families(); - for (const auto &family : families) { - const auto filter = [&](const VendorProfile::PrinterModel &model) { - return ((model.technology == ptFFF && technology & T_FFF) - || (model.technology == ptSLA && technology & T_SLA)) - && model.family == family; - }; - - if (std::find_if(vendor.models.begin(), vendor.models.end(), filter) == vendor.models.end()) { - continue; - } - - const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str()); - auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter); - - picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) { - appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); - wizard_p()->on_printer_pick(this, evt); - }); - - append(new StaticLine(this)); - - append(picker); - printer_pickers.push_back(picker); - has_printers = true; - } - -} - -void PagePrinters::select_all(bool select, bool alternates) -{ - for (auto picker : printer_pickers) { - picker->select_all(select, alternates); - } -} - -int PagePrinters::get_width() const -{ - return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0, - [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); }); -} - -bool PagePrinters::any_selected() const -{ - for (const auto *picker : printer_pickers) { - if (picker->any_selected()) { return true; } - } - - return false; -} - -std::set PagePrinters::get_selected_models() -{ - std::set ret_set; - - for (const auto *picker : printer_pickers) - { - std::set tmp_models = picker->get_selected_models(); - ret_set.insert(tmp_models.begin(), tmp_models.end()); - } - - return ret_set; -} - -void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason) -{ - if (is_primary_printer_page - && (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY) - && printer_pickers.size() > 0 - && printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) { - printer_pickers[0]->select_one(0, true); - } -} - - -const std::string PageMaterials::EMPTY; - -PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name) - : ConfigWizardPage(parent, std::move(title), std::move(shortname)) - , materials(materials) - , list_printer(new StringList(this, wxLB_MULTIPLE)) - , list_type(new StringList(this)) - , list_vendor(new StringList(this)) - , list_profile(new PresetList(this)) -{ - append_spacer(VERTICAL_SPACING); - - const int em = parent->em_unit(); - const int list_h = 30*em; - - - list_printer->SetMinSize(wxSize(23*em, list_h)); - list_type->SetMinSize(wxSize(13*em, list_h)); - list_vendor->SetMinSize(wxSize(13*em, list_h)); - list_profile->SetMinSize(wxSize(23*em, list_h)); - - - - grid = new wxFlexGridSizer(4, em/2, em); - grid->AddGrowableCol(3, 1); - grid->AddGrowableRow(1, 1); - - grid->Add(new wxStaticText(this, wxID_ANY, _L("Printer:"))); - grid->Add(new wxStaticText(this, wxID_ANY, list1name)); - grid->Add(new wxStaticText(this, wxID_ANY, _L("Vendor:"))); - grid->Add(new wxStaticText(this, wxID_ANY, _L("Profile:"))); - - grid->Add(list_printer, 0, wxEXPAND); - grid->Add(list_type, 0, wxEXPAND); - grid->Add(list_vendor, 0, wxEXPAND); - grid->Add(list_profile, 1, wxEXPAND); - - auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL); - auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); - auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); - btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2); - btn_sizer->Add(sel_none); - - wxGetApp().UpdateDarkUI(list_printer); - wxGetApp().UpdateDarkUI(list_type); - wxGetApp().UpdateDarkUI(list_vendor); - wxGetApp().UpdateDarkUI(sel_all); - wxGetApp().UpdateDarkUI(sel_none); - - grid->Add(new wxBoxSizer(wxHORIZONTAL)); - grid->Add(new wxBoxSizer(wxHORIZONTAL)); - grid->Add(new wxBoxSizer(wxHORIZONTAL)); - grid->Add(btn_sizer, 0, wxALIGN_RIGHT); - - append(grid, 1, wxEXPAND); - - append_spacer(VERTICAL_SPACING); - - html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, - wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO); - append(html_window, 0, wxEXPAND); - - list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { - update_lists(list_type->GetSelection(), list_vendor->GetSelection(), evt.GetInt()); - }); - list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { - update_lists(list_type->GetSelection(), list_vendor->GetSelection()); - }); - list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { - update_lists(list_type->GetSelection(), list_vendor->GetSelection()); - }); - - list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); }); - list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); }); - - sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); }); - sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); }); - /* - Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();}); - - list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); }); - list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); }); - list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); }); - */ - reload_presets(); - set_compatible_printers_html_window(std::vector(), false); -} -void PageMaterials::on_paint() -{ -} -void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt) -{ - const wxClientDC dc(list_profile); - const wxPoint pos = evt.GetLogicalPosition(dc); - int item = list_profile->HitTest(pos); - on_material_hovered(item); -} -void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt) -{} -void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt) -{ - on_material_hovered(-1); -} -void PageMaterials::reload_presets() -{ - clear(); - - list_printer->append(_L("(All)"), &EMPTY); - //list_printer->SetLabelMarkup("bald"); - for (const Preset* printer : materials->printers) { - list_printer->append(printer->name, &printer->name); - } - sort_list_data(list_printer, true, false); - if (list_printer->GetCount() > 0) { - list_printer->SetSelection(0); - sel_printers_prev.Clear(); - sel_type_prev = wxNOT_FOUND; - sel_vendor_prev = wxNOT_FOUND; - update_lists(0, 0, 0); - } - - presets_loaded = true; -} - -void PageMaterials::set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers) -{ - const auto bgr_clr = -#if defined(__APPLE__) - html_window->GetParent()->GetBackgroundColour(); -#else -#if defined(_WIN32) - wxGetApp().get_window_default_clr(); -#else - wxSystemSettings::GetColour(wxSYS_COLOUR_MENU); -#endif -#endif - const auto text_clr = wxGetApp().get_label_clr_default(); - const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); - const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); - wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); - wxString text; - if (all_printers) { - wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); - text = wxString::Format( - "" - "" - "" - "" - "" - "%s

%s" - "
" - "
" - "" - "" - , bgr_clr_str - , text_clr_str - , first_line - , second_line - ); - } else { - wxString second_line; - if (!printer_names.empty()) - second_line = (materials->technology == T_FFF ? - _L("Only the following installed printers are compatible with the selected filaments") : - _L("Only the following installed printers are compatible with the selected SLA materials")) + ":"; - text = wxString::Format( - "" - "" - "" - "" - "" - "%s

%s" - "" - "" - , bgr_clr_str - , text_clr_str - , first_line - , second_line); - for (size_t i = 0; i < printer_names.size(); ++i) - { - text += wxString::Format("", boost::nowide::widen(printer_names[i])); - if (i % 3 == 2) { - text += wxString::Format( - "" - ""); - } - } - text += wxString::Format( - "" - "
%s
" - "
" - "
" - "" - "" - ); - } - - wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this)); - const int fs = font.GetPointSize(); - int size[] = { fs,fs,fs,fs,fs,fs,fs }; - html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size); - html_window->SetPage(text); -} - -void PageMaterials::clear_compatible_printers_label() -{ - set_compatible_printers_html_window(std::vector(), false); -} - -void PageMaterials::on_material_hovered(int sel_material) -{ - -} - -void PageMaterials::on_material_highlighted(int sel_material) -{ - if (sel_material == last_hovered_item) - return; - if (sel_material == -1) { - clear_compatible_printers_label(); - return; - } - last_hovered_item = sel_material; - std::vector tabs; - tabs.push_back(std::string()); - tabs.push_back(std::string()); - tabs.push_back(std::string()); - //selected material string - std::string material_name = list_profile->get_data(sel_material); - // get material preset - const std::vector matching_materials = materials->get_presets_by_alias(material_name); - if (matching_materials.empty()) - { - clear_compatible_printers_label(); - return; - } - //find matching printers - std::vector names; - for (const Preset* printer : materials->printers) { - for (const Preset* material : matching_materials) { - if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { - names.push_back(printer->name); - break; - } - } - } - set_compatible_printers_html_window(names, names.size() == materials->printers.size()); -} - -void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected_printer/* = -1*/) -{ - wxWindowUpdateLocker freeze_guard(this); - (void)freeze_guard; - - wxArrayInt sel_printers; - int sel_printers_count = list_printer->GetSelections(sel_printers); - - // Does our wxWidgets version support operator== for wxArrayInt ? - // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614 -#if wxCHECK_VERSION(3, 1, 1) - if (sel_printers != sel_printers_prev) { -#else - auto are_equal = [](const wxArrayInt& arr_first, const wxArrayInt& arr_second) { - if (arr_first.GetCount() != arr_second.GetCount()) - return false; - for (size_t i = 0; i < arr_first.GetCount(); i++) - if (arr_first[i] != arr_second[i]) - return false; - return true; - }; - if (!are_equal(sel_printers, sel_printers_prev)) { -#endif - - // Refresh type list - list_type->Clear(); - list_type->append(_L("(All)"), &EMPTY); - if (sel_printers_count > 0) { - // If all is selected with other printers - // unselect "all" or all printers depending on last value - if (sel_printers[0] == 0 && sel_printers_count > 1) { - if (last_selected_printer == 0) { - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(0); - } else { - list_printer->SetSelection(0, false); - sel_printers_count = list_printer->GetSelections(sel_printers); - } - } - if (sel_printers[0] != 0) { - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) { - const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { - list_type->append(type, &type); - } - }); - } - } else { - //clear selection except "ALL" - list_printer->SetSelection(wxNOT_FOUND); - list_printer->SetSelection(0); - sel_printers_count = list_printer->GetSelections(sel_printers); - - materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) { - const std::string& type = this->materials->get_type(p); - if (list_type->find(type) == wxNOT_FOUND) { - list_type->append(type, &type); - } - }); - } - sort_list_data(list_type, true, true); - } - - sel_printers_prev = sel_printers; - sel_type = 0; - sel_type_prev = wxNOT_FOUND; - list_type->SetSelection(sel_type); - list_profile->Clear(); - } - - if (sel_type != sel_type_prev) { - // Refresh vendor list - - // XXX: The vendor list is created with quadratic complexity here, - // but the number of vendors is going to be very small this shouldn't be a problem. - - list_vendor->Clear(); - list_vendor->append(_L("(All)"), &EMPTY); - if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) { - const std::string& type = list_type->get_data(sel_type); - // find printer preset - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) { - const std::string& vendor = this->materials->get_vendor(p); - if (list_vendor->find(vendor) == wxNOT_FOUND) { - list_vendor->append(vendor, &vendor); - } - }); - } - sort_list_data(list_vendor, true, false); - } - - sel_type_prev = sel_type; - sel_vendor = 0; - sel_vendor_prev = wxNOT_FOUND; - list_vendor->SetSelection(sel_vendor); - list_profile->Clear(); - } - - if (sel_vendor != sel_vendor_prev) { - // Refresh material list - - list_profile->Clear(); - clear_compatible_printers_label(); - if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) { - const std::string& type = list_type->get_data(sel_type); - const std::string& vendor = list_vendor->get_data(sel_vendor); - // finst printer preset - std::vector to_list; - for (int i = 0; i < sel_printers_count; i++) { - const std::string& printer_name = list_printer->get_data(sel_printers[i]); - const Preset* printer = nullptr; - for (const Preset* it : materials->printers) { - if (it->name == printer_name) { - printer = it; - break; - } - } - - materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) { - const std::string& section = materials->appconfig_section(); - bool checked = wizard_p()->appconfig_new.has(section, p->name); - bool was_checked = false; - - int cur_i = list_profile->find(p->alias); - if (cur_i == wxNOT_FOUND) { - cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias); - to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked); - } - else { - was_checked = list_profile->IsChecked(cur_i); - to_list[cur_i].checked = checked || was_checked; - } - list_profile->Check(cur_i, checked || was_checked); - - /* Update preset selection in config. - * If one preset from aliases bundle is selected, - * than mark all presets with this aliases as selected - * */ - if (checked && !was_checked) - wizard_p()->update_presets_in_config(section, p->alias, true); - else if (!checked && was_checked) - wizard_p()->appconfig_new.set(section, p->name, "1"); - }); - } - sort_list_data(list_profile, to_list); - } - - sel_vendor_prev = sel_vendor; - } - wxGetApp().UpdateDarkUI(list_profile); -} - -void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering) -{ -// get data from list -// sort data -// first should be -// then prusa profiles -// then the rest -// in alphabetical order - - std::vector> prusa_profiles; - std::vector> other_profiles; - for (int i = 0 ; i < list->size(); ++i) { - const std::string& data = list->get_data(i); - if (data == EMPTY) // do not sort item - continue; - if (!material_type_ordering && data.find("Prusa") != std::string::npos) - prusa_profiles.push_back(data); - else - other_profiles.push_back(data); - } - if(material_type_ordering) { - - const ConfigOptionDef* def = print_config_def.get("filament_type"); - std::vectorenum_values = def->enum_values; - size_t end_of_sorted = 0; - for (size_t vals = 0; vals < enum_values.size(); vals++) { - for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++) - { - // find instead compare because PET vs PETG - if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) { - //swap - if(profs != end_of_sorted) { - std::reference_wrapper aux = other_profiles[end_of_sorted]; - other_profiles[end_of_sorted] = other_profiles[profs]; - other_profiles[profs] = aux; - } - end_of_sorted++; - break; - } - } - } - } else { - std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { - return a.get() < b.get(); - }); - std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { - return a.get() < b.get(); - }); - } - - list->Clear(); - if (add_All_item) - list->append(_L("(All)"), &EMPTY); - for (const auto& item : prusa_profiles) - list->append(item, &const_cast(item.get())); - for (const auto& item : other_profiles) - list->append(item, &const_cast(item.get())); -} - -void PageMaterials::sort_list_data(PresetList* list, const std::vector& data) -{ - // sort data - // then prusa profiles - // then the rest - // in alphabetical order - std::vector prusa_profiles; - std::vector other_profiles; - //for (int i = 0; i < data.size(); ++i) { - for (const auto& item : data) { - const std::string& name = item.name; - if (name.find("Prusa") != std::string::npos) - prusa_profiles.emplace_back(item); - else - other_profiles.emplace_back(item); - } - std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { - return a.name.get() < b.name.get(); - }); - std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { - return a.name.get() < b.name.get(); - }); - list->Clear(); - for (size_t i = 0; i < prusa_profiles.size(); ++i) { - list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast(prusa_profiles[i].name.get())); - list->Check(i, prusa_profiles[i].checked); - } - for (size_t i = 0; i < other_profiles.size(); ++i) { - list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast(other_profiles[i].name.get())); - list->Check(i + prusa_profiles.size(), other_profiles[i].checked); - } -} - -void PageMaterials::select_material(int i) -{ - const bool checked = list_profile->IsChecked(i); - - const std::string& alias_key = list_profile->get_data(i); - wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked); -} - -void PageMaterials::select_all(bool select) -{ - wxWindowUpdateLocker freeze_guard(this); - (void)freeze_guard; - - for (unsigned i = 0; i < list_profile->GetCount(); i++) { - const bool current = list_profile->IsChecked(i); - if (current != select) { - list_profile->Check(i, select); - select_material(i); - } - } -} - -void PageMaterials::clear() -{ - list_printer->Clear(); - list_type->Clear(); - list_vendor->Clear(); - list_profile->Clear(); - sel_printers_prev.Clear(); - sel_type_prev = wxNOT_FOUND; - sel_vendor_prev = wxNOT_FOUND; - presets_loaded = false; -} - -void PageMaterials::on_activate() -{ - if (! presets_loaded) { - wizard_p()->update_materials(materials->technology); - reload_presets(); - } - first_paint = true; -} - - -const char *PageCustom::default_profile_name = "My Settings"; - -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); - - 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()); - wizard_p()->on_custom_setup(custom_wanted()); - - }); - - append(cb_custom); - append(label); - append(tc_profile_name); -} - -PageUpdate::PageUpdate(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Automatic updates"), _L("Updates")) - , version_check(true) - , preset_update(true) -{ - const AppConfig *app_config = wxGetApp().app_config; - auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - boldfont.SetWeight(wxFONTWEIGHT_BOLD); - - auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates")); - box_slic3r->SetValue(app_config->get("notify_release") != "none"); - append(box_slic3r); - append_text(wxString::Format(_L( - "If enabled, %s checks for new application versions online. When a new version becomes available, " - "a notification is displayed at the next application startup (never during program usage). " - "This is only a notification mechanisms, no automatic installation is done."), SLIC3R_APP_NAME)); - - append_spacer(VERTICAL_SPACING); - - auto *box_presets = new wxCheckBox(this, wxID_ANY, _L("Update built-in Presets automatically")); - box_presets->SetValue(app_config->get("preset_update") == "1"); - append(box_presets); - append_text(wxString::Format(_L( - "If enabled, %s downloads updates of built-in system presets in the background." - "These updates are downloaded into a separate temporary location." - "When a new preset version becomes available it is offered at application startup."), SLIC3R_APP_NAME)); - const auto text_bold = _L("Updates are never applied without user's consent and never overwrite user's customized settings."); - auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold); - label_bold->SetFont(boldfont); - label_bold->Wrap(WRAP_WIDTH); - append(label_bold); - append_text(_L("Additionally a backup snapshot of the whole configuration is created before an update is applied.")); - - box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); }); - box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); }); -} - -PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent) - : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk")) - , full_pathnames(false) -{ - auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _L("Export full pathnames of models and parts sources into 3mf and amf files")); - box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1"); - append(box_pathnames); - append_text(_L( - "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n" - "If not enabled, the Reload from disk command will ask to select each file using an open file dialog." - )); - - box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); }); -} - -#ifdef _WIN32 -PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent) - : ConfigWizardPage(parent, _L("Files association"), _L("Files association")) -{ - cb_3mf = new wxCheckBox(this, wxID_ANY, _L("Associate .3mf files to PrusaSlicer")); - cb_stl = new wxCheckBox(this, wxID_ANY, _L("Associate .stl files to PrusaSlicer")); -// cb_gcode = new wxCheckBox(this, wxID_ANY, _L("Associate .gcode files to PrusaSlicer G-code Viewer")); - - append(cb_3mf); - append(cb_stl); -// append(cb_gcode); -} -#endif // _WIN32 - -PageMode::PageMode(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("View mode"), _L("View mode")) -{ - append_text(_L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n" - "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. " - "The other two offer progressively more sophisticated fine-tuning, " - "they are suitable for advanced and expert users, respectively.")); - - radio_simple = new wxRadioButton(this, wxID_ANY, _L("Simple mode")); - radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode")); - radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode")); - - std::string mode { "simple" }; - wxGetApp().app_config->get("", "view_mode", mode); - - if (mode == "advanced") { radio_advanced->SetValue(true); } - else if (mode == "expert") { radio_expert->SetValue(true); } - else { radio_simple->SetValue(true); } - - append(radio_simple); - append(radio_advanced); - append(radio_expert); - - append_text("\n" + _L("The size of the object can be specified in inches")); - check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); - check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1"); - append(check_inch); - - on_activate(); -} - -void PageMode::serialize_mode(AppConfig *app_config) const -{ - std::string mode = ""; - - if (radio_simple->GetValue()) { mode = "simple"; } - if (radio_advanced->GetValue()) { mode = "advanced"; } - if (radio_expert->GetValue()) { mode = "expert"; } - - app_config->set("view_mode", mode); - app_config->set("use_inches", check_inch->GetValue() ? "1" : "0"); -} - -PageVendors::PageVendors(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors")) -{ - const AppConfig &appconfig = this->wizard_p()->appconfig_new; - - append_text(wxString::Format(_L("Pick another vendor supported by %s"), SLIC3R_APP_NAME) + ":"); - - auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - boldfont.SetWeight(wxFONTWEIGHT_BOLD); - - for (const auto &pair : wizard_p()->bundles) { - const VendorProfile *vendor = pair.second.vendor_profile; - if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } - - auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); - cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { - wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked()); - }); - - const auto &vendors = appconfig.vendors(); - const bool enabled = vendors.find(pair.first) != vendors.end(); - if (enabled) { - cbox->SetValue(true); - - auto pages = wizard_p()->pages_3rdparty.find(vendor->id); - wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created"); - - for (PagePrinters* page : { pages->second.first, pages->second.second }) - if (page) page->install = true; - } - - append(cbox); - } -} - -PageFirmware::PageFirmware(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Firmware Type"), _L("Firmware"), 1) - , gcode_opt(*print_config_def.get("gcode_flavor")) - , gcode_picker(nullptr) -{ - append_text(_L("Choose the type of firmware used by your printer.")); - append_text(_(gcode_opt.tooltip)); - - wxArrayString choices; - choices.Alloc(gcode_opt.enum_labels.size()); - for (const auto &label : gcode_opt.enum_labels) { - choices.Add(label); - } - - gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices); - wxGetApp().UpdateDarkUI(gcode_picker); - const auto &enum_values = gcode_opt.enum_values; - auto needle = enum_values.cend(); - if (gcode_opt.default_value) { - needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize()); - } - if (needle != enum_values.cend()) { - gcode_picker->SetSelection(needle - enum_values.cbegin()); - } else { - gcode_picker->SetSelection(0); - } - - append(gcode_picker); -} - -void PageFirmware::apply_custom_config(DynamicPrintConfig &config) -{ - auto sel = gcode_picker->GetSelection(); - if (sel >= 0 && (size_t)sel < gcode_opt.enum_labels.size()) { - auto *opt = new ConfigOptionEnum(static_cast(sel)); - config.set_key_value("gcode_flavor", opt); - } -} - -PageBedShape::PageBedShape(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1) - , shape_panel(new BedShapePanel(this)) -{ - append_text(_L("Set the shape of your printer's bed.")); - - shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"), - *wizard_p()->custom_config->option("bed_custom_texture"), - *wizard_p()->custom_config->option("bed_custom_model")); - - append(shape_panel); -} - -void PageBedShape::apply_custom_config(DynamicPrintConfig &config) -{ - const std::vector& points = shape_panel->get_shape(); - const std::string& custom_texture = shape_panel->get_custom_texture(); - const std::string& custom_model = shape_panel->get_custom_model(); - config.set_key_value("bed_shape", new ConfigOptionPoints(points)); - config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture)); - config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); -} - -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.")); - 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() {} -}; - -PageDiameters::PageDiameters(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1) - , diam_nozzle(new DiamTextCtrl(this)) - , diam_filam (new DiamTextCtrl(this)) -{ - auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value(); - wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); - diam_nozzle->SetValue(value); - - auto *default_filam = print_config_def.get("filament_diameter")->get_default_value(); - value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); - diam_filam->SetValue(value); - - diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId()); - diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId()); - - append_text(_L("Enter the diameter of your printer's hot end nozzle.")); - - auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5); - auto *text_nozzle = new wxStaticText(this, wxID_ANY, _L("Nozzle Diameter:")); - auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm")); - sizer_nozzle->AddGrowableCol(0, 1); - sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL); - sizer_nozzle->Add(diam_nozzle); - sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_nozzle); - - append_spacer(VERTICAL_SPACING); - - append_text(_L("Enter the diameter of your filament.")); - append_text(_L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.")); - - auto *sizer_filam = new wxFlexGridSizer(3, 5, 5); - auto *text_filam = new wxStaticText(this, wxID_ANY, _L("Filament Diameter:")); - auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm")); - sizer_filam->AddGrowableCol(0, 1); - sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL); - sizer_filam->Add(diam_filam); - sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_filam); -} - -void PageDiameters::apply_custom_config(DynamicPrintConfig &config) -{ - double val = 0.0; - diam_nozzle->GetValue().ToDouble(&val); - auto *opt_nozzle = new ConfigOptionFloats(1, val); - config.set_key_value("nozzle_diameter", opt_nozzle); - - val = 0.0; - diam_filam->GetValue().ToDouble(&val); - auto * opt_filam = new ConfigOptionFloats(1, val); - config.set_key_value("filament_diameter", opt_filam); - - auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { - char buf[64]; // locales don't matter here (sprintf/atof) - sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4); - config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false)); - }; - - set_extrusion_width("support_material_extrusion_width", 0.35); - set_extrusion_width("top_infill_extrusion_width", 0.40); - set_extrusion_width("first_layer_extrusion_width", 0.42); - - set_extrusion_width("extrusion_width", 0.45); - set_extrusion_width("perimeter_extrusion_width", 0.45); - set_extrusion_width("external_perimeter_extrusion_width", 0.45); - set_extrusion_width("infill_extrusion_width", 0.45); - set_extrusion_width("solid_infill_extrusion_width", 0.45); -} - -class SpinCtrlDouble: public wxSpinCtrlDouble -{ -public: - SpinCtrlDouble(wxWindow* parent) - { -#ifdef _WIN32 - long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE; -#else - long style = wxSP_ARROW_KEYS; -#endif - Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, style); -#ifdef _WIN32 - wxGetApp().UpdateDarkUI(this->GetText()); -#endif - this->Refresh(); - } - ~SpinCtrlDouble() {} -}; - -PageTemperatures::PageTemperatures(ConfigWizard *parent) - : ConfigWizardPage(parent, _L("Nozzle and Bed Temperatures"), _L("Temperatures"), 1) - , spin_extr(new SpinCtrlDouble(this)) - , spin_bed (new SpinCtrlDouble(this)) -{ - spin_extr->SetIncrement(5.0); - const auto &def_extr = *print_config_def.get("temperature"); - spin_extr->SetRange(def_extr.min, def_extr.max); - auto *default_extr = def_extr.get_default_value(); - spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200); - - spin_bed->SetIncrement(5.0); - const auto &def_bed = *print_config_def.get("bed_temperature"); - spin_bed->SetRange(def_bed.min, def_bed.max); - auto *default_bed = def_bed.get_default_value(); - spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0); - - append_text(_L("Enter the temperature needed for extruding your filament.")); - append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.")); - - auto *sizer_extr = new wxFlexGridSizer(3, 5, 5); - auto *text_extr = new wxStaticText(this, wxID_ANY, _L("Extrusion Temperature:")); - auto *unit_extr = new wxStaticText(this, wxID_ANY, _L("°C")); - sizer_extr->AddGrowableCol(0, 1); - sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL); - sizer_extr->Add(spin_extr); - sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_extr); - - append_spacer(VERTICAL_SPACING); - - append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed.")); - append_text(_L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.")); - - auto *sizer_bed = new wxFlexGridSizer(3, 5, 5); - auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:")); - auto *unit_bed = new wxStaticText(this, wxID_ANY, _L("°C")); - sizer_bed->AddGrowableCol(0, 1); - sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL); - sizer_bed->Add(spin_bed); - sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL); - append(sizer_bed); -} - -void PageTemperatures::apply_custom_config(DynamicPrintConfig &config) -{ - auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue()); - config.set_key_value("temperature", opt_extr); - auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue()); - config.set_key_value("first_layer_temperature", opt_extr1st); - auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue()); - config.set_key_value("bed_temperature", opt_bed); - auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue()); - config.set_key_value("first_layer_bed_temperature", opt_bed1st); -} - - -// Index - -ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) - : wxPanel(parent) - , bg(ScalableBitmap(parent, "PrusaSlicer_192px_transparent.png", 192)) - , bullet_black(ScalableBitmap(parent, "bullet_black.png")) - , bullet_blue(ScalableBitmap(parent, "bullet_blue.png")) - , bullet_white(ScalableBitmap(parent, "bullet_white.png")) - , item_active(NO_ITEM) - , item_hover(NO_ITEM) - , last_page((size_t)-1) -{ -#ifndef __WXOSX__ - SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX -#endif //__WXOSX__ - SetMinSize(bg.bmp().GetSize()); - - const wxSize size = GetTextExtent("m"); - em_w = size.x; - em_h = size.y; - - Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this); - Bind(wxEVT_SIZE, [this](wxEvent& e) { e.Skip(); Refresh(); }); - Bind(wxEVT_MOTION, &ConfigWizardIndex::on_mouse_move, this); - - Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &evt) { - if (item_hover != -1) { - item_hover = -1; - Refresh(); - } - evt.Skip(); - }); - - Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &evt) { - if (item_hover >= 0) { go_to(item_hover); } - }); -} - -wxDECLARE_EVENT(EVT_INDEX_PAGE, wxCommandEvent); - -void ConfigWizardIndex::add_page(ConfigWizardPage *page) -{ - last_page = items.size(); - items.emplace_back(Item { page->shortname, page->indent, page }); - Refresh(); -} - -void ConfigWizardIndex::add_label(wxString label, unsigned indent) -{ - items.emplace_back(Item { std::move(label), indent, nullptr }); - Refresh(); -} - -ConfigWizardPage* ConfigWizardIndex::active_page() const -{ - if (item_active >= items.size()) { return nullptr; } - - return items[item_active].page; -} - -void ConfigWizardIndex::go_prev() -{ - // Search for a preceiding item that is a page (not a label, ie. page != nullptr) - - if (item_active == NO_ITEM) { return; } - - for (size_t i = item_active; i > 0; i--) { - if (items[i - 1].page != nullptr) { - go_to(i - 1); - return; - } - } -} - -void ConfigWizardIndex::go_next() -{ - // Search for a next item that is a page (not a label, ie. page != nullptr) - - if (item_active == NO_ITEM) { return; } - - for (size_t i = item_active + 1; i < items.size(); i++) { - if (items[i].page != nullptr) { - go_to(i); - return; - } - } -} - -// This one actually performs the go-to op -void ConfigWizardIndex::go_to(size_t i) -{ - if (i != item_active - && i < items.size() - && items[i].page != nullptr) { - auto *new_active = items[i].page; - auto *former_active = active_page(); - if (former_active != nullptr) { - former_active->Hide(); - } - - item_active = i; - new_active->Show(); - - wxCommandEvent evt(EVT_INDEX_PAGE, GetId()); - AddPendingEvent(evt); - - Refresh(); - - new_active->on_activate(); - } -} - -void ConfigWizardIndex::go_to(const ConfigWizardPage *page) -{ - if (page == nullptr) { return; } - - for (size_t i = 0; i < items.size(); i++) { - if (items[i].page == page) { - go_to(i); - return; - } - } -} - -void ConfigWizardIndex::clear() -{ - auto *former_active = active_page(); - if (former_active != nullptr) { former_active->Hide(); } - - items.clear(); - item_active = NO_ITEM; -} - -void ConfigWizardIndex::on_paint(wxPaintEvent & evt) -{ - const auto size = GetClientSize(); - if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; } - - wxPaintDC dc(this); - - const auto bullet_w = bullet_black.bmp().GetSize().GetWidth(); - const auto bullet_h = bullet_black.bmp().GetSize().GetHeight(); - const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0; - const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0; - const int yinc = item_height(); - - int index_width = 0; - - unsigned y = 0; - for (size_t i = 0; i < items.size(); i++) { - const Item& item = items[i]; - unsigned x = em_w/2 + item.indent * em_w; - - if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) { - dc.DrawBitmap(bullet_blue.bmp(), x, y + yoff_icon, false); - } - else if (i < item_active) { dc.DrawBitmap(bullet_black.bmp(), x, y + yoff_icon, false); } - else if (i > item_active) { dc.DrawBitmap(bullet_white.bmp(), x, y + yoff_icon, false); } - - x += + bullet_w + em_w/2; - const auto text_size = dc.GetTextExtent(item.label); - dc.SetTextForeground(wxGetApp().get_label_clr_default()); - dc.DrawText(item.label, x, y + yoff_text); - - y += yinc; - index_width = std::max(index_width, (int)x + text_size.x); - } - - //draw logo - if (int y = size.y - bg.GetBmpHeight(); y>=0) { - dc.DrawBitmap(bg.bmp(), 0, y, false); - index_width = std::max(index_width, bg.GetBmpWidth() + em_w / 2); - } - - if (GetMinSize().x < index_width) { - CallAfter([this, index_width]() { - SetMinSize(wxSize(index_width, GetMinSize().y)); - Refresh(); - }); - } -} - -void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt) -{ - const wxClientDC dc(this); - const wxPoint pos = evt.GetLogicalPosition(dc); - - const ssize_t item_hover_new = pos.y / item_height(); - - if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) { - item_hover = item_hover_new; - Refresh(); - } - - evt.Skip(); -} - -void ConfigWizardIndex::msw_rescale() -{ - const wxSize size = GetTextExtent("m"); - em_w = size.x; - em_h = size.y; - - bg.msw_rescale(); - SetMinSize(bg.bmp().GetSize()); - - bullet_black.msw_rescale(); - bullet_blue.msw_rescale(); - bullet_white.msw_rescale(); - Refresh(); -} - - -// Materials - -const std::string Materials::UNKNOWN = "(Unknown)"; - -void Materials::push(const Preset *preset) -{ - presets.emplace_back(preset); - types.insert(technology & T_FFF - ? Materials::get_filament_type(preset) - : Materials::get_material_type(preset)); -} - -void Materials::add_printer(const Preset* preset) -{ - printers.insert(preset); -} - -void Materials::clear() -{ - presets.clear(); - types.clear(); - printers.clear(); - compatibility_counter.clear(); -} - -const std::string& Materials::appconfig_section() const -{ - return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; -} - -const std::string& Materials::get_type(const Preset *preset) const -{ - return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset); -} - -const std::string& Materials::get_vendor(const Preset *preset) const -{ - return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset); -} - -const std::string& Materials::get_filament_type(const Preset *preset) -{ - const auto *opt = preset->config.opt("filament_type"); - if (opt != nullptr && opt->values.size() > 0) { - return opt->values[0]; - } else { - return UNKNOWN; - } -} - -const std::string& Materials::get_filament_vendor(const Preset *preset) -{ - const auto *opt = preset->config.opt("filament_vendor"); - return opt != nullptr ? opt->value : UNKNOWN; -} - -const std::string& Materials::get_material_type(const Preset *preset) -{ - const auto *opt = preset->config.opt("material_type"); - if (opt != nullptr) { - return opt->value; - } else { - return UNKNOWN; - } -} - -const std::string& Materials::get_material_vendor(const Preset *preset) -{ - const auto *opt = preset->config.opt("material_vendor"); - return opt != nullptr ? opt->value : UNKNOWN; -} - -// priv - -static const std::unordered_map> legacy_preset_map {{ - { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") }, - { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") }, - { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, - { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") }, - { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, - { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") }, - { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, - { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") }, -}}; - -void ConfigWizard::priv::load_pages() -{ - wxWindowUpdateLocker freeze_guard(q); - (void)freeze_guard; - - const ConfigWizardPage *former_active = index->active_page(); - - index->clear(); - - index->add_page(page_welcome); - - // Printers - if (!only_sla_mode) - index->add_page(page_fff); - index->add_page(page_msla); - if (!only_sla_mode) { - index->add_page(page_vendors); - for (const auto &pages : pages_3rdparty) { - for ( PagePrinters* page : { pages.second.first, pages.second.second }) - if (page && page->install) - index->add_page(page); - } - - index->add_page(page_custom); - if (page_custom->custom_wanted()) { - index->add_page(page_firmware); - index->add_page(page_bed); - index->add_page(page_diams); - index->add_page(page_temps); - } - - // Filaments & Materials - if (any_fff_selected) { index->add_page(page_filaments); } - } - if (any_sla_selected) { index->add_page(page_sla_materials); } - - // there should to be selected at least one printer - btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected); - - index->add_page(page_update); - index->add_page(page_reload_from_disk); -#ifdef _WIN32 - index->add_page(page_files_association); -#endif // _WIN32 - index->add_page(page_mode); - - index->go_to(former_active); // Will restore the active item/page if possible - - q->Layout(); -// This Refresh() is needed to avoid ugly artifacts after printer selection, when no one vendor was selected from the very beginnig - q->Refresh(); -} - -void ConfigWizard::priv::init_dialog_size() -{ - // Clamp the Wizard size based on screen dimensions - - const auto idx = wxDisplay::GetFromWindow(q); - wxDisplay display(idx != wxNOT_FOUND ? idx : 0u); - - const auto disp_rect = display.GetClientArea(); - wxRect window_rect( - disp_rect.x + disp_rect.width / 20, - disp_rect.y + disp_rect.height / 20, - 9*disp_rect.width / 10, - 9*disp_rect.height / 10); - - const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), (only_sla_mode ? page_msla->get_width() : page_fff->get_width()) + 30 * em()); // XXX: magic constant, I found no better solution - if (width_hint < window_rect.width) { - window_rect.x += (window_rect.width - width_hint) / 2; - window_rect.width = width_hint; - } - - q->SetSize(window_rect); -} - -void ConfigWizard::priv::load_vendors() -{ - bundles = BundleMap::load(); - - // Load up the set of vendors / models / variants the user has had enabled up till now - AppConfig *app_config = wxGetApp().app_config; - if (! app_config->legacy_datadir()) { - appconfig_new.set_vendors(*app_config); - } else { - // In case of legacy datadir, try to guess the preference based on the printer preset files that are present - const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer"; - for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir)) - if (Slic3r::is_ini_file(dir_entry)) { - auto needle = legacy_preset_map.find(dir_entry.path().filename().string()); - if (needle == legacy_preset_map.end()) { continue; } - - const auto &model = needle->second.first; - const auto &variant = needle->second.second; - appconfig_new.set_variant("PrusaResearch", model, variant, true); - } - } - - // Initialize the is_visible flag in printer Presets - for (auto &pair : bundles) { - pair.second.preset_bundle->load_installed_printers(appconfig_new); - } - - // Copy installed filaments and SLA material names from app_config to appconfig_new - // while resolving current names of profiles, which were renamed in the meantime. - for (PrinterTechnology technology : { ptFFF, ptSLA }) { - const std::string §ion_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; - std::map section_new; - if (app_config->has_section(section_name)) { - const std::map §ion_old = app_config->get_section(section_name); - for (const auto& material_name_and_installed : section_old) - if (material_name_and_installed.second == "1") { - // Material is installed. Resolve it in bundles. - size_t num_found = 0; - const std::string &material_name = material_name_and_installed.first; - for (auto &bundle : bundles) { - const PresetCollection &materials = bundle.second.preset_bundle->materials(technology); - const Preset *preset = materials.find_preset(material_name); - if (preset == nullptr) { - // Not found. Maybe the material preset is there, bu it was was renamed? - const std::string *new_name = materials.get_preset_name_renamed(material_name); - if (new_name != nullptr) - preset = materials.find_preset(*new_name); - } - if (preset != nullptr) { - // Materal preset was found, mark it as installed. - section_new[preset->name] = "1"; - ++ num_found; - } - } - if (num_found == 0) - BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name; - else if (num_found > 1) - BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found; - } - } - appconfig_new.set_section(section_name, section_new); - }; -} - -void ConfigWizard::priv::add_page(ConfigWizardPage *page) -{ - const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0; - hscroll_sizer->Add(page, proportion, wxEXPAND); - all_pages.push_back(page); -} - -void ConfigWizard::priv::enable_next(bool enable) -{ - btn_next->Enable(enable); - btn_finish->Enable(enable); -} - -void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page) -{ - switch (start_page) { - case ConfigWizard::SP_PRINTERS: - index->go_to(page_fff); - btn_next->SetFocus(); - break; - case ConfigWizard::SP_FILAMENTS: - index->go_to(page_filaments); - btn_finish->SetFocus(); - break; - case ConfigWizard::SP_MATERIALS: - index->go_to(page_sla_materials); - btn_finish->SetFocus(); - break; - default: - index->go_to(page_welcome); - btn_next->SetFocus(); - break; - } -} - -void ConfigWizard::priv::create_3rdparty_pages() -{ - for (const auto &pair : bundles) { - const VendorProfile *vendor = pair.second.vendor_profile; - if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } - - bool is_fff_technology = false; - bool is_sla_technology = false; - - for (auto& model: vendor->models) - { - if (!is_fff_technology && model.technology == ptFFF) - is_fff_technology = true; - if (!is_sla_technology && model.technology == ptSLA) - is_sla_technology = true; - } - - PagePrinters* pageFFF = nullptr; - PagePrinters* pageSLA = nullptr; - - if (is_fff_technology) { - pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name+" FFF", *vendor, 1, T_FFF); - add_page(pageFFF); - } - - if (is_sla_technology) { - pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name+" MSLA", *vendor, 1, T_SLA); - add_page(pageSLA); - } - - pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}}); - } -} - -void ConfigWizard::priv::set_run_reason(RunReason run_reason) -{ - this->run_reason = run_reason; - for (auto &page : all_pages) { - page->set_run_reason(run_reason); - } -} - -void ConfigWizard::priv::update_materials(Technology technology) -{ - if (any_fff_selected && (technology & T_FFF)) { - filaments.clear(); - aliases_fff.clear(); - // Iterate filaments in all bundles - for (const auto &pair : bundles) { - for (const auto &filament : pair.second.preset_bundle->filaments) { - // Check if filament is already added - if (filaments.containts(&filament)) - continue; - // Iterate printers in all bundles - for (const auto &printer : pair.second.preset_bundle->printers) { - if (!printer.is_visible || printer.printer_technology() != ptFFF) - continue; - // Filter out inapplicable printers - if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) { - if (!filaments.containts(&filament)) { - filaments.push(&filament); - if (!filament.alias.empty()) - aliases_fff[filament.alias].insert(filament.name); - } - filaments.add_printer(&printer); - } - } - - } - } - // count compatible printers - for (const auto& preset : filaments.presets) { - - const auto filter = [preset](const std::pair element) { - return preset->alias == element.first; - }; - if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) { - continue; - } - std::vector idx_with_same_alias; - for (size_t i = 0; i < filaments.presets.size(); ++i) { - if (preset->alias == filaments.presets[i]->alias) - idx_with_same_alias.push_back(i); - } - size_t counter = 0; - for (const auto& printer : filaments.printers) { - if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF) - continue; - bool compatible = false; - // Test otrher materials with same alias - for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { - const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]); - const Preset& prntr = *printer; - if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { - compatible = true; - break; - } - } - if (compatible) - counter++; - } - filaments.compatibility_counter.emplace_back(preset->alias, counter); - } - } - - if (any_sla_selected && (technology & T_SLA)) { - sla_materials.clear(); - aliases_sla.clear(); - - // Iterate SLA materials in all bundles - for (const auto &pair : bundles) { - for (const auto &material : pair.second.preset_bundle->sla_materials) { - // Check if material is already added - if (sla_materials.containts(&material)) - continue; - // Iterate printers in all bundles - // For now, we only allow the profiles to be compatible with another profiles inside the same bundle. - for (const auto& printer : pair.second.preset_bundle->printers) { - if(!printer.is_visible || printer.printer_technology() != ptSLA) - continue; - // Filter out inapplicable printers - if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) { - // Check if material is already added - if(!sla_materials.containts(&material)) { - sla_materials.push(&material); - if (!material.alias.empty()) - aliases_sla[material.alias].insert(material.name); - } - sla_materials.add_printer(&printer); - } - } - } - } - // count compatible printers - for (const auto& preset : sla_materials.presets) { - - const auto filter = [preset](const std::pair element) { - return preset->alias == element.first; - }; - if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) { - continue; - } - std::vector idx_with_same_alias; - for (size_t i = 0; i < sla_materials.presets.size(); ++i) { - if(preset->alias == sla_materials.presets[i]->alias) - idx_with_same_alias.push_back(i); - } - size_t counter = 0; - for (const auto& printer : sla_materials.printers) { - if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA) - continue; - bool compatible = false; - // Test otrher materials with same alias - for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { - const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]); - const Preset& prntr = *printer; - if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { - compatible = true; - break; - } - } - if (compatible) - counter++; - } - sla_materials.compatibility_counter.emplace_back(preset->alias, counter); - } - } -} - -void ConfigWizard::priv::on_custom_setup(const bool custom_wanted) -{ - custom_printer_selected = custom_wanted; - load_pages(); -} - -void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt) -{ - if (check_sla_selected() != any_sla_selected || - check_fff_selected() != any_fff_selected) { - any_fff_selected = check_fff_selected(); - any_sla_selected = check_sla_selected(); - - load_pages(); - } - - // Update the is_visible flag on relevant printer profiles - for (auto &pair : bundles) { - if (pair.first != evt.vendor_id) { continue; } - - for (auto &preset : pair.second.preset_bundle->printers) { - if (preset.config.opt_string("printer_model") == evt.model_id - && preset.config.opt_string("printer_variant") == evt.variant_name) { - preset.is_visible = evt.enable; - } - } - - // When a printer model is picked, but there is no material installed compatible with this printer model, - // install default materials for selected printer model silently. - check_and_install_missing_materials(page->technology, evt.model_id); - } - - if (page->technology & T_FFF) { - page_filaments->clear(); - } else if (page->technology & T_SLA) { - page_sla_materials->clear(); - } -} - -void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology) -{ - PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials; - for (const std::string& material : printer_model.default_materials) - appconfig_new.set(page_materials->materials->appconfig_section(), material, "1"); -} - -void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set &printer_models) -{ - PageMaterials *page_materials = technology & T_FFF ? page_filaments : page_sla_materials; - const std::string &appconfig_section = page_materials->materials->appconfig_section(); - - // Following block was unnecessary. Its enough to iterate printer_models once. Not for every vendor printer page. - // Filament is selected on same page for all printers of same technology. - /* - auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models, technology](PagePrinters *page_printers, Technology technology) - { - const std::string vendor_id = page_printers->get_vendor_id(); - for (auto& pair : bundles) - if (pair.first == vendor_id) - for (const VendorProfile::PrinterModel *printer_model : printer_models) - for (const std::string &material : printer_model->default_materials) - appconfig_new.set(appconfig_section, material, "1"); - }; - - PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla; - select_default_materials_for_printer_page(page_printers, technology); - - for (const auto& printer : pages_3rdparty) - { - page_printers = technology & T_FFF ? printer.second.first : printer.second.second; - if (page_printers) - select_default_materials_for_printer_page(page_printers, technology); - } - */ - - // Iterate printer_models and select default materials. If none available -> msg to user. - std::vector models_without_default; - for (const VendorProfile::PrinterModel* printer_model : printer_models) { - if (printer_model->default_materials.empty()) { - models_without_default.emplace_back(printer_model); - } else { - for (const std::string& material : printer_model->default_materials) - appconfig_new.set(appconfig_section, material, "1"); - } - } - - if (!models_without_default.empty()) { - std::string printer_names = "\n\n"; - for (const VendorProfile::PrinterModel* printer_model : models_without_default) { - printer_names += printer_model->name + "\n"; - } - printer_names += "\n\n"; - std::string message = (technology & T_FFF ? - GUI::format(_L("Following printer profiles has no default filament: %1%Please select one manually."), printer_names) : - GUI::format(_L("Following printer profiles has no default material: %1%Please select one manually."), printer_names)); - MessageDialog msg(q, message, _L("Notice"), wxOK); - msg.ShowModal(); - } - - update_materials(technology); - ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets(); -} - -void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install) -{ - auto it = pages_3rdparty.find(vendor->id); - wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile"); - - for (PagePrinters* page : { it->second.first, it->second.second }) - if (page) { - if (page->install && !install) - page->select_all(false); - page->install = install; - // if some 3rd vendor is selected, select first printer for them - if (install) - page->printer_pickers[0]->select_one(0, true); - page->Layout(); - } - - load_pages(); -} - -bool ConfigWizard::priv::on_bnt_finish() -{ - wxBusyCursor wait; - /* When Filaments or Sla Materials pages are activated, - * materials for this pages are automaticaly updated and presets are reloaded. - * - * But, if _Finish_ button was clicked without activation of those pages - * (for example, just some printers were added/deleted), - * than last changes wouldn't be updated for filaments/materials. - * SO, do that before close of Wizard - */ - update_materials(T_ANY); - if (any_fff_selected) - page_filaments->reload_presets(); - if (any_sla_selected) - page_sla_materials->reload_presets(); - - // theres no need to check that filament is selected if we have only custom printer - if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true; - // check, that there is selected at least one filament/material - return check_and_install_missing_materials(T_ANY); -} - -// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed -// for each Printer preset of each Printer Model installed. -// -// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently. -// Otherwise the user is quieried whether to install the missing default materials or not. -// -// Return true if the tested Printer Models already had materials installed. -// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these -// respective Printer Models or not. -bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id) -{ - // Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle, - // which is compatible with it. - const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string §ion) - { - const std::map &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map(); - std::set printer_models_without_material; - for (const auto &pair : bundles) { - const PresetCollection &materials = pair.second.preset_bundle->materials(technology); - for (const auto &printer : pair.second.preset_bundle->printers) { - if (printer.is_visible && printer.printer_technology() == technology) { - const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer); - assert(printer_model != nullptr); - if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) && - printer_models_without_material.find(printer_model) == printer_models_without_material.end()) { - bool has_material = false; - for (const auto& preset : appconfig_presets) { - if (preset.second == "1") { - const Preset *material = materials.find_preset(preset.first, false); - if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) { - has_material = true; - break; - } - } - } - if (! has_material) - printer_models_without_material.insert(printer_model); - } - } - } - } - assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id); - return printer_models_without_material; - }; - - const auto ask_and_select_default_materials = [this](const wxString &message, const std::set &printer_models, Technology technology) - { - //wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO); - MessageDialog msg(q, message, _L("Notice"), wxYES_NO); - if (msg.ShowModal() == wxID_YES) - select_default_materials_for_printer_models(technology, printer_models); - }; - - const auto printer_model_list = [](const std::set &printer_models) -> wxString { - wxString out; - for (const VendorProfile::PrinterModel *printer_model : printer_models) { - wxString name = from_u8(printer_model->name); - out += "\t\t"; - out += name; - out += "\n"; - } - return out; - }; - - if (any_fff_selected && (technology & T_FFF)) { - std::set printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS); - if (! printer_models_without_material.empty()) { - if (only_for_model_id.empty()) - ask_and_select_default_materials( - _L("The following FFF printer models have no filament selected:") + - "\n\n\t" + - printer_model_list(printer_models_without_material) + - "\n\n\t" + - _L("Do you want to select default filaments for these FFF printer models?"), - printer_models_without_material, - T_FFF); - else - select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF); - return false; - } - } - - if (any_sla_selected && (technology & T_SLA)) { - std::set printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS); - if (! printer_models_without_material.empty()) { - if (only_for_model_id.empty()) - ask_and_select_default_materials( - _L("The following SLA printer models have no materials selected:") + - "\n\n\t" + - printer_model_list(printer_models_without_material) + - "\n\n\t" + - _L("Do you want to select default SLA materials for these printer models?"), - printer_models_without_material, - T_SLA); - else - select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA); - return false; - } - } - - return true; -} - -static std::set get_new_added_presets(const std::map& old_data, const std::map& new_data) -{ - auto get_aliases = [](const std::map& data) { - std::set old_aliases; - for (auto item : data) { - const std::string& name = item.first; - size_t pos = name.find("@"); - old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1)); - } - return old_aliases; - }; - - std::set old_aliases = get_aliases(old_data); - std::set new_aliases = get_aliases(new_data); - std::set diff; - std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin())); - - return diff; -} - -static std::string get_first_added_preset(const std::map& old_data, const std::map& new_data) -{ - std::set diff = get_new_added_presets(old_data, new_data); - if (diff.empty()) - return std::string(); - return *diff.begin(); -} - -bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes) -{ - wxString header, caption = _L("Configuration is edited in ConfigWizard"); - const auto enabled_vendors = appconfig_new.vendors(); - const auto enabled_vendors_old = app_config->vendors(); - - bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model()); - PrinterTechnology preferred_pt = ptAny; - auto get_preferred_printer_technology = [enabled_vendors, enabled_vendors_old, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) { - const auto config = enabled_vendors.find(bundle_name); - PrinterTechnology pt = ptAny; - if (config != enabled_vendors.end()) { - for (const auto& model : bundle.vendor_profile->models) { - if (const auto model_it = config->second.find(model.id); - model_it != config->second.end() && model_it->second.size() > 0) { - pt = model.technology; - const auto config_old = enabled_vendors_old.find(bundle_name); - if (config_old == enabled_vendors_old.end() || config_old->second.find(model.id) == config_old->second.end()) { - // if preferred printer model has SLA printer technology it's important to check the model for multi-part state - if (pt == ptSLA && suppress_sla_printer) - continue; - return pt; - } - - if (const auto model_it_old = config_old->second.find(model.id); - model_it_old == config_old->second.end() || model_it_old->second != model_it->second) { - // if preferred printer model has SLA printer technology it's important to check the model for multi-part state - if (pt == ptSLA && suppress_sla_printer) - continue; - return pt; - } - } - } - } - return pt; - }; - // Prusa printers are considered first, then 3rd party. - if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle()); - preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) { - for (const auto& bundle : bundles) { - if (bundle.second.is_prusa_bundle) { continue; } - if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny) - continue; - else if (preferred_pt == ptAny) - preferred_pt = pt; - if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer))) - break; - } - } - - if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption)) - return false; - - bool check_unsaved_preset_changes = page_welcome->reset_user_profile(); - if (check_unsaved_preset_changes) - header = _L("All user presets will be deleted."); - int act_btns = UnsavedChangesDialog::ActionButtons::KEEP; - if (!check_unsaved_preset_changes) - act_btns |= UnsavedChangesDialog::ActionButtons::SAVE; - - // Install bundles from resources if needed: - std::vector install_bundles; - for (const auto &pair : bundles) { - if (! pair.second.is_in_resources) { continue; } - - if (pair.second.is_prusa_bundle) { - // Always install Prusa bundle, because it has a lot of filaments/materials - // likely to be referenced by other profiles. - install_bundles.emplace_back(pair.first); - continue; - } - - const auto vendor = enabled_vendors.find(pair.first); - if (vendor == enabled_vendors.end()) { continue; } - - size_t size_sum = 0; - for (const auto &model : vendor->second) { size_sum += model.second.size(); } - - if (size_sum > 0) { - // This vendor needs to be installed - install_bundles.emplace_back(pair.first); - } - } - if (!check_unsaved_preset_changes) - if ((check_unsaved_preset_changes = install_bundles.size() > 0)) - header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size()); - -#ifdef __linux__ - // Desktop integration on Linux - if (page_welcome->integrate_desktop()) - DesktopIntegrationDialog::perform_desktop_integration(); -#endif - - // Decide whether to create snapshot based on run_reason and the reset profile checkbox - bool snapshot = true; - Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE; - switch (run_reason) { - case ConfigWizard::RR_DATA_EMPTY: - snapshot = false; - break; - case ConfigWizard::RR_DATA_LEGACY: - snapshot = true; - break; - case ConfigWizard::RR_DATA_INCOMPAT: - // In this case snapshot has already been taken by - // PresetUpdater with the appropriate reason - snapshot = false; - break; - case ConfigWizard::RR_USER: - snapshot = page_welcome->reset_user_profile(); - snapshot_reason = Snapshot::SNAPSHOT_USER; - break; - } - - if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Do you want to continue changing the configuration?"))) - return false; - - if (check_unsaved_preset_changes && - !wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - - if (install_bundles.size() > 0) { - // Install bundles from resources. - // Don't create snapshot - we've already done that above if applicable. - if (! updater->install_bundles_rsrc(std::move(install_bundles), false)) - return false; - } else { - BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources"; - } - - if (page_welcome->reset_user_profile()) { - BOOST_LOG_TRIVIAL(info) << "Resetting user profiles..."; - preset_bundle->reset(true); - } - - std::string preferred_model; - std::string preferred_variant; - auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { - const auto config = enabled_vendors.find(bundle_name); - if (config == enabled_vendors.end()) - return std::string(); - for (const auto& model : bundle.vendor_profile->models) { - if (const auto model_it = config->second.find(model.id); - model_it != config->second.end() && model_it->second.size() > 0 && - preferred_pt == model.technology) { - variant = *model_it->second.begin(); - const auto config_old = enabled_vendors_old.find(bundle_name); - if (config_old == enabled_vendors_old.end()) - return model.id; - const auto model_it_old = config_old->second.find(model.id); - if (model_it_old == config_old->second.end()) - return model.id; - else if (model_it_old->second != model_it->second) { - for (const auto& var : model_it->second) - if (model_it_old->second.find(var) == model_it_old->second.end()) { - variant = var; - return model.id; - } - } - } - } - if (!variant.empty()) - variant.clear(); - return std::string(); - }; - // Prusa printers are considered first, then 3rd party. - if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant); - preferred_model.empty()) { - for (const auto& bundle : bundles) { - if (bundle.second.is_prusa_bundle) { continue; } - if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant); - !preferred_model.empty()) - break; - } - } - - // if unsaved changes was not cheched till this moment - if (!check_unsaved_preset_changes) { - if ((check_unsaved_preset_changes = !preferred_model.empty())) { - header = _L("A new Printer was installed and it will be activated."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) { - header = _L("Some Printers were uninstalled."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - } - - std::string first_added_filament, first_added_sla_material; - auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) { - if (appconfig_new.has_section(section_name)) { - // get first of new added preset names - const std::map& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map(); - first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name)); - } - }; - get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament); - get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material); - - // if unsaved changes was not cheched till this moment - if (!check_unsaved_preset_changes) { - if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) { - header = !first_added_filament.empty() ? - _L("A new filament was installed and it will be activated.") : - _L("A new SLA material was installed and it will be activated."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - else { - auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) { - return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map()) != appconfig_new.get_section(section_name); - }; - bool is_filaments_changed = changed(AppConfig::SECTION_FILAMENTS); - bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS); - if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) { - header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled."); - if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) - return false; - } - } - } - - // apply materials in app_config - for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS}) - app_config->set_section(section_name, appconfig_new.get_section(section_name)); - - app_config->set_vendors(appconfig_new); - - app_config->set("notify_release", page_update->version_check ? "all" : "none"); - app_config->set("preset_update", page_update->preset_update ? "1" : "0"); - app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); - -#ifdef _WIN32 - app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); - app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); -// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); - - if (wxGetApp().is_editor()) { - if (page_files_association->associate_3mf()) - wxGetApp().associate_3mf_files(); - if (page_files_association->associate_stl()) - wxGetApp().associate_stl_files(); - } -// else { -// if (page_files_association->associate_gcode()) -// wxGetApp().associate_gcode_files(); -// } - -#endif // _WIN32 - - page_mode->serialize_mode(app_config); - - if (check_unsaved_preset_changes) - 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 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)) - return false; - - page_firmware->apply_custom_config(*custom_config); - page_bed->apply_custom_config(*custom_config); - page_diams->apply_custom_config(*custom_config); - page_temps->apply_custom_config(*custom_config); - - const std::string profile_name = page_custom->profile_name(); - preset_bundle->load_config_from_wizard(profile_name, *custom_config); - } - - // Update the selections from the compatibilty. - preset_bundle->export_selections(*app_config); - - return true; -} -void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add) -{ - const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla; - - auto update = [this, add](const std::string& s, const std::string& key) { - assert(! s.empty()); - if (add) - appconfig_new.set(s, key, "1"); - else - appconfig_new.erase(s, key); - }; - - // add or delete presets had a same alias - auto it = aliases.find(alias_key); - if (it != aliases.end()) - for (const std::string& name : it->second) - update(section, name); -} - -bool ConfigWizard::priv::check_fff_selected() -{ - bool ret = page_fff->any_selected(); - for (const auto& printer: pages_3rdparty) - if (printer.second.first) // FFF page - ret |= printer.second.first->any_selected(); - return ret; -} - -bool ConfigWizard::priv::check_sla_selected() -{ - bool ret = page_msla->any_selected(); - for (const auto& printer: pages_3rdparty) - if (printer.second.second) // SLA page - ret |= printer.second.second->any_selected(); - return ret; -} - - -// Public - -ConfigWizard::ConfigWizard(wxWindow *parent) - : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) - , p(new priv(this)) -{ - this->SetFont(wxGetApp().normal_font()); - - p->load_vendors(); - p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ - "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", - })); - - p->index = new ConfigWizardIndex(this); - - auto *vsizer = new wxBoxSizer(wxVERTICAL); - auto *topsizer = new wxBoxSizer(wxHORIZONTAL); - auto* hline = new StaticLine(this); - p->btnsizer = new wxBoxSizer(wxHORIZONTAL); - - // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling. - // Later, we compare that to the size of the current screen and set minimum width based on that (see below). - p->hscroll = new wxScrolledWindow(this); - p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL); - p->hscroll->SetSizer(p->hscroll_sizer); - - topsizer->Add(p->index, 0, wxEXPAND); - topsizer->AddSpacer(INDEX_MARGIN); - topsizer->Add(p->hscroll, 1, wxEXPAND); - - p->btn_sel_all = new wxButton(this, wxID_ANY, _L("Select all standard printers")); - p->btnsizer->Add(p->btn_sel_all); - - p->btn_prev = new wxButton(this, wxID_ANY, _L("< &Back")); - p->btn_next = new wxButton(this, wxID_ANY, _L("&Next >")); - p->btn_finish = new wxButton(this, wxID_APPLY, _L("&Finish")); - p->btn_cancel = new wxButton(this, wxID_CANCEL, _L("Cancel")); // Note: The label needs to be present, otherwise we get accelerator bugs on Mac - p->btnsizer->AddStretchSpacer(); - p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING); - p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING); - p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING); - p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING); - - wxGetApp().UpdateDarkUI(p->btn_sel_all); - wxGetApp().UpdateDarkUI(p->btn_prev); - wxGetApp().UpdateDarkUI(p->btn_next); - wxGetApp().UpdateDarkUI(p->btn_finish); - wxGetApp().UpdateDarkUI(p->btn_cancel); - - const auto prusa_it = p->bundles.find("PrusaResearch"); - wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found"); - const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile; - - p->add_page(p->page_welcome = new PageWelcome(this)); - - - p->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF); - p->only_sla_mode = !p->page_fff->has_printers; - if (!p->only_sla_mode) { - p->add_page(p->page_fff); - p->page_fff->is_primary_printer_page = true; - } - - - p->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA); - p->add_page(p->page_msla); - if (p->only_sla_mode) { - p->page_msla->is_primary_printer_page = true; - } - - if (!p->only_sla_mode) { - // Pages for 3rd party vendors - p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors - p->add_page(p->page_vendors = new PageVendors(this)); - p->add_page(p->page_custom = new PageCustom(this)); - p->custom_printer_selected = p->page_custom->custom_wanted(); - } - - p->any_sla_selected = p->check_sla_selected(); - p->any_fff_selected = ! p->only_sla_mode && p->check_fff_selected(); - - p->update_materials(T_ANY); - if (!p->only_sla_mode) - p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments, - _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") )); - - p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials, - _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") )); - - - p->add_page(p->page_update = new PageUpdate(this)); - p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); -#ifdef _WIN32 - p->add_page(p->page_files_association = new PageFilesAssociation(this)); -#endif // _WIN32 - 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_diams = new PageDiameters(this)); - p->add_page(p->page_temps = new PageTemperatures(this)); - - p->load_pages(); - p->index->go_to(size_t{0}); - - vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); - vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING); - vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN); - - SetSizer(vsizer); - SetSizerAndFit(vsizer); - - // We can now enable scrolling on hscroll - p->hscroll->SetScrollRate(30, 30); - - on_window_geometry(this, [this]() { - p->init_dialog_size(); - }); - - p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); }); - - p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) - { - // check, that there is selected at least one filament/material - ConfigWizardPage* active_page = this->p->index->active_page(); - if (// Leaving the filaments or SLA materials page and - (active_page == p->page_filaments || active_page == p->page_sla_materials) && - // some Printer models had no filament or SLA material selected. - ! p->check_and_install_missing_materials(dynamic_cast(active_page)->materials->technology)) - // In that case don't leave the page and the function above queried the user whether to install default materials. - return; - this->p->index->go_next(); - }); - - p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) - { - if (p->on_bnt_finish()) - this->EndModal(wxID_OK); - }); - - p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { - p->any_sla_selected = true; - p->load_pages(); - p->page_fff->select_all(true, false); - p->page_msla->select_all(true, false); - p->index->go_to(p->page_mode); - }); - - p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) { - const bool is_last = p->index->active_is_last(); - p->btn_next->Show(! is_last); - if (is_last) - p->btn_finish->SetFocus(); - - Layout(); - }); - - if (wxLinux_gtk3) - this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) { - ConfigWizardPage* active_page = p->index->active_page(); - if (!active_page) - return; - for (auto page : p->all_pages) - if (page != active_page) - page->Hide(); - // update best size for the dialog after hiding of the non-active pages - vsizer->SetSizeHints(this); - // set initial dialog size - p->init_dialog_size(); - }); -} - -ConfigWizard::~ConfigWizard() {} - -bool ConfigWizard::run(RunReason reason, StartPage start_page) -{ - BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page; - - GUI_App &app = wxGetApp(); - - p->set_run_reason(reason); - p->set_start_page(start_page); - - if (ShowModal() == wxID_OK) { - bool apply_keeped_changes = false; - if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes)) - return false; - - if (apply_keeped_changes) - app.apply_keeped_preset_modifications(); - - app.app_config->set_legacy_datadir(false); - app.update_mode(); - app.obj_manipul()->update_ui_from_settings(); - BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied"; - return true; - } else { - BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled"; - return false; - } -} - -const wxString& ConfigWizard::name(const bool from_menu/* = false*/) -{ - // A different naming convention is used for the Wizard on Windows & GTK vs. OSX. - // Note: Don't call _() macro here. - // This function just return the current name according to the OS. - // Translation is implemented inside GUI_App::add_config_menu() -#if __APPLE__ - static const wxString config_wizard_name = L("Configuration Assistant"); - static const wxString config_wizard_name_menu = L("Configuration &Assistant"); -#else - static const wxString config_wizard_name = L("Configuration Wizard"); - static const wxString config_wizard_name_menu = L("Configuration &Wizard"); -#endif - return from_menu ? config_wizard_name_menu : config_wizard_name; -} - -void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect) -{ - p->index->msw_rescale(); - - const int em = em_unit(); - - msw_buttons_rescale(this, em, { wxID_APPLY, - wxID_CANCEL, - p->btn_sel_all->GetId(), - p->btn_next->GetId(), - p->btn_prev->GetId() }); - - for (auto printer_picker: p->page_fff->printer_pickers) - msw_buttons_rescale(this, em, printer_picker->get_button_indexes()); - - p->init_dialog_size(); - - Refresh(); -} - -void ConfigWizard::on_sys_color_changed() -{ - wxGetApp().UpdateDlgDarkUI(this); - Refresh(); -} - -} -} +// FIXME: extract absolute units -> em + +#include "ConfigWizard_private.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSW_DARK_MODE +#include +#endif // _MSW_DARK_MODE + +#include "libslic3r/Platform.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Model.hpp" +#include "libslic3r/Color.hpp" +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "GUI_Utils.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "Field.hpp" +#include "DesktopIntegrationDialog.hpp" +#include "slic3r/Config/Snapshot.hpp" +#include "slic3r/Utils/PresetUpdater.hpp" +#include "format.hpp" +#include "MsgDialog.hpp" +#include "UnsavedChangesDialog.hpp" + +#if defined(__linux__) && defined(__WXGTK3__) +#define wxLinux_gtk3 true +#else +#define wxLinux_gtk3 false +#endif //defined(__linux__) && defined(__WXGTK3__) + +namespace Slic3r { +namespace GUI { + + +using Config::Snapshot; +using Config::SnapshotDB; + + +// Configuration data structures extensions needed for the wizard + +bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle) +{ + this->preset_bundle = std::make_unique(); + this->is_in_resources = ais_in_resources; + this->is_prusa_bundle = ais_prusa_bundle; + + std::string path_string = source_path.string(); + // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air. + auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle( + path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable); + UNUSED(config_substitutions); + // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed. + assert(config_substitutions.empty()); + auto first_vendor = preset_bundle->vendors.begin(); + if (first_vendor == preset_bundle->vendors.end()) { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string; + return false; + } + if (presets_loaded == 0) { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No profile loaded.") % path_string; + return false; + } + + BOOST_LOG_TRIVIAL(trace) << boost::format("Vendor bundle: `%1%`: %2% profiles loaded.") % path_string % presets_loaded; + this->vendor_profile = &first_vendor->second; + return true; +} + +Bundle::Bundle(Bundle &&other) + : preset_bundle(std::move(other.preset_bundle)) + , vendor_profile(other.vendor_profile) + , is_in_resources(other.is_in_resources) + , is_prusa_bundle(other.is_prusa_bundle) +{ + other.vendor_profile = nullptr; +} + +BundleMap BundleMap::load() +{ + BundleMap res; + + const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred(); + const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); + + auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + auto prusa_bundle_rsrc = false; + if (! boost::filesystem::exists(prusa_bundle_path)) { + prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + prusa_bundle_rsrc = true; + } + { + Bundle prusa_bundle; + if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true)) + res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle)); + } + + // Load the other bundles in the datadir/vendor directory + // and then additionally from resources/profiles. + bool is_in_resources = false; + for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) { + for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) { + if (Slic3r::is_ini_file(dir_entry)) { + std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part + + // Don't load this bundle if we've already loaded it. + if (res.find(id) != res.end()) { continue; } + + Bundle bundle; + if (bundle.load(dir_entry.path(), is_in_resources)) + res.emplace(std::move(id), std::move(bundle)); + } + } + + is_in_resources = true; + } + + return res; +} + +Bundle& BundleMap::prusa_bundle() +{ + auto it = find(PresetBundle::PRUSA_BUNDLE); + if (it == end()) { + throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); + } + + return it->second; +} + +const Bundle& BundleMap::prusa_bundle() const +{ + return const_cast(this)->prusa_bundle(); +} + + +// Printer model picker GUI control + +struct PrinterPickerEvent : public wxEvent +{ + std::string vendor_id; + std::string model_id; + std::string variant_name; + bool enable; + + PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) + : wxEvent(winid, eventType) + , vendor_id(std::move(vendor_id)) + , model_id(std::move(model_id)) + , variant_name(std::move(variant_name)) + , enable(enable) + {} + + virtual wxEvent *Clone() const + { + return new PrinterPickerEvent(*this); + } +}; + +wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent); + +const std::string PrinterPicker::PRINTER_PLACEHOLDER = "printer_placeholder.png"; + +PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter) + : wxPanel(parent) + , vendor_id(vendor.id) + , width(0) +{ + wxGetApp().UpdateDarkUI(this); + const auto &models = vendor.models; + + auto *sizer = new wxBoxSizer(wxVERTICAL); + + const auto font_title = GetFont().MakeBold().Scaled(1.3f); + const auto font_name = GetFont().MakeBold(); + const auto font_alt_nozzle = GetFont().Scaled(0.9f); + + // wxGrid appends widgets by rows, but we need to construct them in columns. + // These vectors are used to hold the elements so that they can be appended in the right order. + std::vector titles; + std::vector bitmaps; + std::vector variants_panels; + + int max_row_width = 0; + int current_row_width = 0; + + bool is_variants = false; + + for (const auto &model : models) { + if (! filter(model)) { continue; } + + wxBitmap bitmap; + int bitmap_width = 0; + auto load_bitmap = [](const wxString& bitmap_file, wxBitmap& bitmap, int& bitmap_width)->bool { + if (wxFileExists(bitmap_file)) { + bitmap.LoadFile(bitmap_file, wxBITMAP_TYPE_PNG); + bitmap_width = bitmap.GetWidth(); + return true; + } + return false; + }; + if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { + if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { + BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") + % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png") + % vendor.id + % model.id; + load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); + } + } + auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + title->SetFont(font_name); + const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width); + title->Wrap(wrap_width); + + current_row_width += wrap_width; + if (titles.size() % max_cols == max_cols - 1) { + max_row_width = std::max(max_row_width, current_row_width); + current_row_width = 0; + } + + titles.push_back(title); + + auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap); + bitmaps.push_back(bitmap_widget); + + auto *variants_panel = new wxPanel(this); + wxGetApp().UpdateDarkUI(variants_panel); + auto *variants_sizer = new wxBoxSizer(wxVERTICAL); + variants_panel->SetSizer(variants_sizer); + const auto model_id = model.id; + + for (size_t i = 0; i < model.variants.size(); i++) { + const auto &variant = model.variants[i]; + + const auto label = model.technology == ptFFF + ? from_u8((boost::format("%1% %2% %3%") % variant.name % _utf8(L("mm")) % _utf8(L("nozzle"))).str()) + : from_u8(model.name); + + if (i == 1) { + auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _L("Alternate nozzles:")); + alt_label->SetFont(font_alt_nozzle); + variants_sizer->Add(alt_label, 0, wxBOTTOM, 3); + is_variants = true; + } + + auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name); + i == 0 ? cboxes.push_back(cbox) : cboxes_alt.push_back(cbox); + + const bool enabled = appconfig.get_variant(vendor.id, model_id, variant.name); + cbox->SetValue(enabled); + + variants_sizer->Add(cbox, 0, wxBOTTOM, 3); + + cbox->Bind(wxEVT_CHECKBOX, [this, cbox](wxCommandEvent &event) { + on_checkbox(cbox, event.IsChecked()); + }); + } + + variants_panels.push_back(variants_panel); + } + + width = std::max(max_row_width, current_row_width); + + const size_t cols = std::min(max_cols, titles.size()); + + auto *printer_grid = new wxFlexGridSizer(cols, 0, 20); + printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL); + + if (titles.size() > 0) { + const size_t odd_items = titles.size() % cols; + + for (size_t i = 0; i < titles.size() - odd_items; i += cols) { + for (size_t j = i; j < i + cols; j++) { printer_grid->Add(bitmaps[j], 0, wxBOTTOM, 20); } + for (size_t j = i; j < i + cols; j++) { printer_grid->Add(titles[j], 0, wxBOTTOM, 3); } + for (size_t j = i; j < i + cols; j++) { printer_grid->Add(variants_panels[j]); } + + // Add separator space to multiliners + if (titles.size() > cols) { + for (size_t j = i; j < i + cols; j++) { printer_grid->Add(1, 30); } + } + } + if (odd_items > 0) { + const size_t rem = titles.size() - odd_items; + + for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(bitmaps[i], 0, wxBOTTOM, 20); } + for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } + for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(titles[i], 0, wxBOTTOM, 3); } + for (size_t i = 0; i < cols - odd_items; i++) { printer_grid->AddSpacer(1); } + for (size_t i = rem; i < titles.size(); i++) { printer_grid->Add(variants_panels[i]); } + } + } + + auto *title_sizer = new wxBoxSizer(wxHORIZONTAL); + if (! title.IsEmpty()) { + auto *title_widget = new wxStaticText(this, wxID_ANY, title); + title_widget->SetFont(font_title); + title_sizer->Add(title_widget); + } + title_sizer->AddStretchSpacer(); + + if (titles.size() > 1 || is_variants) { + // It only makes sense to add the All / None buttons if there's multiple printers + // All Standard button is added when there are more variants for at least one printer + auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _L("All standard") : _L("Standard")); + auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); + auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); + if (is_variants) + sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent& event) { this->select_all(true, false); }); + sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, true); }); + sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); }); + if (is_variants) + title_sizer->Add(sel_all_std, 0, wxRIGHT, BTN_SPACING); + title_sizer->Add(sel_all, 0, wxRIGHT, BTN_SPACING); + title_sizer->Add(sel_none); + + wxGetApp().UpdateDarkUI(sel_all_std); + wxGetApp().UpdateDarkUI(sel_all); + wxGetApp().UpdateDarkUI(sel_none); + + // fill button indexes used later for buttons rescaling + if (is_variants) + m_button_indexes = { sel_all_std->GetId(), sel_all->GetId(), sel_none->GetId() }; + else { + sel_all_std->Destroy(); + m_button_indexes = { sel_all->GetId(), sel_none->GetId() }; + } + } + + sizer->Add(title_sizer, 0, wxEXPAND | wxBOTTOM, BTN_SPACING); + sizer->Add(printer_grid); + + SetSizer(sizer); +} + +PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig) + : PrinterPicker(parent, vendor, std::move(title), max_cols, appconfig, [](const VendorProfile::PrinterModel&) { return true; }) +{} + +void PrinterPicker::select_all(bool select, bool alternates) +{ + for (const auto &cb : cboxes) { + if (cb->GetValue() != select) { + cb->SetValue(select); + on_checkbox(cb, select); + } + } + + if (! select) { alternates = false; } + + for (const auto &cb : cboxes_alt) { + if (cb->GetValue() != alternates) { + cb->SetValue(alternates); + on_checkbox(cb, alternates); + } + } +} + +void PrinterPicker::select_one(size_t i, bool select) +{ + if (i < cboxes.size() && cboxes[i]->GetValue() != select) { + cboxes[i]->SetValue(select); + on_checkbox(cboxes[i], select); + } +} + +bool PrinterPicker::any_selected() const +{ + for (const auto &cb : cboxes) { + if (cb->GetValue()) { return true; } + } + + for (const auto &cb : cboxes_alt) { + if (cb->GetValue()) { return true; } + } + + return false; +} + +std::set PrinterPicker::get_selected_models() const +{ + std::set ret_set; + + for (const auto& cb : cboxes) + if (cb->GetValue()) + ret_set.emplace(cb->model); + + for (const auto& cb : cboxes_alt) + if (cb->GetValue()) + ret_set.emplace(cb->model); + + return ret_set; +} + +void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) +{ + PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked); + AddPendingEvent(evt); +} + + +// Wizard page base + +ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent) + : wxPanel(parent->p->hscroll) + , parent(parent) + , shortname(std::move(shortname)) + , indent(indent) +{ + wxGetApp().UpdateDarkUI(this); + + auto *sizer = new wxBoxSizer(wxVERTICAL); + + auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + const auto font = GetFont().MakeBold().Scaled(1.5); + text->SetFont(font); + sizer->Add(text, 0, wxALIGN_LEFT, 0); + sizer->AddSpacer(10); + + content = new wxBoxSizer(wxVERTICAL); + sizer->Add(content, 1, wxEXPAND); + + SetSizer(sizer); + + // There is strange layout on Linux with GTK3, + // see https://github.com/prusa3d/PrusaSlicer/issues/5103 and https://github.com/prusa3d/PrusaSlicer/issues/4861 + // So, non-active pages will be hidden later, on wxEVT_SHOW, after completed Layout() for all pages + if (!wxLinux_gtk3) + this->Hide(); + + Bind(wxEVT_SIZE, [this](wxSizeEvent &event) { + this->Layout(); + event.Skip(); + }); +} + +ConfigWizardPage::~ConfigWizardPage() {} + +wxStaticText* ConfigWizardPage::append_text(wxString text) +{ + auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + widget->Wrap(WRAP_WIDTH); + widget->SetMinSize(wxSize(WRAP_WIDTH, -1)); + append(widget); + return widget; +} + +void ConfigWizardPage::append_spacer(int space) +{ + // FIXME: scaling + content->AddSpacer(space); +} + +// Wizard pages + +PageWelcome::PageWelcome(ConfigWizard *parent) + : ConfigWizardPage(parent, from_u8((boost::format( +#ifdef __APPLE__ + _utf8(L("Welcome to the %s Configuration Assistant")) +#else + _utf8(L("Welcome to the %s Configuration Wizard")) +#endif + ) % SLIC3R_APP_NAME).str()), _L("Welcome")) + , welcome_text(append_text(from_u8((boost::format( + _utf8(L("Hello, welcome to %s! This %s helps you with the initial configuration; just a few settings and you will be ready to print."))) + % SLIC3R_APP_NAME + % _utf8(ConfigWizard::name())).str()) + )) + , cbox_reset(append( + new wxCheckBox(this, wxID_ANY, _L("Remove user profiles (a snapshot will be taken beforehand)")) + )) + , cbox_integrate(append( + new wxCheckBox(this, wxID_ANY, _L("Perform desktop integration (Sets this binary to be searchable by the system).")) + )) +{ + welcome_text->Hide(); + cbox_reset->Hide(); + cbox_integrate->Hide(); +} + +void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason) +{ + const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY; + welcome_text->Show(data_empty); + cbox_reset->Show(!data_empty); +#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) + if (!DesktopIntegrationDialog::is_integrated()) + cbox_integrate->Show(true); + else + cbox_integrate->Hide(); +#else + cbox_integrate->Hide(); +#endif +} + + +PagePrinters::PagePrinters(ConfigWizard *parent, + wxString title, + wxString shortname, + const VendorProfile &vendor, + unsigned indent, + Technology technology) + : ConfigWizardPage(parent, std::move(title), std::move(shortname), indent) + , technology(technology) + , install(false) // only used for 3rd party vendors +{ + enum { + COL_SIZE = 200, + }; + + AppConfig *appconfig = &this->wizard_p()->appconfig_new; + + const auto families = vendor.families(); + for (const auto &family : families) { + const auto filter = [&](const VendorProfile::PrinterModel &model) { + return ((model.technology == ptFFF && technology & T_FFF) + || (model.technology == ptSLA && technology & T_SLA)) + && model.family == family; + }; + + if (std::find_if(vendor.models.begin(), vendor.models.end(), filter) == vendor.models.end()) { + continue; + } + + const auto picker_title = family.empty() ? wxString() : from_u8((boost::format(_utf8(L("%s Family"))) % family).str()); + auto *picker = new PrinterPicker(this, vendor, picker_title, MAX_COLS, *appconfig, filter); + + picker->Bind(EVT_PRINTER_PICK, [this, appconfig](const PrinterPickerEvent &evt) { + appconfig->set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable); + wizard_p()->on_printer_pick(this, evt); + }); + + append(new StaticLine(this)); + + append(picker); + printer_pickers.push_back(picker); + has_printers = true; + } + +} + +void PagePrinters::select_all(bool select, bool alternates) +{ + for (auto picker : printer_pickers) { + picker->select_all(select, alternates); + } +} + +int PagePrinters::get_width() const +{ + return std::accumulate(printer_pickers.begin(), printer_pickers.end(), 0, + [](int acc, const PrinterPicker *picker) { return std::max(acc, picker->get_width()); }); +} + +bool PagePrinters::any_selected() const +{ + for (const auto *picker : printer_pickers) { + if (picker->any_selected()) { return true; } + } + + return false; +} + +std::set PagePrinters::get_selected_models() +{ + std::set ret_set; + + for (const auto *picker : printer_pickers) + { + std::set tmp_models = picker->get_selected_models(); + ret_set.insert(tmp_models.begin(), tmp_models.end()); + } + + return ret_set; +} + +void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason) +{ + if (is_primary_printer_page + && (run_reason == ConfigWizard::RR_DATA_EMPTY || run_reason == ConfigWizard::RR_DATA_LEGACY) + && printer_pickers.size() > 0 + && printer_pickers[0]->vendor_id == PresetBundle::PRUSA_BUNDLE) { + printer_pickers[0]->select_one(0, true); + } +} + + +const std::string PageMaterials::EMPTY; + +PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name) + : ConfigWizardPage(parent, std::move(title), std::move(shortname)) + , materials(materials) + , list_printer(new StringList(this, wxLB_MULTIPLE)) + , list_type(new StringList(this)) + , list_vendor(new StringList(this)) + , list_profile(new PresetList(this)) +{ + append_spacer(VERTICAL_SPACING); + + const int em = parent->em_unit(); + const int list_h = 30*em; + + + list_printer->SetMinSize(wxSize(23*em, list_h)); + list_type->SetMinSize(wxSize(13*em, list_h)); + list_vendor->SetMinSize(wxSize(13*em, list_h)); + list_profile->SetMinSize(wxSize(23*em, list_h)); + + + + grid = new wxFlexGridSizer(4, em/2, em); + grid->AddGrowableCol(3, 1); + grid->AddGrowableRow(1, 1); + + grid->Add(new wxStaticText(this, wxID_ANY, _L("Printer:"))); + grid->Add(new wxStaticText(this, wxID_ANY, list1name)); + grid->Add(new wxStaticText(this, wxID_ANY, _L("Vendor:"))); + grid->Add(new wxStaticText(this, wxID_ANY, _L("Profile:"))); + + grid->Add(list_printer, 0, wxEXPAND); + grid->Add(list_type, 0, wxEXPAND); + grid->Add(list_vendor, 0, wxEXPAND); + grid->Add(list_profile, 1, wxEXPAND); + + auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL); + auto *sel_all = new wxButton(this, wxID_ANY, _L("All")); + auto *sel_none = new wxButton(this, wxID_ANY, _L("None")); + btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2); + btn_sizer->Add(sel_none); + + wxGetApp().UpdateDarkUI(list_printer); + wxGetApp().UpdateDarkUI(list_type); + wxGetApp().UpdateDarkUI(list_vendor); + wxGetApp().UpdateDarkUI(sel_all); + wxGetApp().UpdateDarkUI(sel_none); + + grid->Add(new wxBoxSizer(wxHORIZONTAL)); + grid->Add(new wxBoxSizer(wxHORIZONTAL)); + grid->Add(new wxBoxSizer(wxHORIZONTAL)); + grid->Add(btn_sizer, 0, wxALIGN_RIGHT); + + append(grid, 1, wxEXPAND); + + append_spacer(VERTICAL_SPACING); + + html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, + wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO); + append(html_window, 0, wxEXPAND); + + list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { + update_lists(list_type->GetSelection(), list_vendor->GetSelection(), evt.GetInt()); + }); + list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { + update_lists(list_type->GetSelection(), list_vendor->GetSelection()); + }); + list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) { + update_lists(list_type->GetSelection(), list_vendor->GetSelection()); + }); + + list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); }); + list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); }); + + sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); }); + sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); }); + /* + Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();}); + + list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); }); + list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); }); + list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); }); + */ + reload_presets(); + set_compatible_printers_html_window(std::vector(), false); +} +void PageMaterials::on_paint() +{ +} +void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt) +{ + const wxClientDC dc(list_profile); + const wxPoint pos = evt.GetLogicalPosition(dc); + int item = list_profile->HitTest(pos); + on_material_hovered(item); +} +void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt) +{} +void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt) +{ + on_material_hovered(-1); +} +void PageMaterials::reload_presets() +{ + clear(); + + list_printer->append(_L("(All)"), &EMPTY); + //list_printer->SetLabelMarkup("bald"); + for (const Preset* printer : materials->printers) { + list_printer->append(printer->name, &printer->name); + } + sort_list_data(list_printer, true, false); + if (list_printer->GetCount() > 0) { + list_printer->SetSelection(0); + sel_printers_prev.Clear(); + sel_type_prev = wxNOT_FOUND; + sel_vendor_prev = wxNOT_FOUND; + update_lists(0, 0, 0); + } + + presets_loaded = true; +} + +void PageMaterials::set_compatible_printers_html_window(const std::vector& printer_names, bool all_printers) +{ + const auto bgr_clr = +#if defined(__APPLE__) + html_window->GetParent()->GetBackgroundColour(); +#else +#if defined(_WIN32) + wxGetApp().get_window_default_clr(); +#else + wxSystemSettings::GetColour(wxSYS_COLOUR_MENU); +#endif +#endif + const auto text_clr = wxGetApp().get_label_clr_default(); + const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue())); + const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue())); + wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); + wxString text; + if (all_printers) { + wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); + text = wxString::Format( + "" + "" + "" + "" + "" + "%s

%s" + "
" + "
" + "" + "" + , bgr_clr_str + , text_clr_str + , first_line + , second_line + ); + } else { + wxString second_line; + if (!printer_names.empty()) + second_line = (materials->technology == T_FFF ? + _L("Only the following installed printers are compatible with the selected filaments") : + _L("Only the following installed printers are compatible with the selected SLA materials")) + ":"; + text = wxString::Format( + "" + "" + "" + "" + "" + "%s

%s" + "" + "" + , bgr_clr_str + , text_clr_str + , first_line + , second_line); + for (size_t i = 0; i < printer_names.size(); ++i) + { + text += wxString::Format("", boost::nowide::widen(printer_names[i])); + if (i % 3 == 2) { + text += wxString::Format( + "" + ""); + } + } + text += wxString::Format( + "" + "
%s
" + "
" + "
" + "" + "" + ); + } + + wxFont font = get_default_font_for_dpi(this, get_dpi_for_window(this)); + const int fs = font.GetPointSize(); + int size[] = { fs,fs,fs,fs,fs,fs,fs }; + html_window->SetFonts(font.GetFaceName(), font.GetFaceName(), size); + html_window->SetPage(text); +} + +void PageMaterials::clear_compatible_printers_label() +{ + set_compatible_printers_html_window(std::vector(), false); +} + +void PageMaterials::on_material_hovered(int sel_material) +{ + +} + +void PageMaterials::on_material_highlighted(int sel_material) +{ + if (sel_material == last_hovered_item) + return; + if (sel_material == -1) { + clear_compatible_printers_label(); + return; + } + last_hovered_item = sel_material; + std::vector tabs; + tabs.push_back(std::string()); + tabs.push_back(std::string()); + tabs.push_back(std::string()); + //selected material string + std::string material_name = list_profile->get_data(sel_material); + // get material preset + const std::vector matching_materials = materials->get_presets_by_alias(material_name); + if (matching_materials.empty()) + { + clear_compatible_printers_label(); + return; + } + //find matching printers + std::vector names; + for (const Preset* printer : materials->printers) { + for (const Preset* material : matching_materials) { + if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) { + names.push_back(printer->name); + break; + } + } + } + set_compatible_printers_html_window(names, names.size() == materials->printers.size()); +} + +void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected_printer/* = -1*/) +{ + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; + + wxArrayInt sel_printers; + int sel_printers_count = list_printer->GetSelections(sel_printers); + + // Does our wxWidgets version support operator== for wxArrayInt ? + // https://github.com/prusa3d/PrusaSlicer/issues/5152#issuecomment-787208614 +#if wxCHECK_VERSION(3, 1, 1) + if (sel_printers != sel_printers_prev) { +#else + auto are_equal = [](const wxArrayInt& arr_first, const wxArrayInt& arr_second) { + if (arr_first.GetCount() != arr_second.GetCount()) + return false; + for (size_t i = 0; i < arr_first.GetCount(); i++) + if (arr_first[i] != arr_second[i]) + return false; + return true; + }; + if (!are_equal(sel_printers, sel_printers_prev)) { +#endif + + // Refresh type list + list_type->Clear(); + list_type->append(_L("(All)"), &EMPTY); + if (sel_printers_count > 0) { + // If all is selected with other printers + // unselect "all" or all printers depending on last value + if (sel_printers[0] == 0 && sel_printers_count > 1) { + if (last_selected_printer == 0) { + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(0); + } else { + list_printer->SetSelection(0, false); + sel_printers_count = list_printer->GetSelections(sel_printers); + } + } + if (sel_printers[0] != 0) { + for (int i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) { + const std::string& type = this->materials->get_type(p); + if (list_type->find(type) == wxNOT_FOUND) { + list_type->append(type, &type); + } + }); + } + } else { + //clear selection except "ALL" + list_printer->SetSelection(wxNOT_FOUND); + list_printer->SetSelection(0); + sel_printers_count = list_printer->GetSelections(sel_printers); + + materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) { + const std::string& type = this->materials->get_type(p); + if (list_type->find(type) == wxNOT_FOUND) { + list_type->append(type, &type); + } + }); + } + sort_list_data(list_type, true, true); + } + + sel_printers_prev = sel_printers; + sel_type = 0; + sel_type_prev = wxNOT_FOUND; + list_type->SetSelection(sel_type); + list_profile->Clear(); + } + + if (sel_type != sel_type_prev) { + // Refresh vendor list + + // XXX: The vendor list is created with quadratic complexity here, + // but the number of vendors is going to be very small this shouldn't be a problem. + + list_vendor->Clear(); + list_vendor->append(_L("(All)"), &EMPTY); + if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) { + const std::string& type = list_type->get_data(sel_type); + // find printer preset + for (int i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) { + const std::string& vendor = this->materials->get_vendor(p); + if (list_vendor->find(vendor) == wxNOT_FOUND) { + list_vendor->append(vendor, &vendor); + } + }); + } + sort_list_data(list_vendor, true, false); + } + + sel_type_prev = sel_type; + sel_vendor = 0; + sel_vendor_prev = wxNOT_FOUND; + list_vendor->SetSelection(sel_vendor); + list_profile->Clear(); + } + + if (sel_vendor != sel_vendor_prev) { + // Refresh material list + + list_profile->Clear(); + clear_compatible_printers_label(); + if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) { + const std::string& type = list_type->get_data(sel_type); + const std::string& vendor = list_vendor->get_data(sel_vendor); + // finst printer preset + std::vector to_list; + for (int i = 0; i < sel_printers_count; i++) { + const std::string& printer_name = list_printer->get_data(sel_printers[i]); + const Preset* printer = nullptr; + for (const Preset* it : materials->printers) { + if (it->name == printer_name) { + printer = it; + break; + } + } + + materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) { + const std::string& section = materials->appconfig_section(); + bool checked = wizard_p()->appconfig_new.has(section, p->name); + bool was_checked = false; + + int cur_i = list_profile->find(p->alias); + if (cur_i == wxNOT_FOUND) { + cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias); + to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked); + } + else { + was_checked = list_profile->IsChecked(cur_i); + to_list[cur_i].checked = checked || was_checked; + } + list_profile->Check(cur_i, checked || was_checked); + + /* Update preset selection in config. + * If one preset from aliases bundle is selected, + * than mark all presets with this aliases as selected + * */ + if (checked && !was_checked) + wizard_p()->update_presets_in_config(section, p->alias, true); + else if (!checked && was_checked) + wizard_p()->appconfig_new.set(section, p->name, "1"); + }); + } + sort_list_data(list_profile, to_list); + } + + sel_vendor_prev = sel_vendor; + } + wxGetApp().UpdateDarkUI(list_profile); +} + +void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering) +{ +// get data from list +// sort data +// first should be +// then prusa profiles +// then the rest +// in alphabetical order + + std::vector> prusa_profiles; + std::vector> other_profiles; + for (int i = 0 ; i < list->size(); ++i) { + const std::string& data = list->get_data(i); + if (data == EMPTY) // do not sort item + continue; + if (!material_type_ordering && data.find("Prusa") != std::string::npos) + prusa_profiles.push_back(data); + else + other_profiles.push_back(data); + } + if(material_type_ordering) { + + const ConfigOptionDef* def = print_config_def.get("filament_type"); + std::vectorenum_values = def->enum_values; + size_t end_of_sorted = 0; + for (size_t vals = 0; vals < enum_values.size(); vals++) { + for (size_t profs = end_of_sorted; profs < other_profiles.size(); profs++) + { + // find instead compare because PET vs PETG + if (other_profiles[profs].get().find(enum_values[vals]) != std::string::npos) { + //swap + if(profs != end_of_sorted) { + std::reference_wrapper aux = other_profiles[end_of_sorted]; + other_profiles[end_of_sorted] = other_profiles[profs]; + other_profiles[profs] = aux; + } + end_of_sorted++; + break; + } + } + } + } else { + std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { + return a.get() < b.get(); + }); + std::sort(other_profiles.begin(), other_profiles.end(), [](std::reference_wrapper a, std::reference_wrapper b) { + return a.get() < b.get(); + }); + } + + list->Clear(); + if (add_All_item) + list->append(_L("(All)"), &EMPTY); + for (const auto& item : prusa_profiles) + list->append(item, &const_cast(item.get())); + for (const auto& item : other_profiles) + list->append(item, &const_cast(item.get())); +} + +void PageMaterials::sort_list_data(PresetList* list, const std::vector& data) +{ + // sort data + // then prusa profiles + // then the rest + // in alphabetical order + std::vector prusa_profiles; + std::vector other_profiles; + //for (int i = 0; i < data.size(); ++i) { + for (const auto& item : data) { + const std::string& name = item.name; + if (name.find("Prusa") != std::string::npos) + prusa_profiles.emplace_back(item); + else + other_profiles.emplace_back(item); + } + std::sort(prusa_profiles.begin(), prusa_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { + return a.name.get() < b.name.get(); + }); + std::sort(other_profiles.begin(), other_profiles.end(), [](ProfilePrintData a, ProfilePrintData b) { + return a.name.get() < b.name.get(); + }); + list->Clear(); + for (size_t i = 0; i < prusa_profiles.size(); ++i) { + list->append(std::string(prusa_profiles[i].name) + (prusa_profiles[i].omnipresent ? "" : " *"), &const_cast(prusa_profiles[i].name.get())); + list->Check(i, prusa_profiles[i].checked); + } + for (size_t i = 0; i < other_profiles.size(); ++i) { + list->append(std::string(other_profiles[i].name) + (other_profiles[i].omnipresent ? "" : " *"), &const_cast(other_profiles[i].name.get())); + list->Check(i + prusa_profiles.size(), other_profiles[i].checked); + } +} + +void PageMaterials::select_material(int i) +{ + const bool checked = list_profile->IsChecked(i); + + const std::string& alias_key = list_profile->get_data(i); + wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked); +} + +void PageMaterials::select_all(bool select) +{ + wxWindowUpdateLocker freeze_guard(this); + (void)freeze_guard; + + for (unsigned i = 0; i < list_profile->GetCount(); i++) { + const bool current = list_profile->IsChecked(i); + if (current != select) { + list_profile->Check(i, select); + select_material(i); + } + } +} + +void PageMaterials::clear() +{ + list_printer->Clear(); + list_type->Clear(); + list_vendor->Clear(); + list_profile->Clear(); + sel_printers_prev.Clear(); + sel_type_prev = wxNOT_FOUND; + sel_vendor_prev = wxNOT_FOUND; + presets_loaded = false; +} + +void PageMaterials::on_activate() +{ + if (! presets_loaded) { + wizard_p()->update_materials(materials->technology); + reload_presets(); + } + first_paint = true; +} + + +const char *PageCustom::default_profile_name = "My Settings"; + +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); + + 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()); + wizard_p()->on_custom_setup(custom_wanted()); + + }); + + append(cb_custom); + append(label); + append(tc_profile_name); +} + +PageUpdate::PageUpdate(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Automatic updates"), _L("Updates")) + , version_check(true) + , preset_update(true) +{ + const AppConfig *app_config = wxGetApp().app_config; + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _L("Check for application updates")); + box_slic3r->SetValue(app_config->get("notify_release") != "none"); + append(box_slic3r); + append_text(wxString::Format(_L( + "If enabled, %s checks for new application versions online. When a new version becomes available, " + "a notification is displayed at the next application startup (never during program usage). " + "This is only a notification mechanisms, no automatic installation is done."), SLIC3R_APP_NAME)); + + append_spacer(VERTICAL_SPACING); + + auto *box_presets = new wxCheckBox(this, wxID_ANY, _L("Update built-in Presets automatically")); + box_presets->SetValue(app_config->get("preset_update") == "1"); + append(box_presets); + append_text(wxString::Format(_L( + "If enabled, %s downloads updates of built-in system presets in the background." + "These updates are downloaded into a separate temporary location." + "When a new preset version becomes available it is offered at application startup."), SLIC3R_APP_NAME)); + const auto text_bold = _L("Updates are never applied without user's consent and never overwrite user's customized settings."); + auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold); + label_bold->SetFont(boldfont); + label_bold->Wrap(WRAP_WIDTH); + append(label_bold); + append_text(_L("Additionally a backup snapshot of the whole configuration is created before an update is applied.")); + + box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); }); + box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); }); +} + +PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent) + : ConfigWizardPage(parent, _L("Reload from disk"), _L("Reload from disk")) + , full_pathnames(false) +{ + auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _L("Export full pathnames of models and parts sources into 3mf and amf files")); + box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1"); + append(box_pathnames); + append_text(_L( + "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n" + "If not enabled, the Reload from disk command will ask to select each file using an open file dialog." + )); + + box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); }); +} + +#ifdef _WIN32 +PageFilesAssociation::PageFilesAssociation(ConfigWizard* parent) + : ConfigWizardPage(parent, _L("Files association"), _L("Files association")) +{ + cb_3mf = new wxCheckBox(this, wxID_ANY, _L("Associate .3mf files to PrusaSlicer")); + cb_stl = new wxCheckBox(this, wxID_ANY, _L("Associate .stl files to PrusaSlicer")); +// cb_gcode = new wxCheckBox(this, wxID_ANY, _L("Associate .gcode files to PrusaSlicer G-code Viewer")); + + append(cb_3mf); + append(cb_stl); +// append(cb_gcode); +} +#endif // _WIN32 + +PageMode::PageMode(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("View mode"), _L("View mode")) +{ + append_text(_L("PrusaSlicer's user interfaces comes in three variants:\nSimple, Advanced, and Expert.\n" + "The Simple mode shows only the most frequently used settings relevant for regular 3D printing. " + "The other two offer progressively more sophisticated fine-tuning, " + "they are suitable for advanced and expert users, respectively.")); + + radio_simple = new wxRadioButton(this, wxID_ANY, _L("Simple mode")); + radio_advanced = new wxRadioButton(this, wxID_ANY, _L("Advanced mode")); + radio_expert = new wxRadioButton(this, wxID_ANY, _L("Expert mode")); + + std::string mode { "simple" }; + wxGetApp().app_config->get("", "view_mode", mode); + + if (mode == "advanced") { radio_advanced->SetValue(true); } + else if (mode == "expert") { radio_expert->SetValue(true); } + else { radio_simple->SetValue(true); } + + append(radio_simple); + append(radio_advanced); + append(radio_expert); + + append_text("\n" + _L("The size of the object can be specified in inches")); + check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); + check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1"); + append(check_inch); + + on_activate(); +} + +void PageMode::serialize_mode(AppConfig *app_config) const +{ + std::string mode = ""; + + if (radio_simple->GetValue()) { mode = "simple"; } + if (radio_advanced->GetValue()) { mode = "advanced"; } + if (radio_expert->GetValue()) { mode = "expert"; } + + app_config->set("view_mode", mode); + app_config->set("use_inches", check_inch->GetValue() ? "1" : "0"); +} + +PageVendors::PageVendors(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Other Vendors"), _L("Other Vendors")) +{ + const AppConfig &appconfig = this->wizard_p()->appconfig_new; + + append_text(wxString::Format(_L("Pick another vendor supported by %s"), SLIC3R_APP_NAME) + ":"); + + auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + boldfont.SetWeight(wxFONTWEIGHT_BOLD); + + for (const auto &pair : wizard_p()->bundles) { + const VendorProfile *vendor = pair.second.vendor_profile; + if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } + + auto *cbox = new wxCheckBox(this, wxID_ANY, vendor->name); + cbox->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent &event) { + wizard_p()->on_3rdparty_install(vendor, cbox->IsChecked()); + }); + + const auto &vendors = appconfig.vendors(); + const bool enabled = vendors.find(pair.first) != vendors.end(); + if (enabled) { + cbox->SetValue(true); + + auto pages = wizard_p()->pages_3rdparty.find(vendor->id); + wxCHECK_RET(pages != wizard_p()->pages_3rdparty.end(), "Internal error: 3rd party vendor printers page not created"); + + for (PagePrinters* page : { pages->second.first, pages->second.second }) + if (page) page->install = true; + } + + append(cbox); + } +} + +PageFirmware::PageFirmware(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Firmware Type"), _L("Firmware"), 1) + , gcode_opt(*print_config_def.get("gcode_flavor")) + , gcode_picker(nullptr) +{ + append_text(_L("Choose the type of firmware used by your printer.")); + append_text(_(gcode_opt.tooltip)); + + wxArrayString choices; + choices.Alloc(gcode_opt.enum_labels.size()); + for (const auto &label : gcode_opt.enum_labels) { + choices.Add(label); + } + + gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices); + wxGetApp().UpdateDarkUI(gcode_picker); + const auto &enum_values = gcode_opt.enum_values; + auto needle = enum_values.cend(); + if (gcode_opt.default_value) { + needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize()); + } + if (needle != enum_values.cend()) { + gcode_picker->SetSelection(needle - enum_values.cbegin()); + } else { + gcode_picker->SetSelection(0); + } + + append(gcode_picker); +} + +void PageFirmware::apply_custom_config(DynamicPrintConfig &config) +{ + auto sel = gcode_picker->GetSelection(); + if (sel >= 0 && (size_t)sel < gcode_opt.enum_labels.size()) { + auto *opt = new ConfigOptionEnum(static_cast(sel)); + config.set_key_value("gcode_flavor", opt); + } +} + +PageBedShape::PageBedShape(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Bed Shape and Size"), _L("Bed Shape"), 1) + , shape_panel(new BedShapePanel(this)) +{ + append_text(_L("Set the shape of your printer's bed.")); + + shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"), + *wizard_p()->custom_config->option("bed_custom_texture"), + *wizard_p()->custom_config->option("bed_custom_model")); + + append(shape_panel); +} + +void PageBedShape::apply_custom_config(DynamicPrintConfig &config) +{ + const std::vector& points = shape_panel->get_shape(); + const std::string& custom_texture = shape_panel->get_custom_texture(); + const std::string& custom_model = shape_panel->get_custom_model(); + config.set_key_value("bed_shape", new ConfigOptionPoints(points)); + config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture)); + config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); +} + +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.")); + 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() {} +}; + +PageDiameters::PageDiameters(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1) + , diam_nozzle(new DiamTextCtrl(this)) + , diam_filam (new DiamTextCtrl(this)) +{ + auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value(); + wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); + diam_nozzle->SetValue(value); + + auto *default_filam = print_config_def.get("filament_diameter")->get_default_value(); + value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); + diam_filam->SetValue(value); + + diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId()); + diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId()); + + append_text(_L("Enter the diameter of your printer's hot end nozzle.")); + + auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5); + auto *text_nozzle = new wxStaticText(this, wxID_ANY, _L("Nozzle Diameter:")); + auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm")); + sizer_nozzle->AddGrowableCol(0, 1); + sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL); + sizer_nozzle->Add(diam_nozzle); + sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_nozzle); + + append_spacer(VERTICAL_SPACING); + + append_text(_L("Enter the diameter of your filament.")); + append_text(_L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.")); + + auto *sizer_filam = new wxFlexGridSizer(3, 5, 5); + auto *text_filam = new wxStaticText(this, wxID_ANY, _L("Filament Diameter:")); + auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm")); + sizer_filam->AddGrowableCol(0, 1); + sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL); + sizer_filam->Add(diam_filam); + sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_filam); +} + +void PageDiameters::apply_custom_config(DynamicPrintConfig &config) +{ + double val = 0.0; + diam_nozzle->GetValue().ToDouble(&val); + auto *opt_nozzle = new ConfigOptionFloats(1, val); + config.set_key_value("nozzle_diameter", opt_nozzle); + + val = 0.0; + diam_filam->GetValue().ToDouble(&val); + auto * opt_filam = new ConfigOptionFloats(1, val); + config.set_key_value("filament_diameter", opt_filam); + + auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { + char buf[64]; // locales don't matter here (sprintf/atof) + sprintf(buf, "%.2lf", dmr * opt_nozzle->values.front() / 0.4); + config.set_key_value(key, new ConfigOptionFloatOrPercent(atof(buf), false)); + }; + + set_extrusion_width("support_material_extrusion_width", 0.35); + set_extrusion_width("top_infill_extrusion_width", 0.40); + set_extrusion_width("first_layer_extrusion_width", 0.42); + + set_extrusion_width("extrusion_width", 0.45); + set_extrusion_width("perimeter_extrusion_width", 0.45); + set_extrusion_width("external_perimeter_extrusion_width", 0.45); + set_extrusion_width("infill_extrusion_width", 0.45); + set_extrusion_width("solid_infill_extrusion_width", 0.45); +} + +class SpinCtrlDouble: public wxSpinCtrlDouble +{ +public: + SpinCtrlDouble(wxWindow* parent) + { +#ifdef _WIN32 + long style = wxSP_ARROW_KEYS | wxBORDER_SIMPLE; +#else + long style = wxSP_ARROW_KEYS; +#endif + Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, style); +#ifdef _WIN32 + wxGetApp().UpdateDarkUI(this->GetText()); +#endif + this->Refresh(); + } + ~SpinCtrlDouble() {} +}; + +PageTemperatures::PageTemperatures(ConfigWizard *parent) + : ConfigWizardPage(parent, _L("Nozzle and Bed Temperatures"), _L("Temperatures"), 1) + , spin_extr(new SpinCtrlDouble(this)) + , spin_bed (new SpinCtrlDouble(this)) +{ + spin_extr->SetIncrement(5.0); + const auto &def_extr = *print_config_def.get("temperature"); + spin_extr->SetRange(def_extr.min, def_extr.max); + auto *default_extr = def_extr.get_default_value(); + spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200); + + spin_bed->SetIncrement(5.0); + const auto &def_bed = *print_config_def.get("bed_temperature"); + spin_bed->SetRange(def_bed.min, def_bed.max); + auto *default_bed = def_bed.get_default_value(); + spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0); + + append_text(_L("Enter the temperature needed for extruding your filament.")); + append_text(_L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.")); + + auto *sizer_extr = new wxFlexGridSizer(3, 5, 5); + auto *text_extr = new wxStaticText(this, wxID_ANY, _L("Extrusion Temperature:")); + auto *unit_extr = new wxStaticText(this, wxID_ANY, _L("°C")); + sizer_extr->AddGrowableCol(0, 1); + sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL); + sizer_extr->Add(spin_extr); + sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_extr); + + append_spacer(VERTICAL_SPACING); + + append_text(_L("Enter the bed temperature needed for getting your filament to stick to your heated bed.")); + append_text(_L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.")); + + auto *sizer_bed = new wxFlexGridSizer(3, 5, 5); + auto *text_bed = new wxStaticText(this, wxID_ANY, _L("Bed Temperature:")); + auto *unit_bed = new wxStaticText(this, wxID_ANY, _L("°C")); + sizer_bed->AddGrowableCol(0, 1); + sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL); + sizer_bed->Add(spin_bed); + sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL); + append(sizer_bed); +} + +void PageTemperatures::apply_custom_config(DynamicPrintConfig &config) +{ + auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue()); + config.set_key_value("temperature", opt_extr); + auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue()); + config.set_key_value("first_layer_temperature", opt_extr1st); + auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue()); + config.set_key_value("bed_temperature", opt_bed); + auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue()); + config.set_key_value("first_layer_bed_temperature", opt_bed1st); +} + + +// Index + +ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) + : wxPanel(parent) + , bg(ScalableBitmap(parent, "PrusaSlicer_192px_transparent.png", 192)) + , bullet_black(ScalableBitmap(parent, "bullet_black.png")) + , bullet_blue(ScalableBitmap(parent, "bullet_blue.png")) + , bullet_white(ScalableBitmap(parent, "bullet_white.png")) + , item_active(NO_ITEM) + , item_hover(NO_ITEM) + , last_page((size_t)-1) +{ +#ifndef __WXOSX__ + SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX +#endif //__WXOSX__ + SetMinSize(bg.bmp().GetSize()); + + const wxSize size = GetTextExtent("m"); + em_w = size.x; + em_h = size.y; + + Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this); + Bind(wxEVT_SIZE, [this](wxEvent& e) { e.Skip(); Refresh(); }); + Bind(wxEVT_MOTION, &ConfigWizardIndex::on_mouse_move, this); + + Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent &evt) { + if (item_hover != -1) { + item_hover = -1; + Refresh(); + } + evt.Skip(); + }); + + Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &evt) { + if (item_hover >= 0) { go_to(item_hover); } + }); +} + +wxDECLARE_EVENT(EVT_INDEX_PAGE, wxCommandEvent); + +void ConfigWizardIndex::add_page(ConfigWizardPage *page) +{ + last_page = items.size(); + items.emplace_back(Item { page->shortname, page->indent, page }); + Refresh(); +} + +void ConfigWizardIndex::add_label(wxString label, unsigned indent) +{ + items.emplace_back(Item { std::move(label), indent, nullptr }); + Refresh(); +} + +ConfigWizardPage* ConfigWizardIndex::active_page() const +{ + if (item_active >= items.size()) { return nullptr; } + + return items[item_active].page; +} + +void ConfigWizardIndex::go_prev() +{ + // Search for a preceiding item that is a page (not a label, ie. page != nullptr) + + if (item_active == NO_ITEM) { return; } + + for (size_t i = item_active; i > 0; i--) { + if (items[i - 1].page != nullptr) { + go_to(i - 1); + return; + } + } +} + +void ConfigWizardIndex::go_next() +{ + // Search for a next item that is a page (not a label, ie. page != nullptr) + + if (item_active == NO_ITEM) { return; } + + for (size_t i = item_active + 1; i < items.size(); i++) { + if (items[i].page != nullptr) { + go_to(i); + return; + } + } +} + +// This one actually performs the go-to op +void ConfigWizardIndex::go_to(size_t i) +{ + if (i != item_active + && i < items.size() + && items[i].page != nullptr) { + auto *new_active = items[i].page; + auto *former_active = active_page(); + if (former_active != nullptr) { + former_active->Hide(); + } + + item_active = i; + new_active->Show(); + + wxCommandEvent evt(EVT_INDEX_PAGE, GetId()); + AddPendingEvent(evt); + + Refresh(); + + new_active->on_activate(); + } +} + +void ConfigWizardIndex::go_to(const ConfigWizardPage *page) +{ + if (page == nullptr) { return; } + + for (size_t i = 0; i < items.size(); i++) { + if (items[i].page == page) { + go_to(i); + return; + } + } +} + +void ConfigWizardIndex::clear() +{ + auto *former_active = active_page(); + if (former_active != nullptr) { former_active->Hide(); } + + items.clear(); + item_active = NO_ITEM; +} + +void ConfigWizardIndex::on_paint(wxPaintEvent & evt) +{ + const auto size = GetClientSize(); + if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; } + + wxPaintDC dc(this); + + const auto bullet_w = bullet_black.bmp().GetSize().GetWidth(); + const auto bullet_h = bullet_black.bmp().GetSize().GetHeight(); + const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0; + const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0; + const int yinc = item_height(); + + int index_width = 0; + + unsigned y = 0; + for (size_t i = 0; i < items.size(); i++) { + const Item& item = items[i]; + unsigned x = em_w/2 + item.indent * em_w; + + if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) { + dc.DrawBitmap(bullet_blue.bmp(), x, y + yoff_icon, false); + } + else if (i < item_active) { dc.DrawBitmap(bullet_black.bmp(), x, y + yoff_icon, false); } + else if (i > item_active) { dc.DrawBitmap(bullet_white.bmp(), x, y + yoff_icon, false); } + + x += + bullet_w + em_w/2; + const auto text_size = dc.GetTextExtent(item.label); + dc.SetTextForeground(wxGetApp().get_label_clr_default()); + dc.DrawText(item.label, x, y + yoff_text); + + y += yinc; + index_width = std::max(index_width, (int)x + text_size.x); + } + + //draw logo + if (int y = size.y - bg.GetBmpHeight(); y>=0) { + dc.DrawBitmap(bg.bmp(), 0, y, false); + index_width = std::max(index_width, bg.GetBmpWidth() + em_w / 2); + } + + if (GetMinSize().x < index_width) { + CallAfter([this, index_width]() { + SetMinSize(wxSize(index_width, GetMinSize().y)); + Refresh(); + }); + } +} + +void ConfigWizardIndex::on_mouse_move(wxMouseEvent &evt) +{ + const wxClientDC dc(this); + const wxPoint pos = evt.GetLogicalPosition(dc); + + const ssize_t item_hover_new = pos.y / item_height(); + + if (item_hover_new < ssize_t(items.size()) && item_hover_new != item_hover) { + item_hover = item_hover_new; + Refresh(); + } + + evt.Skip(); +} + +void ConfigWizardIndex::msw_rescale() +{ + const wxSize size = GetTextExtent("m"); + em_w = size.x; + em_h = size.y; + + bg.msw_rescale(); + SetMinSize(bg.bmp().GetSize()); + + bullet_black.msw_rescale(); + bullet_blue.msw_rescale(); + bullet_white.msw_rescale(); + Refresh(); +} + + +// Materials + +const std::string Materials::UNKNOWN = "(Unknown)"; + +void Materials::push(const Preset *preset) +{ + presets.emplace_back(preset); + types.insert(technology & T_FFF + ? Materials::get_filament_type(preset) + : Materials::get_material_type(preset)); +} + +void Materials::add_printer(const Preset* preset) +{ + printers.insert(preset); +} + +void Materials::clear() +{ + presets.clear(); + types.clear(); + printers.clear(); + compatibility_counter.clear(); +} + +const std::string& Materials::appconfig_section() const +{ + return (technology & T_FFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; +} + +const std::string& Materials::get_type(const Preset *preset) const +{ + return (technology & T_FFF) ? get_filament_type(preset) : get_material_type(preset); +} + +const std::string& Materials::get_vendor(const Preset *preset) const +{ + return (technology & T_FFF) ? get_filament_vendor(preset) : get_material_vendor(preset); +} + +const std::string& Materials::get_filament_type(const Preset *preset) +{ + const auto *opt = preset->config.opt("filament_type"); + if (opt != nullptr && opt->values.size() > 0) { + return opt->values[0]; + } else { + return UNKNOWN; + } +} + +const std::string& Materials::get_filament_vendor(const Preset *preset) +{ + const auto *opt = preset->config.opt("filament_vendor"); + return opt != nullptr ? opt->value : UNKNOWN; +} + +const std::string& Materials::get_material_type(const Preset *preset) +{ + const auto *opt = preset->config.opt("material_type"); + if (opt != nullptr) { + return opt->value; + } else { + return UNKNOWN; + } +} + +const std::string& Materials::get_material_vendor(const Preset *preset) +{ + const auto *opt = preset->config.opt("material_vendor"); + return opt != nullptr ? opt->value : UNKNOWN; +} + +// priv + +static const std::unordered_map> legacy_preset_map {{ + { "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") }, + { "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") }, + { "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, + { "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") }, + { "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") }, + { "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") }, + { "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") }, + { "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") }, +}}; + +void ConfigWizard::priv::load_pages() +{ + wxWindowUpdateLocker freeze_guard(q); + (void)freeze_guard; + + const ConfigWizardPage *former_active = index->active_page(); + + index->clear(); + + index->add_page(page_welcome); + + // Printers + if (!only_sla_mode) + index->add_page(page_fff); + index->add_page(page_msla); + if (!only_sla_mode) { + index->add_page(page_vendors); + for (const auto &pages : pages_3rdparty) { + for ( PagePrinters* page : { pages.second.first, pages.second.second }) + if (page && page->install) + index->add_page(page); + } + + index->add_page(page_custom); + if (page_custom->custom_wanted()) { + index->add_page(page_firmware); + index->add_page(page_bed); + index->add_page(page_diams); + index->add_page(page_temps); + } + + // Filaments & Materials + if (any_fff_selected) { index->add_page(page_filaments); } + } + if (any_sla_selected) { index->add_page(page_sla_materials); } + + // there should to be selected at least one printer + btn_finish->Enable(any_fff_selected || any_sla_selected || custom_printer_selected); + + index->add_page(page_update); + index->add_page(page_reload_from_disk); +#ifdef _WIN32 +#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (page_files_association != nullptr) +#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + index->add_page(page_files_association); +#endif // _WIN32 + index->add_page(page_mode); + + index->go_to(former_active); // Will restore the active item/page if possible + + q->Layout(); +// This Refresh() is needed to avoid ugly artifacts after printer selection, when no one vendor was selected from the very beginnig + q->Refresh(); +} + +void ConfigWizard::priv::init_dialog_size() +{ + // Clamp the Wizard size based on screen dimensions + + const auto idx = wxDisplay::GetFromWindow(q); + wxDisplay display(idx != wxNOT_FOUND ? idx : 0u); + + const auto disp_rect = display.GetClientArea(); + wxRect window_rect( + disp_rect.x + disp_rect.width / 20, + disp_rect.y + disp_rect.height / 20, + 9*disp_rect.width / 10, + 9*disp_rect.height / 10); + + const int width_hint = index->GetSize().GetWidth() + std::max(90 * em(), (only_sla_mode ? page_msla->get_width() : page_fff->get_width()) + 30 * em()); // XXX: magic constant, I found no better solution + if (width_hint < window_rect.width) { + window_rect.x += (window_rect.width - width_hint) / 2; + window_rect.width = width_hint; + } + + q->SetSize(window_rect); +} + +void ConfigWizard::priv::load_vendors() +{ + bundles = BundleMap::load(); + + // Load up the set of vendors / models / variants the user has had enabled up till now + AppConfig *app_config = wxGetApp().app_config; + if (! app_config->legacy_datadir()) { + appconfig_new.set_vendors(*app_config); + } else { + // In case of legacy datadir, try to guess the preference based on the printer preset files that are present + const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer"; + for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir)) + if (Slic3r::is_ini_file(dir_entry)) { + auto needle = legacy_preset_map.find(dir_entry.path().filename().string()); + if (needle == legacy_preset_map.end()) { continue; } + + const auto &model = needle->second.first; + const auto &variant = needle->second.second; + appconfig_new.set_variant("PrusaResearch", model, variant, true); + } + } + + // Initialize the is_visible flag in printer Presets + for (auto &pair : bundles) { + pair.second.preset_bundle->load_installed_printers(appconfig_new); + } + + // Copy installed filaments and SLA material names from app_config to appconfig_new + // while resolving current names of profiles, which were renamed in the meantime. + for (PrinterTechnology technology : { ptFFF, ptSLA }) { + const std::string §ion_name = (technology == ptFFF) ? AppConfig::SECTION_FILAMENTS : AppConfig::SECTION_MATERIALS; + std::map section_new; + if (app_config->has_section(section_name)) { + const std::map §ion_old = app_config->get_section(section_name); + for (const auto& material_name_and_installed : section_old) + if (material_name_and_installed.second == "1") { + // Material is installed. Resolve it in bundles. + size_t num_found = 0; + const std::string &material_name = material_name_and_installed.first; + for (auto &bundle : bundles) { + const PresetCollection &materials = bundle.second.preset_bundle->materials(technology); + const Preset *preset = materials.find_preset(material_name); + if (preset == nullptr) { + // Not found. Maybe the material preset is there, bu it was was renamed? + const std::string *new_name = materials.get_preset_name_renamed(material_name); + if (new_name != nullptr) + preset = materials.find_preset(*new_name); + } + if (preset != nullptr) { + // Materal preset was found, mark it as installed. + section_new[preset->name] = "1"; + ++ num_found; + } + } + if (num_found == 0) + BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was not found in installed vendor Preset Bundles.") % material_name; + else if (num_found > 1) + BOOST_LOG_TRIVIAL(error) << boost::format("Profile %1% was found in %2% vendor Preset Bundles.") % material_name % num_found; + } + } + appconfig_new.set_section(section_name, section_new); + }; +} + +void ConfigWizard::priv::add_page(ConfigWizardPage *page) +{ + const int proportion = (page->shortname == _L("Filaments")) || (page->shortname == _L("SLA Materials")) ? 1 : 0; + hscroll_sizer->Add(page, proportion, wxEXPAND); + all_pages.push_back(page); +} + +void ConfigWizard::priv::enable_next(bool enable) +{ + btn_next->Enable(enable); + btn_finish->Enable(enable); +} + +void ConfigWizard::priv::set_start_page(ConfigWizard::StartPage start_page) +{ + switch (start_page) { + case ConfigWizard::SP_PRINTERS: + index->go_to(page_fff); + btn_next->SetFocus(); + break; + case ConfigWizard::SP_FILAMENTS: + index->go_to(page_filaments); + btn_finish->SetFocus(); + break; + case ConfigWizard::SP_MATERIALS: + index->go_to(page_sla_materials); + btn_finish->SetFocus(); + break; + default: + index->go_to(page_welcome); + btn_next->SetFocus(); + break; + } +} + +void ConfigWizard::priv::create_3rdparty_pages() +{ + for (const auto &pair : bundles) { + const VendorProfile *vendor = pair.second.vendor_profile; + if (vendor->id == PresetBundle::PRUSA_BUNDLE) { continue; } + + bool is_fff_technology = false; + bool is_sla_technology = false; + + for (auto& model: vendor->models) + { + if (!is_fff_technology && model.technology == ptFFF) + is_fff_technology = true; + if (!is_sla_technology && model.technology == ptSLA) + is_sla_technology = true; + } + + PagePrinters* pageFFF = nullptr; + PagePrinters* pageSLA = nullptr; + + if (is_fff_technology) { + pageFFF = new PagePrinters(q, vendor->name + " " +_L("FFF Technology Printers"), vendor->name+" FFF", *vendor, 1, T_FFF); + add_page(pageFFF); + } + + if (is_sla_technology) { + pageSLA = new PagePrinters(q, vendor->name + " " + _L("SLA Technology Printers"), vendor->name+" MSLA", *vendor, 1, T_SLA); + add_page(pageSLA); + } + + pages_3rdparty.insert({vendor->id, {pageFFF, pageSLA}}); + } +} + +void ConfigWizard::priv::set_run_reason(RunReason run_reason) +{ + this->run_reason = run_reason; + for (auto &page : all_pages) { + page->set_run_reason(run_reason); + } +} + +void ConfigWizard::priv::update_materials(Technology technology) +{ + if (any_fff_selected && (technology & T_FFF)) { + filaments.clear(); + aliases_fff.clear(); + // Iterate filaments in all bundles + for (const auto &pair : bundles) { + for (const auto &filament : pair.second.preset_bundle->filaments) { + // Check if filament is already added + if (filaments.containts(&filament)) + continue; + // Iterate printers in all bundles + for (const auto &printer : pair.second.preset_bundle->printers) { + if (!printer.is_visible || printer.printer_technology() != ptFFF) + continue; + // Filter out inapplicable printers + if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) { + if (!filaments.containts(&filament)) { + filaments.push(&filament); + if (!filament.alias.empty()) + aliases_fff[filament.alias].insert(filament.name); + } + filaments.add_printer(&printer); + } + } + + } + } + // count compatible printers + for (const auto& preset : filaments.presets) { + + const auto filter = [preset](const std::pair element) { + return preset->alias == element.first; + }; + if (std::find_if(filaments.compatibility_counter.begin(), filaments.compatibility_counter.end(), filter) != filaments.compatibility_counter.end()) { + continue; + } + std::vector idx_with_same_alias; + for (size_t i = 0; i < filaments.presets.size(); ++i) { + if (preset->alias == filaments.presets[i]->alias) + idx_with_same_alias.push_back(i); + } + size_t counter = 0; + for (const auto& printer : filaments.printers) { + if (!(*printer).is_visible || (*printer).printer_technology() != ptFFF) + continue; + bool compatible = false; + // Test otrher materials with same alias + for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { + const Preset& prst = *(filaments.presets[idx_with_same_alias[i]]); + const Preset& prntr = *printer; + if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { + compatible = true; + break; + } + } + if (compatible) + counter++; + } + filaments.compatibility_counter.emplace_back(preset->alias, counter); + } + } + + if (any_sla_selected && (technology & T_SLA)) { + sla_materials.clear(); + aliases_sla.clear(); + + // Iterate SLA materials in all bundles + for (const auto &pair : bundles) { + for (const auto &material : pair.second.preset_bundle->sla_materials) { + // Check if material is already added + if (sla_materials.containts(&material)) + continue; + // Iterate printers in all bundles + // For now, we only allow the profiles to be compatible with another profiles inside the same bundle. + for (const auto& printer : pair.second.preset_bundle->printers) { + if(!printer.is_visible || printer.printer_technology() != ptSLA) + continue; + // Filter out inapplicable printers + if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) { + // Check if material is already added + if(!sla_materials.containts(&material)) { + sla_materials.push(&material); + if (!material.alias.empty()) + aliases_sla[material.alias].insert(material.name); + } + sla_materials.add_printer(&printer); + } + } + } + } + // count compatible printers + for (const auto& preset : sla_materials.presets) { + + const auto filter = [preset](const std::pair element) { + return preset->alias == element.first; + }; + if (std::find_if(sla_materials.compatibility_counter.begin(), sla_materials.compatibility_counter.end(), filter) != sla_materials.compatibility_counter.end()) { + continue; + } + std::vector idx_with_same_alias; + for (size_t i = 0; i < sla_materials.presets.size(); ++i) { + if(preset->alias == sla_materials.presets[i]->alias) + idx_with_same_alias.push_back(i); + } + size_t counter = 0; + for (const auto& printer : sla_materials.printers) { + if (!(*printer).is_visible || (*printer).printer_technology() != ptSLA) + continue; + bool compatible = false; + // Test otrher materials with same alias + for (size_t i = 0; i < idx_with_same_alias.size() && !compatible; ++i) { + const Preset& prst = *(sla_materials.presets[idx_with_same_alias[i]]); + const Preset& prntr = *printer; + if (is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) { + compatible = true; + break; + } + } + if (compatible) + counter++; + } + sla_materials.compatibility_counter.emplace_back(preset->alias, counter); + } + } +} + +void ConfigWizard::priv::on_custom_setup(const bool custom_wanted) +{ + custom_printer_selected = custom_wanted; + load_pages(); +} + +void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt) +{ + if (check_sla_selected() != any_sla_selected || + check_fff_selected() != any_fff_selected) { + any_fff_selected = check_fff_selected(); + any_sla_selected = check_sla_selected(); + + load_pages(); + } + + // Update the is_visible flag on relevant printer profiles + for (auto &pair : bundles) { + if (pair.first != evt.vendor_id) { continue; } + + for (auto &preset : pair.second.preset_bundle->printers) { + if (preset.config.opt_string("printer_model") == evt.model_id + && preset.config.opt_string("printer_variant") == evt.variant_name) { + preset.is_visible = evt.enable; + } + } + + // When a printer model is picked, but there is no material installed compatible with this printer model, + // install default materials for selected printer model silently. + check_and_install_missing_materials(page->technology, evt.model_id); + } + + if (page->technology & T_FFF) { + page_filaments->clear(); + } else if (page->technology & T_SLA) { + page_sla_materials->clear(); + } +} + +void ConfigWizard::priv::select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology) +{ + PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials; + for (const std::string& material : printer_model.default_materials) + appconfig_new.set(page_materials->materials->appconfig_section(), material, "1"); +} + +void ConfigWizard::priv::select_default_materials_for_printer_models(Technology technology, const std::set &printer_models) +{ + PageMaterials *page_materials = technology & T_FFF ? page_filaments : page_sla_materials; + const std::string &appconfig_section = page_materials->materials->appconfig_section(); + + // Following block was unnecessary. Its enough to iterate printer_models once. Not for every vendor printer page. + // Filament is selected on same page for all printers of same technology. + /* + auto select_default_materials_for_printer_page = [this, appconfig_section, printer_models, technology](PagePrinters *page_printers, Technology technology) + { + const std::string vendor_id = page_printers->get_vendor_id(); + for (auto& pair : bundles) + if (pair.first == vendor_id) + for (const VendorProfile::PrinterModel *printer_model : printer_models) + for (const std::string &material : printer_model->default_materials) + appconfig_new.set(appconfig_section, material, "1"); + }; + + PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla; + select_default_materials_for_printer_page(page_printers, technology); + + for (const auto& printer : pages_3rdparty) + { + page_printers = technology & T_FFF ? printer.second.first : printer.second.second; + if (page_printers) + select_default_materials_for_printer_page(page_printers, technology); + } + */ + + // Iterate printer_models and select default materials. If none available -> msg to user. + std::vector models_without_default; + for (const VendorProfile::PrinterModel* printer_model : printer_models) { + if (printer_model->default_materials.empty()) { + models_without_default.emplace_back(printer_model); + } else { + for (const std::string& material : printer_model->default_materials) + appconfig_new.set(appconfig_section, material, "1"); + } + } + + if (!models_without_default.empty()) { + std::string printer_names = "\n\n"; + for (const VendorProfile::PrinterModel* printer_model : models_without_default) { + printer_names += printer_model->name + "\n"; + } + printer_names += "\n\n"; + std::string message = (technology & T_FFF ? + GUI::format(_L("Following printer profiles has no default filament: %1%Please select one manually."), printer_names) : + GUI::format(_L("Following printer profiles has no default material: %1%Please select one manually."), printer_names)); + MessageDialog msg(q, message, _L("Notice"), wxOK); + msg.ShowModal(); + } + + update_materials(technology); + ((technology & T_FFF) ? page_filaments : page_sla_materials)->reload_presets(); +} + +void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install) +{ + auto it = pages_3rdparty.find(vendor->id); + wxCHECK_RET(it != pages_3rdparty.end(), "Internal error: GUI page not found for 3rd party vendor profile"); + + for (PagePrinters* page : { it->second.first, it->second.second }) + if (page) { + if (page->install && !install) + page->select_all(false); + page->install = install; + // if some 3rd vendor is selected, select first printer for them + if (install) + page->printer_pickers[0]->select_one(0, true); + page->Layout(); + } + + load_pages(); +} + +bool ConfigWizard::priv::on_bnt_finish() +{ + wxBusyCursor wait; + /* When Filaments or Sla Materials pages are activated, + * materials for this pages are automaticaly updated and presets are reloaded. + * + * But, if _Finish_ button was clicked without activation of those pages + * (for example, just some printers were added/deleted), + * than last changes wouldn't be updated for filaments/materials. + * SO, do that before close of Wizard + */ + update_materials(T_ANY); + if (any_fff_selected) + page_filaments->reload_presets(); + if (any_sla_selected) + page_sla_materials->reload_presets(); + + // theres no need to check that filament is selected if we have only custom printer + if (custom_printer_selected && !any_fff_selected && !any_sla_selected) return true; + // check, that there is selected at least one filament/material + return check_and_install_missing_materials(T_ANY); +} + +// This allmighty method verifies, whether there is at least a single compatible filament or SLA material installed +// for each Printer preset of each Printer Model installed. +// +// In case only_for_model_id is set, then the test is done for that particular printer model only, and the default materials are installed silently. +// Otherwise the user is quieried whether to install the missing default materials or not. +// +// Return true if the tested Printer Models already had materials installed. +// Return false if there were some Printer Models with missing materials, independent from whether the defaults were installed for these +// respective Printer Models or not. +bool ConfigWizard::priv::check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id) +{ + // Walk over all installed Printer presets and verify whether there is a filament or SLA material profile installed at the same PresetBundle, + // which is compatible with it. + const auto printer_models_missing_materials = [this, only_for_model_id](PrinterTechnology technology, const std::string §ion) + { + const std::map &appconfig_presets = appconfig_new.has_section(section) ? appconfig_new.get_section(section) : std::map(); + std::set printer_models_without_material; + for (const auto &pair : bundles) { + const PresetCollection &materials = pair.second.preset_bundle->materials(technology); + for (const auto &printer : pair.second.preset_bundle->printers) { + if (printer.is_visible && printer.printer_technology() == technology) { + const VendorProfile::PrinterModel *printer_model = PresetUtils::system_printer_model(printer); + assert(printer_model != nullptr); + if ((only_for_model_id.empty() || only_for_model_id == printer_model->id) && + printer_models_without_material.find(printer_model) == printer_models_without_material.end()) { + bool has_material = false; + for (const auto& preset : appconfig_presets) { + if (preset.second == "1") { + const Preset *material = materials.find_preset(preset.first, false); + if (material != nullptr && is_compatible_with_printer(PresetWithVendorProfile(*material, nullptr), PresetWithVendorProfile(printer, nullptr))) { + has_material = true; + break; + } + } + } + if (! has_material) + printer_models_without_material.insert(printer_model); + } + } + } + } + assert(printer_models_without_material.empty() || only_for_model_id.empty() || only_for_model_id == (*printer_models_without_material.begin())->id); + return printer_models_without_material; + }; + + const auto ask_and_select_default_materials = [this](const wxString &message, const std::set &printer_models, Technology technology) + { + //wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO); + MessageDialog msg(q, message, _L("Notice"), wxYES_NO); + if (msg.ShowModal() == wxID_YES) + select_default_materials_for_printer_models(technology, printer_models); + }; + + const auto printer_model_list = [](const std::set &printer_models) -> wxString { + wxString out; + for (const VendorProfile::PrinterModel *printer_model : printer_models) { + wxString name = from_u8(printer_model->name); + out += "\t\t"; + out += name; + out += "\n"; + } + return out; + }; + + if (any_fff_selected && (technology & T_FFF)) { + std::set printer_models_without_material = printer_models_missing_materials(ptFFF, AppConfig::SECTION_FILAMENTS); + if (! printer_models_without_material.empty()) { + if (only_for_model_id.empty()) + ask_and_select_default_materials( + _L("The following FFF printer models have no filament selected:") + + "\n\n\t" + + printer_model_list(printer_models_without_material) + + "\n\n\t" + + _L("Do you want to select default filaments for these FFF printer models?"), + printer_models_without_material, + T_FFF); + else + select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_FFF); + return false; + } + } + + if (any_sla_selected && (technology & T_SLA)) { + std::set printer_models_without_material = printer_models_missing_materials(ptSLA, AppConfig::SECTION_MATERIALS); + if (! printer_models_without_material.empty()) { + if (only_for_model_id.empty()) + ask_and_select_default_materials( + _L("The following SLA printer models have no materials selected:") + + "\n\n\t" + + printer_model_list(printer_models_without_material) + + "\n\n\t" + + _L("Do you want to select default SLA materials for these printer models?"), + printer_models_without_material, + T_SLA); + else + select_default_materials_for_printer_model(**printer_models_without_material.begin(), T_SLA); + return false; + } + } + + return true; +} + +static std::set get_new_added_presets(const std::map& old_data, const std::map& new_data) +{ + auto get_aliases = [](const std::map& data) { + std::set old_aliases; + for (auto item : data) { + const std::string& name = item.first; + size_t pos = name.find("@"); + old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1)); + } + return old_aliases; + }; + + std::set old_aliases = get_aliases(old_data); + std::set new_aliases = get_aliases(new_data); + std::set diff; + std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin())); + + return diff; +} + +static std::string get_first_added_preset(const std::map& old_data, const std::map& new_data) +{ + std::set diff = get_new_added_presets(old_data, new_data); + if (diff.empty()) + return std::string(); + return *diff.begin(); +} + +bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes) +{ + wxString header, caption = _L("Configuration is edited in ConfigWizard"); + const auto enabled_vendors = appconfig_new.vendors(); + const auto enabled_vendors_old = app_config->vendors(); + + bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model()); + PrinterTechnology preferred_pt = ptAny; + auto get_preferred_printer_technology = [enabled_vendors, enabled_vendors_old, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) { + const auto config = enabled_vendors.find(bundle_name); + PrinterTechnology pt = ptAny; + if (config != enabled_vendors.end()) { + for (const auto& model : bundle.vendor_profile->models) { + if (const auto model_it = config->second.find(model.id); + model_it != config->second.end() && model_it->second.size() > 0) { + pt = model.technology; + const auto config_old = enabled_vendors_old.find(bundle_name); + if (config_old == enabled_vendors_old.end() || config_old->second.find(model.id) == config_old->second.end()) { + // if preferred printer model has SLA printer technology it's important to check the model for multi-part state + if (pt == ptSLA && suppress_sla_printer) + continue; + return pt; + } + + if (const auto model_it_old = config_old->second.find(model.id); + model_it_old == config_old->second.end() || model_it_old->second != model_it->second) { + // if preferred printer model has SLA printer technology it's important to check the model for multi-part state + if (pt == ptSLA && suppress_sla_printer) + continue; + return pt; + } + } + } + } + return pt; + }; + // Prusa printers are considered first, then 3rd party. + if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle()); + preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) { + for (const auto& bundle : bundles) { + if (bundle.second.is_prusa_bundle) { continue; } + if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny) + continue; + else if (preferred_pt == ptAny) + preferred_pt = pt; + if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer))) + break; + } + } + + if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption)) + return false; + + bool check_unsaved_preset_changes = page_welcome->reset_user_profile(); + if (check_unsaved_preset_changes) + header = _L("All user presets will be deleted."); + int act_btns = UnsavedChangesDialog::ActionButtons::KEEP; + if (!check_unsaved_preset_changes) + act_btns |= UnsavedChangesDialog::ActionButtons::SAVE; + + // Install bundles from resources if needed: + std::vector install_bundles; + for (const auto &pair : bundles) { + if (! pair.second.is_in_resources) { continue; } + + if (pair.second.is_prusa_bundle) { + // Always install Prusa bundle, because it has a lot of filaments/materials + // likely to be referenced by other profiles. + install_bundles.emplace_back(pair.first); + continue; + } + + const auto vendor = enabled_vendors.find(pair.first); + if (vendor == enabled_vendors.end()) { continue; } + + size_t size_sum = 0; + for (const auto &model : vendor->second) { size_sum += model.second.size(); } + + if (size_sum > 0) { + // This vendor needs to be installed + install_bundles.emplace_back(pair.first); + } + } + if (!check_unsaved_preset_changes) + if ((check_unsaved_preset_changes = install_bundles.size() > 0)) + header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size()); + +#ifdef __linux__ + // Desktop integration on Linux + if (page_welcome->integrate_desktop()) + DesktopIntegrationDialog::perform_desktop_integration(); +#endif + + // Decide whether to create snapshot based on run_reason and the reset profile checkbox + bool snapshot = true; + Snapshot::Reason snapshot_reason = Snapshot::SNAPSHOT_UPGRADE; + switch (run_reason) { + case ConfigWizard::RR_DATA_EMPTY: + snapshot = false; + break; + case ConfigWizard::RR_DATA_LEGACY: + snapshot = true; + break; + case ConfigWizard::RR_DATA_INCOMPAT: + // In this case snapshot has already been taken by + // PresetUpdater with the appropriate reason + snapshot = false; + break; + case ConfigWizard::RR_USER: + snapshot = page_welcome->reset_user_profile(); + snapshot_reason = Snapshot::SNAPSHOT_USER; + break; + } + + if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Do you want to continue changing the configuration?"))) + return false; + + if (check_unsaved_preset_changes && + !wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + + if (install_bundles.size() > 0) { + // Install bundles from resources. + // Don't create snapshot - we've already done that above if applicable. + if (! updater->install_bundles_rsrc(std::move(install_bundles), false)) + return false; + } else { + BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources"; + } + + if (page_welcome->reset_user_profile()) { + BOOST_LOG_TRIVIAL(info) << "Resetting user profiles..."; + preset_bundle->reset(true); + } + + std::string preferred_model; + std::string preferred_variant; + auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) { + const auto config = enabled_vendors.find(bundle_name); + if (config == enabled_vendors.end()) + return std::string(); + for (const auto& model : bundle.vendor_profile->models) { + if (const auto model_it = config->second.find(model.id); + model_it != config->second.end() && model_it->second.size() > 0 && + preferred_pt == model.technology) { + variant = *model_it->second.begin(); + const auto config_old = enabled_vendors_old.find(bundle_name); + if (config_old == enabled_vendors_old.end()) + return model.id; + const auto model_it_old = config_old->second.find(model.id); + if (model_it_old == config_old->second.end()) + return model.id; + else if (model_it_old->second != model_it->second) { + for (const auto& var : model_it->second) + if (model_it_old->second.find(var) == model_it_old->second.end()) { + variant = var; + return model.id; + } + } + } + } + if (!variant.empty()) + variant.clear(); + return std::string(); + }; + // Prusa printers are considered first, then 3rd party. + if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant); + preferred_model.empty()) { + for (const auto& bundle : bundles) { + if (bundle.second.is_prusa_bundle) { continue; } + if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant); + !preferred_model.empty()) + break; + } + } + + // if unsaved changes was not cheched till this moment + if (!check_unsaved_preset_changes) { + if ((check_unsaved_preset_changes = !preferred_model.empty())) { + header = _L("A new Printer was installed and it will be activated."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) { + header = _L("Some Printers were uninstalled."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + } + + std::string first_added_filament, first_added_sla_material; + auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) { + if (appconfig_new.has_section(section_name)) { + // get first of new added preset names + const std::map& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map(); + first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name)); + } + }; + get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament); + get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material); + + // if unsaved changes was not cheched till this moment + if (!check_unsaved_preset_changes) { + if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) { + header = !first_added_filament.empty() ? + _L("A new filament was installed and it will be activated.") : + _L("A new SLA material was installed and it will be activated."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + else { + auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) { + return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map()) != appconfig_new.get_section(section_name); + }; + bool is_filaments_changed = changed(AppConfig::SECTION_FILAMENTS); + bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS); + if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) { + header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + } + } + + // apply materials in app_config + for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS}) + app_config->set_section(section_name, appconfig_new.get_section(section_name)); + + app_config->set_vendors(appconfig_new); + + app_config->set("notify_release", page_update->version_check ? "all" : "none"); + app_config->set("preset_update", page_update->preset_update ? "1" : "0"); + app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); + +#ifdef _WIN32 +#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (page_files_association != nullptr) { +#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); + app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); +// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); + + if (wxGetApp().is_editor()) { + if (page_files_association->associate_3mf()) + wxGetApp().associate_3mf_files(); + if (page_files_association->associate_stl()) + wxGetApp().associate_stl_files(); + } +// else { +// if (page_files_association->associate_gcode()) +// wxGetApp().associate_gcode_files(); +// } +#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + } + else { + app_config->set("associate_3mf", "0"); + app_config->set("associate_stl", "0"); +// app_config->set("associate_gcode", "0"); + } +#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + +#endif // _WIN32 + + page_mode->serialize_mode(app_config); + + if (check_unsaved_preset_changes) + 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 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)) + return false; + + page_firmware->apply_custom_config(*custom_config); + page_bed->apply_custom_config(*custom_config); + page_diams->apply_custom_config(*custom_config); + page_temps->apply_custom_config(*custom_config); + + const std::string profile_name = page_custom->profile_name(); + preset_bundle->load_config_from_wizard(profile_name, *custom_config); + } + + // Update the selections from the compatibilty. + preset_bundle->export_selections(*app_config); + + return true; +} +void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add) +{ + const PresetAliases& aliases = section == AppConfig::SECTION_FILAMENTS ? aliases_fff : aliases_sla; + + auto update = [this, add](const std::string& s, const std::string& key) { + assert(! s.empty()); + if (add) + appconfig_new.set(s, key, "1"); + else + appconfig_new.erase(s, key); + }; + + // add or delete presets had a same alias + auto it = aliases.find(alias_key); + if (it != aliases.end()) + for (const std::string& name : it->second) + update(section, name); +} + +bool ConfigWizard::priv::check_fff_selected() +{ + bool ret = page_fff->any_selected(); + for (const auto& printer: pages_3rdparty) + if (printer.second.first) // FFF page + ret |= printer.second.first->any_selected(); + return ret; +} + +bool ConfigWizard::priv::check_sla_selected() +{ + bool ret = page_msla->any_selected(); + for (const auto& printer: pages_3rdparty) + if (printer.second.second) // SLA page + ret |= printer.second.second->any_selected(); + return ret; +} + + +// Public + +ConfigWizard::ConfigWizard(wxWindow *parent) + : DPIDialog(parent, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(name()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + , p(new priv(this)) +{ + this->SetFont(wxGetApp().normal_font()); + + p->load_vendors(); + p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ + "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", + })); + + p->index = new ConfigWizardIndex(this); + + auto *vsizer = new wxBoxSizer(wxVERTICAL); + auto *topsizer = new wxBoxSizer(wxHORIZONTAL); + auto* hline = new StaticLine(this); + p->btnsizer = new wxBoxSizer(wxHORIZONTAL); + + // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling. + // Later, we compare that to the size of the current screen and set minimum width based on that (see below). + p->hscroll = new wxScrolledWindow(this); + p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL); + p->hscroll->SetSizer(p->hscroll_sizer); + + topsizer->Add(p->index, 0, wxEXPAND); + topsizer->AddSpacer(INDEX_MARGIN); + topsizer->Add(p->hscroll, 1, wxEXPAND); + + p->btn_sel_all = new wxButton(this, wxID_ANY, _L("Select all standard printers")); + p->btnsizer->Add(p->btn_sel_all); + + p->btn_prev = new wxButton(this, wxID_ANY, _L("< &Back")); + p->btn_next = new wxButton(this, wxID_ANY, _L("&Next >")); + p->btn_finish = new wxButton(this, wxID_APPLY, _L("&Finish")); + p->btn_cancel = new wxButton(this, wxID_CANCEL, _L("Cancel")); // Note: The label needs to be present, otherwise we get accelerator bugs on Mac + p->btnsizer->AddStretchSpacer(); + p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING); + p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING); + p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING); + p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING); + + wxGetApp().UpdateDarkUI(p->btn_sel_all); + wxGetApp().UpdateDarkUI(p->btn_prev); + wxGetApp().UpdateDarkUI(p->btn_next); + wxGetApp().UpdateDarkUI(p->btn_finish); + wxGetApp().UpdateDarkUI(p->btn_cancel); + + const auto prusa_it = p->bundles.find("PrusaResearch"); + wxCHECK_RET(prusa_it != p->bundles.cend(), "Vendor PrusaResearch not found"); + const VendorProfile *vendor_prusa = prusa_it->second.vendor_profile; + + p->add_page(p->page_welcome = new PageWelcome(this)); + + + p->page_fff = new PagePrinters(this, _L("Prusa FFF Technology Printers"), "Prusa FFF", *vendor_prusa, 0, T_FFF); + p->only_sla_mode = !p->page_fff->has_printers; + if (!p->only_sla_mode) { + p->add_page(p->page_fff); + p->page_fff->is_primary_printer_page = true; + } + + + p->page_msla = new PagePrinters(this, _L("Prusa MSLA Technology Printers"), "Prusa MSLA", *vendor_prusa, 0, T_SLA); + p->add_page(p->page_msla); + if (p->only_sla_mode) { + p->page_msla->is_primary_printer_page = true; + } + + if (!p->only_sla_mode) { + // Pages for 3rd party vendors + p->create_3rdparty_pages(); // Needs to be done _before_ creating PageVendors + p->add_page(p->page_vendors = new PageVendors(this)); + p->add_page(p->page_custom = new PageCustom(this)); + p->custom_printer_selected = p->page_custom->custom_wanted(); + } + + p->any_sla_selected = p->check_sla_selected(); + p->any_fff_selected = ! p->only_sla_mode && p->check_fff_selected(); + + p->update_materials(T_ANY); + if (!p->only_sla_mode) + p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments, + _L("Filament Profiles Selection"), _L("Filaments"), _L("Type:") )); + + p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials, + _L("SLA Material Profiles Selection") + " ", _L("SLA Materials"), _L("Type:") )); + + + p->add_page(p->page_update = new PageUpdate(this)); + p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); +#ifdef _WIN32 +#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + // file association is not possible anymore starting with Win 8 + if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) +#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + p->add_page(p->page_files_association = new PageFilesAssociation(this)); +#endif // _WIN32 + 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_diams = new PageDiameters(this)); + p->add_page(p->page_temps = new PageTemperatures(this)); + + p->load_pages(); + p->index->go_to(size_t{0}); + + vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); + vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING); + vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN); + + SetSizer(vsizer); + SetSizerAndFit(vsizer); + + // We can now enable scrolling on hscroll + p->hscroll->SetScrollRate(30, 30); + + on_window_geometry(this, [this]() { + p->init_dialog_size(); + }); + + p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { this->p->index->go_prev(); }); + + p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) + { + // check, that there is selected at least one filament/material + ConfigWizardPage* active_page = this->p->index->active_page(); + if (// Leaving the filaments or SLA materials page and + (active_page == p->page_filaments || active_page == p->page_sla_materials) && + // some Printer models had no filament or SLA material selected. + ! p->check_and_install_missing_materials(dynamic_cast(active_page)->materials->technology)) + // In that case don't leave the page and the function above queried the user whether to install default materials. + return; + this->p->index->go_next(); + }); + + p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) + { + if (p->on_bnt_finish()) + this->EndModal(wxID_OK); + }); + + p->btn_sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &) { + p->any_sla_selected = true; + p->load_pages(); + p->page_fff->select_all(true, false); + p->page_msla->select_all(true, false); + p->index->go_to(p->page_mode); + }); + + p->index->Bind(EVT_INDEX_PAGE, [this](const wxCommandEvent &) { + const bool is_last = p->index->active_is_last(); + p->btn_next->Show(! is_last); + if (is_last) + p->btn_finish->SetFocus(); + + Layout(); + }); + + if (wxLinux_gtk3) + this->Bind(wxEVT_SHOW, [this, vsizer](const wxShowEvent& e) { + ConfigWizardPage* active_page = p->index->active_page(); + if (!active_page) + return; + for (auto page : p->all_pages) + if (page != active_page) + page->Hide(); + // update best size for the dialog after hiding of the non-active pages + vsizer->SetSizeHints(this); + // set initial dialog size + p->init_dialog_size(); + }); +} + +ConfigWizard::~ConfigWizard() {} + +bool ConfigWizard::run(RunReason reason, StartPage start_page) +{ + BOOST_LOG_TRIVIAL(info) << boost::format("Running ConfigWizard, reason: %1%, start_page: %2%") % reason % start_page; + + GUI_App &app = wxGetApp(); + + p->set_run_reason(reason); + p->set_start_page(start_page); + + if (ShowModal() == wxID_OK) { + bool apply_keeped_changes = false; + if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes)) + return false; + + if (apply_keeped_changes) + app.apply_keeped_preset_modifications(); + + app.app_config->set_legacy_datadir(false); + app.update_mode(); + app.obj_manipul()->update_ui_from_settings(); + BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied"; + return true; + } else { + BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled"; + return false; + } +} + +const wxString& ConfigWizard::name(const bool from_menu/* = false*/) +{ + // A different naming convention is used for the Wizard on Windows & GTK vs. OSX. + // Note: Don't call _() macro here. + // This function just return the current name according to the OS. + // Translation is implemented inside GUI_App::add_config_menu() +#if __APPLE__ + static const wxString config_wizard_name = L("Configuration Assistant"); + static const wxString config_wizard_name_menu = L("Configuration &Assistant"); +#else + static const wxString config_wizard_name = L("Configuration Wizard"); + static const wxString config_wizard_name_menu = L("Configuration &Wizard"); +#endif + return from_menu ? config_wizard_name_menu : config_wizard_name; +} + +void ConfigWizard::on_dpi_changed(const wxRect &suggested_rect) +{ + p->index->msw_rescale(); + + const int em = em_unit(); + + msw_buttons_rescale(this, em, { wxID_APPLY, + wxID_CANCEL, + p->btn_sel_all->GetId(), + p->btn_next->GetId(), + p->btn_prev->GetId() }); + + for (auto printer_picker: p->page_fff->printer_pickers) + msw_buttons_rescale(this, em, printer_picker->get_button_indexes()); + + p->init_dialog_size(); + + Refresh(); +} + +void ConfigWizard::on_sys_color_changed() +{ + wxGetApp().UpdateDlgDarkUI(this); + Refresh(); +} + +} +} diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 4435f836b..af9b63acd 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1,3333 +1,3354 @@ -#include "libslic3r/Technologies.hpp" -#include "GUI_App.hpp" -#include "GUI_Init.hpp" -#include "GUI_ObjectList.hpp" -#include "GUI_ObjectManipulation.hpp" -#include "GUI_Factories.hpp" -#include "format.hpp" -#include "I18N.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "libslic3r/Utils.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/I18N.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/Color.hpp" - -#include "GUI.hpp" -#include "GUI_Utils.hpp" -#include "3DScene.hpp" -#include "MainFrame.hpp" -#include "Plater.hpp" -#include "GLCanvas3D.hpp" - -#include "../Utils/PresetUpdater.hpp" -#include "../Utils/PrintHost.hpp" -#include "../Utils/Process.hpp" -#include "../Utils/MacDarkMode.hpp" -#include "../Utils/AppUpdater.hpp" -#include "slic3r/Config/Snapshot.hpp" -#include "ConfigSnapshotDialog.hpp" -#include "FirmwareDialog.hpp" -#include "Preferences.hpp" -#include "Tab.hpp" -#include "SysInfoDialog.hpp" -#include "KBShortcutsDialog.hpp" -#include "UpdateDialogs.hpp" -#include "Mouse3DController.hpp" -#include "RemovableDriveManager.hpp" -#include "InstanceCheck.hpp" -#include "NotificationManager.hpp" -#include "UnsavedChangesDialog.hpp" -#include "SavePresetDialog.hpp" -#include "PrintHostDialogs.hpp" -#include "DesktopIntegrationDialog.hpp" -#include "SendSystemInfoDialog.hpp" - -#include "BitmapCache.hpp" -#include "Notebook.hpp" - -#ifdef __WXMSW__ -#include -#include -#ifdef _MSW_DARK_MODE -#include -#endif // _MSW_DARK_MODE -#endif -#ifdef _WIN32 -#include -#endif - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG -#include -#include -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG - -// Needed for forcing menu icons back under gtk2 and gtk3 -#if defined(__WXGTK20__) || defined(__WXGTK3__) - #include -#endif - -using namespace std::literals; - -namespace Slic3r { -namespace GUI { - -class MainFrame; - -class SplashScreen : public wxSplashScreen -{ -public: - SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition) - : wxSplashScreen(bitmap, splashStyle, milliseconds, static_cast(wxGetApp().mainframe), wxID_ANY, wxDefaultPosition, wxDefaultSize, -#ifdef __APPLE__ - wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP -#else - wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR -#endif // !__APPLE__ - ) - { - wxASSERT(bitmap.IsOk()); - - int init_dpi = get_dpi_for_window(this); - this->SetPosition(pos); - this->CenterOnScreen(); - int new_dpi = get_dpi_for_window(this); - - m_scale = (float)(new_dpi) / (float)(init_dpi); - m_main_bitmap = bitmap; - - scale_bitmap(m_main_bitmap, m_scale); - - // init constant texts and scale fonts - init_constant_text(); - - // this font will be used for the action string - m_action_font = m_constant_text.credits_font.Bold(); - - // draw logo and constant info text - Decorate(m_main_bitmap); - } - - void SetText(const wxString& text) - { - set_bitmap(m_main_bitmap); - if (!text.empty()) { - wxBitmap bitmap(m_main_bitmap); - - wxMemoryDC memDC; - memDC.SelectObject(bitmap); - - memDC.SetFont(m_action_font); - memDC.SetTextForeground(wxColour(237, 107, 33)); - memDC.DrawText(text, int(m_scale * 60), m_action_line_y_position); - - memDC.SelectObject(wxNullBitmap); - set_bitmap(bitmap); -#ifdef __WXOSX__ - // without this code splash screen wouldn't be updated under OSX - wxYield(); -#endif - } - } - - static wxBitmap MakeBitmap(wxBitmap bmp) - { - if (!bmp.IsOk()) - return wxNullBitmap; - - // create dark grey background for the splashscreen - // It will be 5/3 of the weight of the bitmap - int width = lround((double)5 / 3 * bmp.GetWidth()); - int height = bmp.GetHeight(); - - wxImage image(width, height); - unsigned char* imgdata_ = image.GetData(); - for (int i = 0; i < width * height; ++i) { - *imgdata_++ = 51; - *imgdata_++ = 51; - *imgdata_++ = 51; - } - - wxBitmap new_bmp(image); - - wxMemoryDC memDC; - memDC.SelectObject(new_bmp); - memDC.DrawBitmap(bmp, width - bmp.GetWidth(), 0, true); - - return new_bmp; - } - - void Decorate(wxBitmap& bmp) - { - if (!bmp.IsOk()) - return; - - // draw text to the box at the left of the splashscreen. - // this box will be 2/5 of the weight of the bitmap, and be at the left. - int width = lround(bmp.GetWidth() * 0.4); - - // load bitmap for logo - BitmapCache bmp_cache; - int logo_size = lround(width * 0.25); - wxBitmap logo_bmp = *bmp_cache.load_svg(wxGetApp().logo_name(), logo_size, logo_size); - - wxCoord margin = int(m_scale * 20); - - wxRect banner_rect(wxPoint(0, logo_size), wxPoint(width, bmp.GetHeight())); - banner_rect.Deflate(margin, 2 * margin); - - // use a memory DC to draw directly onto the bitmap - wxMemoryDC memDc(bmp); - - // draw logo - memDc.DrawBitmap(logo_bmp, margin, margin, true); - - // draw the (white) labels inside of our black box (at the left of the splashscreen) - memDc.SetTextForeground(wxColour(255, 255, 255)); - - memDc.SetFont(m_constant_text.title_font); - memDc.DrawLabel(m_constant_text.title, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); - - int title_height = memDc.GetTextExtent(m_constant_text.title).GetY(); - banner_rect.SetTop(banner_rect.GetTop() + title_height); - banner_rect.SetHeight(banner_rect.GetHeight() - title_height); - - memDc.SetFont(m_constant_text.version_font); - memDc.DrawLabel(m_constant_text.version, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); - int version_height = memDc.GetTextExtent(m_constant_text.version).GetY(); - - memDc.SetFont(m_constant_text.credits_font); - memDc.DrawLabel(m_constant_text.credits, banner_rect, wxALIGN_BOTTOM | wxALIGN_LEFT); - int credits_height = memDc.GetMultiLineTextExtent(m_constant_text.credits).GetY(); - int text_height = memDc.GetTextExtent("text").GetY(); - - // calculate position for the dynamic text - int logo_and_header_height = margin + logo_size + title_height + version_height; - m_action_line_y_position = logo_and_header_height + 0.5 * (bmp.GetHeight() - margin - credits_height - logo_and_header_height - text_height); - } - -private: - wxBitmap m_main_bitmap; - wxFont m_action_font; - int m_action_line_y_position; - float m_scale {1.0}; - - struct ConstantText - { - wxString title; - wxString version; - wxString credits; - - wxFont title_font; - wxFont version_font; - wxFont credits_font; - - void init(wxFont init_font) - { - // title - title = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME; - - // dynamically get the version to display - version = _L("Version") + " " + std::string(SLIC3R_VERSION); - - // credits infornation - credits = title + " " + - _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n" + - _L("Developed by Prusa Research.")+ "\n\n" + - title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + "\n\n" + - _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + - _L("Artwork model by M Boyer"); - - title_font = version_font = credits_font = init_font; - } - } - m_constant_text; - - void init_constant_text() - { - m_constant_text.init(get_default_font(this)); - - // As default we use a system font for current display. - // Scale fonts in respect to banner width - - int text_banner_width = lround(0.4 * m_main_bitmap.GetWidth()) - roundl(m_scale * 50); // banner_width - margins - - float title_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.title).GetX(); - scale_font(m_constant_text.title_font, title_font_scale > 3.5f ? 3.5f : title_font_scale); - - float version_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.version).GetX(); - scale_font(m_constant_text.version_font, version_font_scale > 2.f ? 2.f : version_font_scale); - - // The width of the credits information string doesn't respect to the banner width some times. - // So, scale credits_font in the respect to the longest string width - int longest_string_width = word_wrap_string(m_constant_text.credits); - float font_scale = (float)text_banner_width / longest_string_width; - scale_font(m_constant_text.credits_font, font_scale); - } - - void set_bitmap(wxBitmap& bmp) - { - m_window->SetBitmap(bmp); - m_window->Refresh(); - m_window->Update(); - } - - void scale_bitmap(wxBitmap& bmp, float scale) - { - if (scale == 1.0) - return; - - wxImage image = bmp.ConvertToImage(); - if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0) - return; - - int width = int(scale * image.GetWidth()); - int height = int(scale * image.GetHeight()); - image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); - - bmp = wxBitmap(std::move(image)); - } - - void scale_font(wxFont& font, float scale) - { -#ifdef __WXMSW__ - // Workaround for the font scaling in respect to the current active display, - // not for the primary display, as it's implemented in Font.cpp - // See https://github.com/wxWidgets/wxWidgets/blob/master/src/msw/font.cpp - // void wxNativeFontInfo::SetFractionalPointSize(float pointSizeNew) - wxNativeFontInfo nfi= *font.GetNativeFontInfo(); - float pointSizeNew = scale * font.GetPointSize(); - nfi.lf.lfHeight = nfi.GetLogFontHeightAtPPI(pointSizeNew, get_dpi_for_window(this)); - nfi.pointSize = pointSizeNew; - font = wxFont(nfi); -#else - font.Scale(scale); -#endif //__WXMSW__ - } - - // wrap a string for the strings no longer then 55 symbols - // return extent of the longest string - int word_wrap_string(wxString& input) - { - size_t line_len = 55;// count of symbols in one line - int idx = -1; - size_t cur_len = 0; - - wxString longest_sub_string; - auto get_longest_sub_string = [input](wxString &longest_sub_str, size_t cur_len, size_t i) { - if (cur_len > longest_sub_str.Len()) - longest_sub_str = input.SubString(i - cur_len + 1, i); - }; - - for (size_t i = 0; i < input.Len(); i++) - { - cur_len++; - if (input[i] == ' ') - idx = i; - if (input[i] == '\n') - { - get_longest_sub_string(longest_sub_string, cur_len, i); - idx = -1; - cur_len = 0; - } - if (cur_len >= line_len && idx >= 0) - { - get_longest_sub_string(longest_sub_string, cur_len, i); - input[idx] = '\n'; - cur_len = i - static_cast(idx); - } - } - - return GetTextExtent(longest_sub_string).GetX(); - } -}; - - -#ifdef __linux__ -bool static check_old_linux_datadir(const wxString& app_name) { - // If we are on Linux and the datadir does not exist yet, look into the old - // location where the datadir was before version 2.3. If we find it there, - // tell the user that he might wanna migrate to the new location. - // (https://github.com/prusa3d/PrusaSlicer/issues/2911) - // To be precise, the datadir should exist, it is created when single instance - // lock happens. Instead of checking for existence, check the contents. - - namespace fs = boost::filesystem; - - std::string new_path = Slic3r::data_dir(); - - wxString dir; - if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) - dir = wxFileName::GetHomeDir() + wxS("/.config"); - std::string default_path = (dir + "/" + app_name).ToUTF8().data(); - - if (new_path != default_path) { - // This happens when the user specifies a custom --datadir. - // Do not show anything in that case. - return true; - } - - fs::path data_dir = fs::path(new_path); - if (! fs::is_directory(data_dir)) - return true; // This should not happen. - - int file_count = std::distance(fs::directory_iterator(data_dir), fs::directory_iterator()); - - if (file_count <= 1) { // just cache dir with an instance lock - std::string old_path = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data(); - - if (fs::is_directory(old_path)) { - wxString msg = from_u8((boost::format(_u8L("Starting with %1% 2.3, configuration " - "directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\n" - "This directory did not exist yet (maybe you run the new version for the first time).\nHowever, " - "an old %1% configuration directory was detected in \n%3%.\n\n" - "Consider moving the contents of the old directory to the new location in order to access " - "your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old " - "location again.\n\n" - "What do you want to do now?")) % SLIC3R_APP_NAME % new_path % old_path).str()); - wxString caption = from_u8((boost::format(_u8L("%s - BREAKING CHANGE")) % SLIC3R_APP_NAME).str()); - RichMessageDialog dlg(nullptr, msg, caption, wxYES_NO); - dlg.SetYesNoLabels(_L("Quit, I will move my data now"), _L("Start the application")); - if (dlg.ShowModal() != wxID_NO) - return false; - } - } else { - // If the new directory exists, be silent. The user likely already saw the message. - } - return true; -} -#endif - -#ifdef _WIN32 -#if 0 // External Updater is replaced with AppUpdater.cpp -static bool run_updater_win() -{ - // find updater exe - boost::filesystem::path path_updater = boost::dll::program_location().parent_path() / "prusaslicer-updater.exe"; - // run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst - std::string msg; - bool res = create_process(path_updater, L"/silent", msg); - if (!res) - BOOST_LOG_TRIVIAL(error) << msg; - return res; -} -#endif // 0 -#endif // _WIN32 - -struct FileWildcards { - std::string_view title; - std::vector file_extensions; -}; - -static const FileWildcards file_wildcards_by_type[FT_SIZE] = { - /* FT_STL */ { "STL files"sv, { ".stl"sv } }, - /* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } }, - /* FT_OBJECT */ { "Object files"sv, { ".stl"sv, ".obj"sv } }, - /* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } }, - /* FT_3MF */ { "3MF files"sv, { ".3mf"sv } }, - /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } }, - /* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv } }, - /* FT_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } }, - /* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } }, - - /* FT_INI */ { "INI files"sv, { ".ini"sv } }, - /* FT_SVG */ { "SVG files"sv, { ".svg"sv } }, - - /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, - - /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, -}; - -#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR -wxString file_wildcards(FileType file_type) -{ - const FileWildcards& data = file_wildcards_by_type[file_type]; - std::string title; - std::string mask; - - // Generate cumulative first item - for (const std::string_view& ext : data.file_extensions) { - if (title.empty()) { - title = "*"; - title += ext; - mask = title; - } - else { - title += ", *"; - title += ext; - mask += ";*"; - mask += ext; - } - mask += ";*"; - mask += boost::to_upper_copy(std::string(ext)); - } - - wxString ret = GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); - - // Adds an item for each of the extensions - if (data.file_extensions.size() > 1) { - for (const std::string_view& ext : data.file_extensions) { - title = "*"; - title += ext; - ret += GUI::format_wxstr("|%s (%s)|%s", data.title, title, title); - } - } - - return ret; -} -#else -// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. -// The function accepts a custom extension parameter. If the parameter is provided, the custom extension -// will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips -// an extension from the provided initial file name and substitutes it with the default extension (the first one in the template). -wxString file_wildcards(FileType file_type, const std::string &custom_extension) -{ - const FileWildcards& data = file_wildcards_by_type[file_type]; - std::string title; - std::string mask; - std::string custom_ext_lower; - - if (! custom_extension.empty()) { - // Generate an extension into the title mask and into the list of extensions. - custom_ext_lower = boost::to_lower_copy(custom_extension); - const std::string custom_ext_upper = boost::to_upper_copy(custom_extension); - if (custom_ext_lower == custom_extension) { - // Add a lower case version. - title = std::string("*") + custom_ext_lower; - mask = title; - // Add an upper case version. - mask += ";*"; - mask += custom_ext_upper; - } else if (custom_ext_upper == custom_extension) { - // Add an upper case version. - title = std::string("*") + custom_ext_upper; - mask = title; - // Add a lower case version. - mask += ";*"; - mask += custom_ext_lower; - } else { - // Add the mixed case version only. - title = std::string("*") + custom_extension; - mask = title; - } - } - - for (const std::string_view &ext : data.file_extensions) - // Only add an extension if it was not added first as the custom extension. - if (ext != custom_ext_lower) { - if (title.empty()) { - title = "*"; - title += ext; - mask = title; - } else { - title += ", *"; - title += ext; - mask += ";*"; - mask += ext; - } - mask += ";*"; - mask += boost::to_upper_copy(std::string(ext)); - } - - return GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); -} -#endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR - -static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } - -#ifdef WIN32 -#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) -static void register_win32_dpi_event() -{ - enum { WM_DPICHANGED_ = 0x02e0 }; - - wxWindow::MSWRegisterMessageHandler(WM_DPICHANGED_, [](wxWindow *win, WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) { - const int dpi = wParam & 0xffff; - const auto rect = reinterpret_cast(lParam); - const wxRect wxrect(wxPoint(rect->top, rect->left), wxPoint(rect->bottom, rect->right)); - - DpiChangedEvent evt(EVT_DPI_CHANGED_SLICER, dpi, wxrect); - win->GetEventHandler()->AddPendingEvent(evt); - - return true; - }); -} -#endif // !wxVERSION_EQUAL_OR_GREATER_THAN - -static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; - -static void register_win32_device_notification_event() -{ - wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. - auto main_frame = dynamic_cast(win); - auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); - if (plater == nullptr) - // Maybe some other top level window like a dialog or maybe a pop-up menu? - return true; - PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; - switch (wParam) { - case DBT_DEVICEARRIVAL: - if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) - plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); - else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { - PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; -// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) { -// printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name); - if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) - plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name))); - } - break; - case DBT_DEVICEREMOVECOMPLETE: - if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) - plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); - else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { - PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; -// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) -// printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name); - if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) - plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name))); - } - break; - default: - break; - } - return true; - }); - - wxWindow::MSWRegisterMessageHandler(MainFrame::WM_USER_MEDIACHANGED, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. - auto main_frame = dynamic_cast(win); - auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); - if (plater == nullptr) - // Maybe some other top level window like a dialog or maybe a pop-up menu? - return true; - wchar_t sPath[MAX_PATH]; - if (lParam == SHCNE_MEDIAINSERTED || lParam == SHCNE_MEDIAREMOVED) { - struct _ITEMIDLIST* pidl = *reinterpret_cast(wParam); - if (! SHGetPathFromIDList(pidl, sPath)) { - BOOST_LOG_TRIVIAL(error) << "MediaInserted: SHGetPathFromIDList failed"; - return false; - } - } - switch (lParam) { - case SHCNE_MEDIAINSERTED: - { - //printf("SHCNE_MEDIAINSERTED %S\n", sPath); - plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); - break; - } - case SHCNE_MEDIAREMOVED: - { - //printf("SHCNE_MEDIAREMOVED %S\n", sPath); - plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); - break; - } - default: -// printf("Unknown\n"); - break; - } - return true; - }); - - wxWindow::MSWRegisterMessageHandler(WM_INPUT, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - auto main_frame = dynamic_cast(Slic3r::GUI::find_toplevel_parent(win)); - auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); -// if (wParam == RIM_INPUTSINK && plater != nullptr && main_frame->IsActive()) { - if (wParam == RIM_INPUT && plater != nullptr && main_frame->IsActive()) { - RAWINPUT raw; - UINT rawSize = sizeof(RAWINPUT); - ::GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER)); - if (raw.header.dwType == RIM_TYPEHID && plater->get_mouse3d_controller().handle_raw_input_win32(raw.data.hid.bRawData, raw.data.hid.dwSizeHid)) - return true; - } - return false; - }); - - wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { - COPYDATASTRUCT* copy_data_structure = { 0 }; - copy_data_structure = (COPYDATASTRUCT*)lParam; - if (copy_data_structure->dwData == 1) { - LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData; - Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments)); - } - return true; - }); -} -#endif // WIN32 - -static void generic_exception_handle() -{ - // Note: Some wxWidgets APIs use wxLogError() to report errors, eg. wxImage - // - see https://docs.wxwidgets.org/3.1/classwx_image.html#aa249e657259fe6518d68a5208b9043d0 - // - // wxLogError typically goes around exception handling and display an error dialog some time - // after an error is logged even if exception handling and OnExceptionInMainLoop() take place. - // This is why we use wxLogError() here as well instead of a custom dialog, because it accumulates - // errors if multiple have been collected and displays just one error message for all of them. - // Otherwise we would get multiple error messages for one missing png, for example. - // - // If a custom error message window (or some other solution) were to be used, it would be necessary - // to turn off wxLogError() usage in wx APIs, most notably in wxImage - // - see https://docs.wxwidgets.org/trunk/classwx_image.html#aa32e5d3507cc0f8c3330135bc0befc6a - - try { - throw; - } catch (const std::bad_alloc& ex) { - // bad_alloc in main thread is most likely fatal. Report immediately to the user (wxLogError would be delayed) - // and terminate the app so it is at least certain to happen now. - wxString errmsg = wxString::Format(_L("%s has encountered an error. It was likely caused by running out of memory. " - "If you are sure you have enough RAM on your system, this may also be a bug and we would " - "be glad if you reported it.\n\nThe application will now terminate."), SLIC3R_APP_NAME); - wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Fatal error"), wxOK | wxICON_ERROR); - BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what(); - std::terminate(); - } catch (const boost::io::bad_format_string& ex) { - wxString errmsg = _L("PrusaSlicer has encountered a localization error. " - "Please report to PrusaSlicer team, what language was active and in which scenario " - "this issue happened. Thank you.\n\nThe application will now terminate."); - wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Critical error"), wxOK | wxICON_ERROR); - BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); - std::terminate(); - throw; - } catch (const std::exception& ex) { - wxLogError(format_wxstr(_L("Internal error: %1%"), ex.what())); - BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); - throw; - } -} - -void GUI_App::post_init() -{ - assert(initialized()); - if (! this->initialized()) - throw Slic3r::RuntimeError("Calling post_init() while not yet initialized"); - - if (this->init_params->start_as_gcodeviewer) { - if (! this->init_params->input_files.empty()) - this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str())); - } - else { - if (! this->init_params->preset_substitutions.empty()) - show_substitutions_info(this->init_params->preset_substitutions); - -#if 0 - // Load the cummulative config over the currently active profiles. - //FIXME if multiple configs are loaded, only the last one will have an effect. - // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). - // As of now only the full configs are supported here. - if (!m_print_config.empty()) - this->gui->mainframe->load_config(m_print_config); -#endif - if (! this->init_params->load_configs.empty()) - // Load the last config to give it a name at the UI. The name of the preset may be later - // changed by loading an AMF or 3MF. - //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. - this->mainframe->load_config_file(this->init_params->load_configs.back()); - // If loading a 3MF file, the config is loaded from the last one. - if (!this->init_params->input_files.empty()) { - const std::vector res = this->plater()->load_files(this->init_params->input_files, true, true); - if (!res.empty() && this->init_params->input_files.size() == 1) { - // Update application titlebar when opening a project file - const std::string& filename = this->init_params->input_files.front(); - if (boost::algorithm::iends_with(filename, ".amf") || - boost::algorithm::iends_with(filename, ".amf.xml") || - boost::algorithm::iends_with(filename, ".3mf")) - this->plater()->set_project_filename(from_u8(filename)); - } - } - if (! this->init_params->extra_config.empty()) - this->mainframe->load_config(this->init_params->extra_config); - } - - // show "Did you know" notification - if (app_config->get("show_hints") == "1" && ! is_gcode_viewer()) - plater_->get_notification_manager()->push_hint_notification(true); - - // The extra CallAfter() is needed because of Mac, where this is the only way - // to popup a modal dialog on start without screwing combo boxes. - // This is ugly but I honestly found no better way to do it. - // Neither wxShowEvent nor wxWindowCreateEvent work reliably. - if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater. - if (! this->check_updates(false)) - // Configuration is not compatible and reconfigure was refused by the user. Application is closing. - return; - CallAfter([this] { - bool cw_showed = this->config_wizard_startup(); - this->preset_updater->sync(preset_bundle); - this->app_version_check(false); - if (! cw_showed) { - // The CallAfter is needed as well, without it, GL extensions did not show. - // Also, we only want to show this when the wizard does not, so the new user - // sees something else than "we want something" on the first start. - show_send_system_info_dialog_if_needed(); - } - }); - } - - // Set PrusaSlicer version and save to PrusaSlicer.ini or PrusaSlicerGcodeViewer.ini. - app_config->set("version", SLIC3R_VERSION); - app_config->save(); - -#ifdef _WIN32 - // Sets window property to mainframe so other instances can indentify it. - OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); -#endif //WIN32 -} - -IMPLEMENT_APP(GUI_App) - -GUI_App::GUI_App(EAppMode mode) - : wxApp() - , m_app_mode(mode) - , m_em_unit(10) - , m_imgui(new ImGuiWrapper()) - , m_removable_drive_manager(std::make_unique()) - , m_other_instance_message_handler(std::make_unique()) -{ - //app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp - this->init_app_config(); - // init app downloader after path to datadir is set - m_app_updater = std::make_unique(); -} - -GUI_App::~GUI_App() -{ - if (app_config != nullptr) - delete app_config; - - if (preset_bundle != nullptr) - delete preset_bundle; - - if (preset_updater != nullptr) - delete preset_updater; -} - -// If formatted for github, plaintext with OpenGL extensions enclosed into
. -// Otherwise HTML formatted for the system info dialog. -std::string GUI_App::get_gl_info(bool for_github) -{ - return OpenGLManager::get_gl_info().to_string(for_github); -} - -wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas) -{ - return m_opengl_mgr.init_glcontext(canvas); -} - -bool GUI_App::init_opengl() -{ -#ifdef __linux__ - bool status = m_opengl_mgr.init_gl(); - m_opengl_initialized = true; - return status; -#else - return m_opengl_mgr.init_gl(); -#endif -} - -// gets path to PrusaSlicer.ini, returns semver from first line comment -static boost::optional parse_semver_from_ini(std::string path) -{ - std::ifstream stream(path); - std::stringstream buffer; - buffer << stream.rdbuf(); - std::string body = buffer.str(); - size_t start = body.find("PrusaSlicer "); - if (start == std::string::npos) - return boost::none; - body = body.substr(start + 12); - size_t end = body.find_first_of(" \n"); - if (end < body.size()) - body.resize(end); - return Semver::parse(body); -} - -void GUI_App::init_app_config() -{ - // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release. -// SetAppName(SLIC3R_APP_KEY); - SetAppName(SLIC3R_APP_KEY "-alpha"); -// SetAppName(SLIC3R_APP_KEY "-beta"); - - -// SetAppDisplayName(SLIC3R_APP_NAME); - - // Set the Slic3r data directory at the Slic3r XS module. - // Unix: ~/ .Slic3r - // Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" - // Mac : "~/Library/Application Support/Slic3r" - - if (data_dir().empty()) { - #ifndef __linux__ - set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()); - #else - // Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}. - // https://github.com/prusa3d/PrusaSlicer/issues/2911 - wxString dir; - if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) - dir = wxFileName::GetHomeDir() + wxS("/.config"); - set_data_dir((dir + "/" + GetAppName()).ToUTF8().data()); - #endif - } else { - m_datadir_redefined = true; - } - - if (!app_config) - app_config = new AppConfig(is_editor() ? AppConfig::EAppMode::Editor : AppConfig::EAppMode::GCodeViewer); - - // load settings - m_app_conf_exists = app_config->exists(); - if (m_app_conf_exists) { - std::string error = app_config->load(); - if (!error.empty()) { - // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - if (is_editor()) { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - else { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - } - } -} - -// returns old config path to copy from if such exists, -// returns an empty string if such config path does not exists or if it cannot be loaded. -std::string GUI_App::check_older_app_config(Semver current_version, bool backup) -{ - std::string older_data_dir_path; - - // If the config folder is redefined - do not check - if (m_datadir_redefined) - return {}; - - // find other version app config (alpha / beta / release) - std::string config_path = app_config->config_path(); - boost::filesystem::path parent_file_path(config_path); - std::string filename = parent_file_path.filename().string(); - parent_file_path.remove_filename().remove_filename(); - - std::vector candidates; - - if (SLIC3R_APP_KEY "-alpha" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-alpha" / filename); - if (SLIC3R_APP_KEY "-beta" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-beta" / filename); - if (SLIC3R_APP_KEY != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY / filename); - - Semver last_semver = current_version; - for (const auto& candidate : candidates) { - if (boost::filesystem::exists(candidate)) { - // parse - boost::optionalother_semver = parse_semver_from_ini(candidate.string()); - if (other_semver && *other_semver > last_semver) { - last_semver = *other_semver; - older_data_dir_path = candidate.parent_path().string(); - } - } - } - if (older_data_dir_path.empty()) - return {}; - BOOST_LOG_TRIVIAL(info) << "last app config file used: " << older_data_dir_path; - // ask about using older data folder - - InfoDialog msg(nullptr - , format_wxstr(_L("You are opening %1% version %2%."), SLIC3R_APP_NAME, SLIC3R_VERSION) - , backup ? - format_wxstr(_L( - "The active configuration was created by %1% %2%," - "\nwhile a newer configuration was found in %3%" - "\ncreated by %1% %4%." - "\n\nShall the newer configuration be imported?" - "\nIf so, your active configuration will be backed up before importing the new configuration." - ) - , SLIC3R_APP_NAME, current_version.to_string(), older_data_dir_path, last_semver.to_string()) - : format_wxstr(_L( - "An existing configuration was found in %3%" - "\ncreated by %1% %2%." - "\n\nShall this configuration be imported?" - ) - , SLIC3R_APP_NAME, last_semver.to_string(), older_data_dir_path) - , true, wxYES_NO); - - if (backup) { - msg.SetButtonLabel(wxID_YES, _L("Import")); - msg.SetButtonLabel(wxID_NO, _L("Don't import")); - } - - if (msg.ShowModal() == wxID_YES) { - std::string snapshot_id; - if (backup) { - const Config::Snapshot* snapshot{ nullptr }; - if (! GUI::Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_USER, "", - _u8L("Continue and import newer configuration?"), &snapshot)) - return {}; - if (snapshot) { - // Save snapshot ID before loading the alternate AppConfig, as loading the alternate AppConfig may fail. - snapshot_id = snapshot->id; - assert(! snapshot_id.empty()); - app_config->set("on_snapshot", snapshot_id); - } else - BOOST_LOG_TRIVIAL(error) << "Failed to take congiguration snapshot"; - } - - // load app config from older file - std::string error = app_config->load((boost::filesystem::path(older_data_dir_path) / filename).string()); - if (!error.empty()) { - // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - if (is_editor()) { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - else { - throw Slic3r::RuntimeError( - _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error.") + - "\n\n" + app_config->config_path() + "\n\n" + error); - } - } - if (!snapshot_id.empty()) - app_config->set("on_snapshot", snapshot_id); - m_app_conf_exists = true; - return older_data_dir_path; - } - return {}; -} - -void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path) -{ - BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path; - m_single_instance_checker = std::make_unique(boost::nowide::widen(name), boost::nowide::widen(path)); -} - -bool GUI_App::OnInit() -{ - try { - return on_init_inner(); - } catch (const std::exception&) { - generic_exception_handle(); - return false; - } -} - -bool GUI_App::on_init_inner() -{ - // Set initialization of image handlers before any UI actions - See GH issue #7469 - wxInitAllImageHandlers(); - -#if defined(_WIN32) && ! defined(_WIN64) - // Win32 32bit build. - if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") { - RichMessageDialog dlg(nullptr, - _L("You are running a 32 bit build of PrusaSlicer on 64-bit Windows." - "\n32 bit build of PrusaSlicer will likely not be able to utilize all the RAM available in the system." - "\nPlease download and install a 64 bit build of PrusaSlicer from https://www.prusa3d.cz/prusaslicer/." - "\nDo you wish to continue?"), - "PrusaSlicer", wxICON_QUESTION | wxYES_NO); - if (dlg.ShowModal() != wxID_YES) - return false; - } -#endif // _WIN64 - - // Forcing back menu icons under gtk2 and gtk3. Solution is based on: - // https://docs.gtk.org/gtk3/class.Settings.html - // see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb - // TODO: Find workaround for GTK4 -#if defined(__WXGTK20__) || defined(__WXGTK3__) - g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL); -#endif - - // Verify resources path - const wxString resources_dir = from_u8(Slic3r::resources_dir()); - wxCHECK_MSG(wxDirExists(resources_dir), false, - wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); - -#ifdef __linux__ - if (! check_old_linux_datadir(GetAppName())) { - std::cerr << "Quitting, user chose to move their data to new location." << std::endl; - return false; - } -#endif - - // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. -// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0); - // Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible - // performance when working on high resolution multi-display setups. -// wxSystemOptions::SetOption("msw.notebook.themed-background", 0); - -// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION; - - // !!! Initialization of UI settings as a language, application color mode, fonts... have to be done before first UI action. - // Like here, before the show InfoDialog in check_older_app_config() - - // If load_language() fails, the application closes. - load_language(wxString(), true); -#ifdef _MSW_DARK_MODE - bool init_dark_color_mode = app_config->get("dark_color_mode") == "1"; - bool init_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; - NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled); -#endif - // initialize label colors and fonts - init_label_colours(); - init_fonts(); - - std::string older_data_dir_path; - if (m_app_conf_exists) { - if (app_config->orig_version().valid() && app_config->orig_version() < *Semver::parse(SLIC3R_VERSION)) - // Only copying configuration if it was saved with a newer slicer than the one currently running. - older_data_dir_path = check_older_app_config(app_config->orig_version(), true); - } else { - // No AppConfig exists, fresh install. Always try to copy from an alternate location, don't make backup of the current configuration. - older_data_dir_path = check_older_app_config(Semver(), false); - } - -#ifdef _MSW_DARK_MODE - // app_config can be updated in check_older_app_config(), so check if dark_color_mode and sys_menu_enabled was changed - 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(); - } - if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; - init_sys_menu_enabled != new_sys_menu_enabled) - NppDarkMode::SetSystemMenuForApp(new_sys_menu_enabled); -#endif - - if (is_editor()) { - std::string msg = Http::tls_global_init(); - std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location"); - bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store(); - - if (!msg.empty() && !ssl_accept) { - RichMessageDialog - dlg(nullptr, - wxString::Format(_L("%s\nDo you want to continue?"), msg), - "PrusaSlicer", wxICON_QUESTION | wxYES_NO); - dlg.ShowCheckBox(_L("Remember my choice")); - if (dlg.ShowModal() != wxID_YES) return false; - - app_config->set("tls_cert_store_accepted", - dlg.IsCheckBoxChecked() ? "yes" : "no"); - app_config->set("tls_accepted_cert_store_location", - dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); - } - } - - SplashScreen* scrn = nullptr; - if (app_config->get("show_splash_screen") == "1") { - // make a bitmap with dark grey banner on the left side - wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var(is_editor() ? "splashscreen.jpg" : "splashscreen-gcodepreview.jpg")), wxBITMAP_TYPE_JPEG)); - - // Detect position (display) to show the splash screen - // Now this position is equal to the mainframe position - wxPoint splashscreen_pos = wxDefaultPosition; - bool default_splashscreen_pos = true; - if (app_config->has("window_mainframe") && app_config->get("restore_win_position") == "1") { - auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe")); - default_splashscreen_pos = metrics == boost::none; - if (!default_splashscreen_pos) - splashscreen_pos = metrics->get_rect().GetPosition(); - } - - if (!default_splashscreen_pos) { - // workaround for crash related to the positioning of the window on secondary monitor - get_app_config()->set("restore_win_position", "crashed_at_splashscreen_pos"); - get_app_config()->save(); - } - - // create splash screen with updated bmp - scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("PrusaSlicer", nullptr, 400), - wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos); - - if (!default_splashscreen_pos) - // revert "restore_win_position" value if application wasn't crashed - get_app_config()->set("restore_win_position", "1"); -#ifndef __linux__ - wxYield(); -#endif - scrn->SetText(_L("Loading configuration")+ dots); - } - - preset_bundle = new PresetBundle(); - - // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory - // supplied as argument to --datadir; in that case we should still run the wizard - preset_bundle->setup_directories(); - - if (! older_data_dir_path.empty()) { - preset_bundle->import_newer_configs(older_data_dir_path); - app_config->save(); - } - - if (is_editor()) { -#ifdef __WXMSW__ - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); -#endif // __WXMSW__ - - preset_updater = new PresetUpdater(); - Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this); - Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) { - if (this->plater_ != nullptr && app_config->get("notify_release") == "all") { - std::string evt_string = into_u8(evt.GetString()); - if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) { - auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable); - this->plater_->get_notification_manager()->push_notification( notif_type - , NotificationManager::NotificationLevel::ImportantNotificationLevel - , Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string) - , _u8L("See Releases page.") - , [](wxEvtHandler* evnthndlr) {wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; } - ); - } - } - }); - Bind(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, [this](const wxCommandEvent& evt) { - //lm:This does not force a render. The progress bar only updateswhen the mouse is moved. - if (this->plater_ != nullptr) - this->plater_->get_notification_manager()->set_download_progress_percentage((float)std::stoi(into_u8(evt.GetString())) / 100.f ); - }); - - Bind(EVT_SLIC3R_APP_DOWNLOAD_FAILED, [this](const wxCommandEvent& evt) { - if (this->plater_ != nullptr) - this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::AppDownload); - if(!evt.GetString().IsEmpty()) - show_error(nullptr, evt.GetString()); - }); - - Bind(EVT_SLIC3R_APP_OPEN_FAILED, [](const wxCommandEvent& evt) { - show_error(nullptr, evt.GetString()); - }); - } - else { -#ifdef __WXMSW__ - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); -#endif // __WXMSW__ - } - - // Suppress the '- default -' presets. - preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1"); - try { - // Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only. - // If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force - // installation of a compatible system preset, thus nullifying the system preset substitutions. - init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent); - } catch (const std::exception &ex) { - show_error(nullptr, ex.what()); - } - -#ifdef WIN32 -#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - register_win32_dpi_event(); -#endif // !wxVERSION_EQUAL_OR_GREATER_THAN - register_win32_device_notification_event(); -#endif // WIN32 - - // Let the libslic3r know the callback, which will translate messages on demand. - Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); - - // application frame - if (scrn && is_editor()) - scrn->SetText(_L("Preparing settings tabs") + dots); - - mainframe = new MainFrame(); - // hide settings tabs after first Layout - if (is_editor()) - mainframe->select_tab(size_t(0)); - - sidebar().obj_list()->init_objects(); // propagate model objects to object list -// update_mode(); // !!! do that later - SetTopWindow(mainframe); - - plater_->init_notification_manager(); - - m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); - - if (is_gcode_viewer()) { - mainframe->update_layout(); - if (plater_ != nullptr) - // ensure the selected technology is ptFFF - plater_->set_printer_technology(ptFFF); - } - else - load_current_presets(); - - // Save the active profiles as a "saved into project". - update_saved_preset_from_current_preset(); - - if (plater_ != nullptr) { - // Save the names of active presets and project specific config into ProjectDirtyStateManager. - plater_->reset_project_dirty_initial_presets(); - // Update Project dirty state, update application title bar. - plater_->update_project_dirty_from_presets(); - } - - mainframe->Show(true); - - obj_list()->set_min_height(); - - update_mode(); // update view mode after fix of the object_list size - -#ifdef __APPLE__ - other_instance_message_handler()->bring_instance_forward(); -#endif //__APPLE__ - - Bind(wxEVT_IDLE, [this](wxIdleEvent& event) - { - if (! plater_) - return; - - this->obj_manipul()->update_if_dirty(); - - // An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT - // and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized. -#ifdef __linux__ - if (! m_post_initialized && m_opengl_initialized) { -#else - if (! m_post_initialized) { -#endif - m_post_initialized = true; -#ifdef WIN32 - this->mainframe->register_win32_callbacks(); -#endif - this->post_init(); - } - - if (m_post_initialized && app_config->dirty() && app_config->get("autosave") == "1") - app_config->save(); - }); - - m_initialized = true; - - if (const std::string& crash_reason = app_config->get("restore_win_position"); - boost::starts_with(crash_reason,"crashed")) - { - wxString preferences_item = _L("Restore window position on start"); - InfoDialog dialog(nullptr, - _L("PrusaSlicer started after a crash"), - format_wxstr(_L("PrusaSlicer crashed last time when attempting to set window position.\n" - "We are sorry for the inconvenience, it unfortunately happens with certain multiple-monitor setups.\n" - "More precise reason for the crash: \"%1%\".\n" - "For more information see our GitHub issue tracker: \"%2%\" and \"%3%\"\n\n" - "To avoid this problem, consider disabling \"%4%\" in \"Preferences\". " - "Otherwise, the application will most likely crash again next time."), - "" + from_u8(crash_reason) + "", - "#2939", - "#5573", - "" + preferences_item + ""), - true, wxYES_NO); - - dialog.SetButtonLabel(wxID_YES, format_wxstr(_L("Disable \"%1%\""), preferences_item)); - dialog.SetButtonLabel(wxID_NO, format_wxstr(_L("Leave \"%1%\" enabled") , preferences_item)); - - auto answer = dialog.ShowModal(); - if (answer == wxID_YES) - app_config->set("restore_win_position", "0"); - else if (answer == wxID_NO) - app_config->set("restore_win_position", "1"); - app_config->save(); - } - - return true; -} - -unsigned GUI_App::get_colour_approx_luma(const wxColour &colour) -{ - double r = colour.Red(); - double g = colour.Green(); - double b = colour.Blue(); - - return std::round(std::sqrt( - r * r * .241 + - g * g * .691 + - b * b * .068 - )); -} - -bool GUI_App::dark_mode() -{ -#if __APPLE__ - // The check for dark mode returns false positive on 10.12 and 10.13, - // which allowed setting dark menu bar and dock area, which is - // is detected as dark mode. We must run on at least 10.14 where the - // proper dark mode was first introduced. - return wxPlatformInfo::Get().CheckOSVersion(10, 14) && mac_dark_mode(); -#else - return wxGetApp().app_config->get("dark_color_mode") == "1" ? true : check_dark_mode(); - //const unsigned luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - //return luma < 128; -#endif -} - -const wxColour GUI_App::get_label_default_clr_system() -{ - return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57); -} - -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() -{ - m_color_label_modified = get_label_default_clr_modified(); - m_color_label_sys = get_label_default_clr_system(); - - bool is_dark_mode = dark_mode(); -#ifdef _WIN32 - m_color_label_default = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); - m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT); - m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); - m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1); - m_color_default_btn_label = is_dark_mode ? wxColour(255, 181, 100): wxColour(203, 61, 0); - m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216); -#else - m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); -#endif - m_color_window_default = is_dark_mode ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); -} - -void GUI_App::update_label_colours_from_appconfig() -{ - if (app_config->has("label_clr_sys")) { - auto str = app_config->get("label_clr_sys"); - if (str != "") - m_color_label_sys = wxColour(str); - } - - if (app_config->has("label_clr_modified")) { - auto str = app_config->get("label_clr_modified"); - if (str != "") - m_color_label_modified = wxColour(str); - } -} - -void GUI_App::update_label_colours() -{ - for (Tab* tab : tabs_list) - tab->update_label_colours(); -} - -#ifdef _WIN32 -static bool is_focused(HWND hWnd) -{ - HWND hFocusedWnd = ::GetFocus(); - return hFocusedWnd && hWnd == hFocusedWnd; -} - -static bool is_default(wxWindow* win) -{ - wxTopLevelWindow* tlw = find_toplevel_parent(win); - if (!tlw) - return false; - - return win == tlw->GetDefaultItem(); -} -#endif - -void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/) -{ -#ifdef _WIN32 - bool is_focused_button = false; - bool is_default_button = false; - if (wxButton* btn = dynamic_cast(window)) { - if (!(btn->GetWindowStyle() & wxNO_BORDER)) { - btn->SetWindowStyle(btn->GetWindowStyle() | wxNO_BORDER); - highlited = true; - } - // button marking - { - auto mark_button = [this, btn, highlited](const bool mark) { - if (btn->GetLabel().IsEmpty()) - btn->SetBackgroundColour(mark ? m_color_selected_btn_bg : highlited ? m_color_highlight_default : m_color_window_default); - else - btn->SetForegroundColour(mark ? m_color_hovered_btn_label : (is_default(btn) ? m_color_default_btn_label : m_color_label_default)); - btn->Refresh(); - btn->Update(); - }; - - // hovering - btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); }); - btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); }); - // focusing - btn->Bind(wxEVT_SET_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); }); - btn->Bind(wxEVT_KILL_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); }); - - is_focused_button = is_focused(btn->GetHWND()); - is_default_button = is_default(btn); - if (is_focused_button || is_default_button) - mark_button(is_focused_button); - } - } - else if (wxTextCtrl* text = dynamic_cast(window)) { - if (text->GetBorder() != wxBORDER_SIMPLE) - text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE); - } - else if (wxCheckListBox* list = dynamic_cast(window)) { - list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE); - list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - for (size_t i = 0; i < list->GetCount(); i++) - if (wxOwnerDrawn* item = list->GetItem(i)) { - item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - item->SetTextColour(m_color_label_default); - } - return; - } - else if (dynamic_cast(window)) - window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE); - - if (!just_font) - window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - if (!is_focused_button && !is_default_button) - window->SetForegroundColour(m_color_label_default); -#endif -} - -// recursive function for scaling fonts for all controls in Window -#ifdef _WIN32 -static void update_dark_children_ui(wxWindow* window, bool just_buttons_update = false) -{ - bool is_btn = dynamic_cast(window) != nullptr; - if (!(just_buttons_update && !is_btn)) - wxGetApp().UpdateDarkUI(window, is_btn); - - auto children = window->GetChildren(); - for (auto child : children) { - update_dark_children_ui(child); - } -} -#endif - -// Note: Don't use this function for Dialog contains ScalableButtons -void GUI_App::UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update/* = false*/) -{ -#ifdef _WIN32 - update_dark_children_ui(dlg, just_buttons_update); -#endif -} -void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/) -{ -#ifdef _WIN32 - UpdateDarkUI(dvc, highlited ? dark_mode() : false); -#ifdef _MSW_DARK_MODE - dvc->RefreshHeaderDarkMode(&m_normal_font); -#endif //_MSW_DARK_MODE - if (dvc->HasFlag(wxDV_ROW_LINES)) - dvc->SetAlternateRowColour(m_color_highlight_default); - if (dvc->GetBorder() != wxBORDER_SIMPLE) - dvc->SetWindowStyle(dvc->GetWindowStyle() | wxBORDER_SIMPLE); -#endif -} - -void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent) -{ -#ifdef _WIN32 - wxGetApp().UpdateDarkUI(parent); - - auto children = parent->GetChildren(); - for (auto child : children) { - if (dynamic_cast(child)) - child->SetForegroundColour(m_color_label_default); - } -#endif -} - -void GUI_App::init_fonts() -{ - m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); - m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - -#ifdef __WXMAC__ - m_small_font.SetPointSize(11); - m_bold_font.SetPointSize(13); -#endif /*__WXMAC__*/ - - // wxSYS_OEM_FIXED_FONT and wxSYS_ANSI_FIXED_FONT use the same as - // DEFAULT in wxGtk. Use the TELETYPE family as a work-around - m_code_font = wxFont(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)); - m_code_font.SetPointSize(m_normal_font.GetPointSize()); -} - -void GUI_App::update_fonts(const MainFrame *main_frame) -{ - /* Only normal and bold fonts are used for an application rescale, - * because of under MSW small and normal fonts are the same. - * To avoid same rescaling twice, just fill this values - * from rescaled MainFrame - */ - if (main_frame == nullptr) - main_frame = this->mainframe; - m_normal_font = main_frame->normal_font(); - m_small_font = m_normal_font; - m_bold_font = main_frame->normal_font().Bold(); - m_link_font = m_bold_font.Underlined(); - m_em_unit = main_frame->em_unit(); - m_code_font.SetPointSize(m_normal_font.GetPointSize()); -} - -void GUI_App::set_label_clr_modified(const wxColour& clr) -{ - if (m_color_label_modified == clr) - return; - m_color_label_modified = clr; - const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); - app_config->set("label_clr_modified", str); - app_config->save(); -} - -void GUI_App::set_label_clr_sys(const wxColour& clr) -{ - if (m_color_label_sys == clr) - return; - m_color_label_sys = clr; - const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); - app_config->set("label_clr_sys", str); - app_config->save(); -} - -bool GUI_App::tabs_as_menu() const -{ - return app_config->get("tabs_as_menu") == "1"; // || dark_mode(); -} - -wxSize GUI_App::get_min_size() const -{ - return wxSize(76*m_em_unit, 49 * m_em_unit); -} - -float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const -{ -#ifdef __APPLE__ - const float icon_sc = 1.0f; // for Retina display will be used its own scale -#else - const float icon_sc = m_em_unit*0.1f; -#endif // __APPLE__ - - const std::string& use_val = app_config->get("use_custom_toolbar_size"); - const std::string& val = app_config->get("custom_toolbar_size"); - const std::string& auto_val = app_config->get("auto_toolbar_size"); - - if (val.empty() || auto_val.empty() || use_val.empty()) - return icon_sc; - - int int_val = use_val == "0" ? 100 : atoi(val.c_str()); - // correct value in respect to auto_toolbar_size - int_val = std::min(atoi(auto_val.c_str()), int_val); - - if (is_limited && int_val < 50) - int_val = 50; - - return 0.01f * int_val * icon_sc; -} - -void GUI_App::set_auto_toolbar_icon_scale(float scale) const -{ -#ifdef __APPLE__ - const float icon_sc = 1.0f; // for Retina display will be used its own scale -#else - const float icon_sc = m_em_unit * 0.1f; -#endif // __APPLE__ - - long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100); - std::string val = std::to_string(int_val); - - app_config->set("auto_toolbar_size", val); -} - -// check user printer_presets for the containing information about "Print Host upload" -void GUI_App::check_printer_presets() -{ - std::vector preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers); - if (preset_names.empty()) - return; - - wxString msg_text = _L("You have the following presets with saved options for \"Print Host upload\"") + ":"; - for (const std::string& preset_name : preset_names) - msg_text += "\n \"" + from_u8(preset_name) + "\","; - msg_text.RemoveLast(); - msg_text += "\n\n" + _L("But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n" - "Settings will be available in physical printers settings.") + "\n\n" + - _L("By default new Printer devices will be named as \"Printer N\" during its creation.\n" - "Note: This name can be changed later from the physical printers settings"); - - //wxMessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); - MessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); - - preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers); -} - -void GUI_App::recreate_GUI(const wxString& msg_name) -{ - m_is_recreating_gui = true; - - mainframe->shutdown(); - - wxProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE); - dlg.Pulse(); - dlg.Update(10, _L("Recreating") + dots); - - MainFrame *old_main_frame = mainframe; - mainframe = new MainFrame(); - if (is_editor()) - // hide settings tabs after first Layout - mainframe->select_tab(size_t(0)); - // Propagate model objects to object list. - sidebar().obj_list()->init_objects(); - SetTopWindow(mainframe); - - dlg.Update(30, _L("Recreating") + dots); - old_main_frame->Destroy(); - - dlg.Update(80, _L("Loading of current presets") + dots); - m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); - load_current_presets(); - mainframe->Show(true); - - dlg.Update(90, _L("Loading of a mode view") + dots); - - obj_list()->set_min_height(); - update_mode(); - - // #ys_FIXME_delete_after_testing Do we still need this ? -// CallAfter([]() { -// // Run the config wizard, don't offer the "reset user profile" checkbox. -// config_wizard_startup(true); -// }); - - m_is_recreating_gui = false; -} - -void GUI_App::system_info() -{ - SysInfoDialog dlg; - dlg.ShowModal(); -} - -void GUI_App::keyboard_shortcuts() -{ - KBShortcutsDialog dlg; - dlg.ShowModal(); -} - -// static method accepting a wxWindow object as first parameter -bool GUI_App::catch_error(std::function cb, - // wxMessageDialog* message_dialog, - const std::string& err /*= ""*/) -{ - if (!err.empty()) { - if (cb) - cb(); - // if (message_dialog) - // message_dialog->(err, "Error", wxOK | wxICON_ERROR); - show_error(/*this*/nullptr, err); - return true; - } - return false; -} - -// static method accepting a wxWindow object as first parameter -void fatal_error(wxWindow* parent) -{ - show_error(parent, ""); - // exit 1; // #ys_FIXME -} - -#ifdef _WIN32 - -#ifdef _MSW_DARK_MODE -static void update_scrolls(wxWindow* window) -{ - wxWindowList::compatibility_iterator node = window->GetChildren().GetFirst(); - while (node) - { - wxWindow* win = node->GetData(); - if (dynamic_cast(win) || - dynamic_cast(win) || - dynamic_cast(win)) - NppDarkMode::SetDarkExplorerTheme(win->GetHWND()); - - update_scrolls(win); - node = node->GetNext(); - } -} -#endif //_MSW_DARK_MODE - - -#ifdef _MSW_DARK_MODE -void GUI_App::force_menu_update() -{ - NppDarkMode::SetSystemMenuForApp(app_config->get("sys_menu_enabled") == "1"); -} -#endif //_MSW_DARK_MODE - -void GUI_App::force_colors_update() -{ -#ifdef _MSW_DARK_MODE - NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1"); - if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl()) - NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND); - NppDarkMode::SetDarkTitleBar(mainframe->GetHWND()); - NppDarkMode::SetDarkTitleBar(mainframe->m_settings_dialog.GetHWND()); -#endif //_MSW_DARK_MODE - m_force_colors_update = true; -} -#endif //_WIN32 - -// Called after the Preferences dialog is closed and the program settings are saved. -// Update the UI based on the current preferences. -void GUI_App::update_ui_from_settings() -{ - update_label_colours(); -#ifdef _WIN32 - // Upadte UI colors before Update UI from settings - if (m_force_colors_update) { - m_force_colors_update = false; - mainframe->force_color_changed(); - mainframe->diff_dialog.force_color_changed(); - mainframe->preferences_dialog->force_color_changed(); - mainframe->printhost_queue_dlg()->force_color_changed(); -#ifdef _MSW_DARK_MODE - update_scrolls(mainframe); - if (mainframe->is_dlg_layout()) { - // update for tabs bar - UpdateDarkUI(&mainframe->m_settings_dialog); - mainframe->m_settings_dialog.Fit(); - mainframe->m_settings_dialog.Refresh(); - // update scrollbars - update_scrolls(&mainframe->m_settings_dialog); - } -#endif //_MSW_DARK_MODE - } -#endif - mainframe->update_ui_from_settings(); -} - -void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized) -{ - const std::string name = into_u8(window->GetName()); - - window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) { - window_pos_save(window, name); - event.Skip(); - }); - - window_pos_restore(window, name, default_maximized); - - on_window_geometry(window, [=]() { - window_pos_sanitize(window); - }); -} - -void GUI_App::load_project(wxWindow *parent, wxString& input_file) const -{ - input_file.Clear(); - wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one file (3MF/AMF):"), - app_config->get_last_dir(), "", - file_wildcards(FT_PROJECT), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) - input_file = dialog.GetPath(); -} - -void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const -{ - input_files.Clear(); - wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):"), - from_u8(app_config->get_last_dir()), "", - file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) - dialog.GetPaths(input_files); -} - -void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const -{ - input_file.Clear(); - wxFileDialog dialog(parent ? parent : GetTopWindow(), - _L("Choose one file (GCODE/.GCO/.G/.ngc/NGC):"), - app_config->get_last_dir(), "", - file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - if (dialog.ShowModal() == wxID_OK) - input_file = dialog.GetPath(); -} - -bool GUI_App::switch_language() -{ - if (select_language()) { - recreate_GUI(_L("Changing of an application language") + dots); - return true; - } else { - return false; - } -} - -#ifdef __linux__ -static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language, - const wxLanguageInfo* system_language) -{ - constexpr size_t max_len = 50; - char path[max_len] = ""; - std::vector locales; - const std::string lang_prefix = into_u8(language->CanonicalName.BeforeFirst('_')); - - // Call locale -a so we can parse the output to get the list of available locales - // We expect lines such as "en_US.utf8". Pick ones starting with the language code - // we are switching to. Lines with different formatting will be removed later. - FILE* fp = popen("locale -a", "r"); - if (fp != NULL) { - while (fgets(path, max_len, fp) != NULL) { - std::string line(path); - line = line.substr(0, line.find('\n')); - if (boost::starts_with(line, lang_prefix)) - locales.push_back(line); - } - pclose(fp); - } - - // locales now contain all candidates for this language. - // Sort them so ones containing anything about UTF-8 are at the end. - std::sort(locales.begin(), locales.end(), [](const std::string& a, const std::string& b) - { - auto has_utf8 = [](const std::string & s) { - auto S = boost::to_upper_copy(s); - return S.find("UTF8") != std::string::npos || S.find("UTF-8") != std::string::npos; - }; - return ! has_utf8(a) && has_utf8(b); - }); - - // Remove the suffix behind a dot, if there is one. - for (std::string& s : locales) - s = s.substr(0, s.find(".")); - - // We just hope that dear Linux "locale -a" returns country codes - // in ISO 3166-1 alpha-2 code (two letter) format. - // https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes - // To be sure, remove anything not looking as expected - // (any number of lowercase letters, underscore, two uppercase letters). - locales.erase(std::remove_if(locales.begin(), - locales.end(), - [](const std::string& s) { - return ! std::regex_match(s, - std::regex("^[a-z]+_[A-Z]{2}$")); - }), - locales.end()); - - // Is there a candidate matching a country code of a system language? Move it to the end, - // while maintaining the order of matches, so that the best match ends up at the very end. - std::string system_country = "_" + into_u8(system_language->CanonicalName.AfterFirst('_')).substr(0, 2); - int cnt = locales.size(); - for (int i=0; iempty()) { - const std::string &locale = *it; - const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale)); - if (wxLocale::IsAvailable(lang->Language)) - return lang; - } - return language; -} -#endif - -int GUI_App::GetSingleChoiceIndex(const wxString& message, - const wxString& caption, - const wxArrayString& choices, - int initialSelection) -{ -#ifdef _WIN32 - wxSingleChoiceDialog dialog(nullptr, message, caption, choices); - wxGetApp().UpdateDlgDarkUI(&dialog); - - dialog.SetSelection(initialSelection); - return dialog.ShowModal() == wxID_OK ? dialog.GetSelection() : -1; -#else - return wxGetSingleChoiceIndex(message, caption, choices, initialSelection); -#endif -} - -// select language from the list of installed languages -bool GUI_App::select_language() -{ - wxArrayString translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY); - std::vector language_infos; - language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH)); - for (size_t i = 0; i < translations.GetCount(); ++ i) { - const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]); - if (langinfo != nullptr) - language_infos.emplace_back(langinfo); - } - sort_remove_duplicates(language_infos); - std::sort(language_infos.begin(), language_infos.end(), [](const wxLanguageInfo* l, const wxLanguageInfo* r) { return l->Description < r->Description; }); - - wxArrayString names; - names.Alloc(language_infos.size()); - - // Some valid language should be selected since the application start up. - const wxLanguage current_language = wxLanguage(m_wxLocale->GetLanguage()); - int init_selection = -1; - int init_selection_alt = -1; - int init_selection_default = -1; - for (size_t i = 0; i < language_infos.size(); ++ i) { - if (wxLanguage(language_infos[i]->Language) == current_language) - // The dictionary matches the active language and country. - init_selection = i; - else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == m_wxLocale->GetCanonicalName().BeforeFirst('_')) || - // if the active language is Slovak, mark the Czech language as active. - (language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && m_wxLocale->GetCanonicalName().BeforeFirst('_') == "sk")) - // The dictionary matches the active language, it does not necessarily match the country. - init_selection_alt = i; - if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en") - // This will be the default selection if the active language does not match any dictionary. - init_selection_default = i; - names.Add(language_infos[i]->Description); - } - if (init_selection == -1) - // This is the dictionary matching the active language. - init_selection = init_selection_alt; - if (init_selection != -1) - // This is the language to highlight in the choice dialog initially. - init_selection_default = init_selection; - - const long index = GetSingleChoiceIndex(_L("Select the language"), _L("Language"), names, init_selection_default); - // Try to load a new language. - if (index != -1 && (init_selection == -1 || init_selection != index)) { - const wxLanguageInfo *new_language_info = language_infos[index]; - if (this->load_language(new_language_info->CanonicalName, false)) { - // Save language at application config. - // Which language to save as the selected dictionary language? - // 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its - // stability in the future: - // wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); - // 2) Current locale language may not match the dictionary name, see GH issue #3901 - // m_wxLocale->GetCanonicalName() - // 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name. - app_config->set("translation_language", new_language_info->CanonicalName.ToUTF8().data()); - app_config->save(); - return true; - } - } - - return false; -} - -// Load gettext translation files and activate them at the start of the application, -// based on the "translation_language" key stored in the application config. -bool GUI_App::load_language(wxString language, bool initial) -{ - if (initial) { - // There is a static list of lookup path prefixes in wxWidgets. Add ours. - wxFileTranslationsLoader::AddCatalogLookupPathPrefix(from_u8(localization_dir())); - // Get the active language from PrusaSlicer.ini, or empty string if the key does not exist. - language = app_config->get("translation_language"); - if (! language.empty()) - BOOST_LOG_TRIVIAL(trace) << boost::format("translation_language provided by PrusaSlicer.ini: %1%") % language; - - // Get the system language. - { - const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); - if (lang_system != wxLANGUAGE_UNKNOWN) { - m_language_info_system = wxLocale::GetLanguageInfo(lang_system); - BOOST_LOG_TRIVIAL(trace) << boost::format("System language detected (user locales and such): %1%") % m_language_info_system->CanonicalName.ToUTF8().data(); - } - } - { - // Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance. - wxLocale temp_locale; - // Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code). - wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT); - // Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer - // and try to match them with the system specific "preferred languages". - // There seems to be a support for that on Windows and OSX, while on Linuxes the code just returns wxLocale::GetSystemLanguage(). - // The last parameter gets added to the list of detected dictionaries. This is a workaround - // for not having the English dictionary. Let's hope wxWidgets of various versions process this call the same way. - wxString best_language = wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); - if (! best_language.IsEmpty()) { - m_language_info_best = wxLocale::FindLanguageInfo(best_language); - BOOST_LOG_TRIVIAL(trace) << boost::format("Best translation language detected (may be different from user locales): %1%") % m_language_info_best->CanonicalName.ToUTF8().data(); - } - #ifdef __linux__ - wxString lc_all; - if (wxGetEnv("LC_ALL", &lc_all) && ! lc_all.IsEmpty()) { - // Best language returned by wxWidgets on Linux apparently does not respect LC_ALL. - // Disregard the "best" suggestion in case LC_ALL is provided. - m_language_info_best = nullptr; - } - #endif - } - } - - const wxLanguageInfo *language_info = language.empty() ? nullptr : wxLocale::FindLanguageInfo(language); - if (! language.empty() && (language_info == nullptr || language_info->CanonicalName.empty())) { - // Fix for wxWidgets issue, where the FindLanguageInfo() returns locales with undefined ANSII code (wxLANGUAGE_KONKANI or wxLANGUAGE_MANIPURI). - language_info = nullptr; - BOOST_LOG_TRIVIAL(error) << boost::format("Language code \"%1%\" is not supported") % language.ToUTF8().data(); - } - - if (language_info != nullptr && language_info->LayoutDirection == wxLayout_RightToLeft) { - BOOST_LOG_TRIVIAL(trace) << boost::format("The following language code requires right to left layout, which is not supported by PrusaSlicer: %1%") % language_info->CanonicalName.ToUTF8().data(); - language_info = nullptr; - } - - if (language_info == nullptr) { - // PrusaSlicer does not support the Right to Left languages yet. - if (m_language_info_system != nullptr && m_language_info_system->LayoutDirection != wxLayout_RightToLeft) - language_info = m_language_info_system; - if (m_language_info_best != nullptr && m_language_info_best->LayoutDirection != wxLayout_RightToLeft) - language_info = m_language_info_best; - if (language_info == nullptr) - language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US); - } - - BOOST_LOG_TRIVIAL(trace) << boost::format("Switching wxLocales to %1%") % language_info->CanonicalName.ToUTF8().data(); - - // Alternate language code. - wxLanguage language_dict = wxLanguage(language_info->Language); - if (language_info->CanonicalName.BeforeFirst('_') == "sk") { - // Slovaks understand Czech well. Give them the Czech translation. - language_dict = wxLANGUAGE_CZECH; - BOOST_LOG_TRIVIAL(trace) << "Using Czech dictionaries for Slovak language"; - } - - // Select language for locales. This language may be different from the language of the dictionary. - if (language_info == m_language_info_best || language_info == m_language_info_system) { - // The current language matches user's default profile exactly. That's great. - } else if (m_language_info_best != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_best->CanonicalName.BeforeFirst('_')) { - // Use whatever the operating system recommends, if it the language code of the dictionary matches the recommended language. - // This allows a Swiss guy to use a German dictionary without forcing him to German locales. - language_info = m_language_info_best; - } else if (m_language_info_system != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_')) - language_info = m_language_info_system; - -#ifdef __linux__ - // If we can't find this locale , try to use different one for the language - // instead of just reporting that it is impossible to switch. - if (! wxLocale::IsAvailable(language_info->Language)) { - std::string original_lang = into_u8(language_info->CanonicalName); - language_info = linux_get_existing_locale_language(language_info, m_language_info_system); - BOOST_LOG_TRIVIAL(trace) << boost::format("Can't switch language to %1% (missing locales). Using %2% instead.") - % original_lang % language_info->CanonicalName.ToUTF8().data(); - } -#endif - - if (! wxLocale::IsAvailable(language_info->Language)) { - // Loading the language dictionary failed. - wxString message = "Switching PrusaSlicer to language " + language_info->CanonicalName + " failed."; -#if !defined(_WIN32) && !defined(__APPLE__) - // likely some linux system - message += "\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n"; -#endif - if (initial) - message + "\n\nApplication will close."; - wxMessageBox(message, "PrusaSlicer - Switching language failed", wxOK | wxICON_ERROR); - if (initial) - std::exit(EXIT_FAILURE); - else - return false; - } - - // Release the old locales, create new locales. - //FIXME wxWidgets cause havoc if the current locale is deleted. We just forget it causing memory leaks for now. - m_wxLocale.release(); - m_wxLocale = Slic3r::make_unique(); - m_wxLocale->Init(language_info->Language); - // 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); - 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. - //wxSetlocale(LC_NUMERIC, "C"); - Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data()); - return true; -} - -Tab* GUI_App::get_tab(Preset::Type type) -{ - for (Tab* tab: tabs_list) - if (tab->type() == type) - return tab->completed() ? tab : nullptr; // To avoid actions with no-completed Tab - return nullptr; -} - -ConfigOptionMode GUI_App::get_mode() -{ - if (!app_config->has("view_mode")) - return comSimple; - - const auto mode = app_config->get("view_mode"); - return mode == "expert" ? comExpert : - mode == "simple" ? comSimple : comAdvanced; -} - -void GUI_App::save_mode(const /*ConfigOptionMode*/int mode) -{ - const std::string mode_str = mode == comExpert ? "expert" : - mode == comSimple ? "simple" : "advanced"; - app_config->set("view_mode", mode_str); - app_config->save(); - update_mode(); -} - -// Update view mode according to selected menu -void GUI_App::update_mode() -{ - sidebar().update_mode(); - -#ifdef _MSW_DARK_MODE - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); -#endif - - for (auto tab : tabs_list) - tab->update_mode(); - - plater()->update_menus(); - plater()->canvas3D()->update_gizmos_on_off_state(); -} - -void GUI_App::add_config_menu(wxMenuBar *menu) -{ - auto local_menu = new wxMenu(); - wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt)); - - const auto config_wizard_name = _(ConfigWizard::name(true)); - const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Run %s"))) % config_wizard_name).str()); - // Cmd+, is standard on OS X - what about other operating systems? - if (is_editor()) { - local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); - local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots")); - local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot")); - local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates")); - local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application")); -#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - //if (DesktopIntegrationDialog::integration_possible()) - local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration")); -#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) - local_menu->AppendSeparator(); - } - local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots + -#ifdef __APPLE__ - "\tCtrl+,", -#else - "\tCtrl+P", -#endif - _L("Application preferences")); - wxMenu* mode_menu = nullptr; - if (is_editor()) { - local_menu->AppendSeparator(); - mode_menu = new wxMenu(); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple View Mode")); -// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced View Mode")); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), _L("Advanced View Mode")); - mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _L("Expert"), _L("Expert View Mode")); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced); - Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comExpert) evt.Check(true); }, config_id_base + ConfigMenuModeExpert); - - local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s View Mode"), SLIC3R_APP_NAME)); - } - local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuLanguage, _L("&Language")); - if (is_editor()) { - local_menu->AppendSeparator(); - local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _L("Flash Printer &Firmware"), _L("Upload a firmware image into an Arduino based printer")); - // TODO: for when we're able to flash dictionaries - // local_menu->Append(config_id_base + FirmwareMenuDict, _L("Flash Language File"), _L("Upload a language dictionary file into a Prusa printer")); - } - - local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) { - switch (event.GetId() - config_id_base) { - case ConfigMenuWizard: - run_wizard(ConfigWizard::RR_USER); - break; - case ConfigMenuUpdateConf: - check_updates(true); - break; - case ConfigMenuUpdateApp: - app_version_check(true); - break; -#ifdef __linux__ - case ConfigMenuDesktopIntegration: - show_desktop_integration_dialog(); - break; -#endif - case ConfigMenuTakeSnapshot: - // Take a configuration snapshot. - if (wxString action_name = _L("Taking a configuration snapshot"); - check_and_save_current_preset_changes(action_name, _L("Some presets are modified and the unsaved changes will not be captured by the configuration snapshot."), false, true)) { - wxTextEntryDialog dlg(nullptr, action_name, _L("Snapshot name")); - UpdateDlgDarkUI(&dlg); - - // set current normal font for dialog children, - // because of just dlg.SetFont(normal_font()) has no result; - for (auto child : dlg.GetChildren()) - child->SetFont(normal_font()); - - if (dlg.ShowModal() == wxID_OK) - if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error( - *app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()); - snapshot != nullptr) - app_config->set("on_snapshot", snapshot->id); - } - break; - case ConfigMenuSnapshots: - if (check_and_save_current_preset_changes(_L("Loading a configuration snapshot"), "", false)) { - std::string on_snapshot; - if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) - on_snapshot = app_config->get("on_snapshot"); - ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); - dlg.ShowModal(); - if (!dlg.snapshot_to_activate().empty()) { - if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) && - ! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "", - GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate()))) - break; - try { - app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); - // Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system - // presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users - // are known to be creative and mess with the config files in various ways. - if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable); - ! all_substitutions.empty()) - show_substitutions_info(all_substitutions); - - // Load the currently selected preset into the GUI, update the preset selection box. - load_current_presets(); - } catch (std::exception &ex) { - GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what())); - } - } - } - break; - case ConfigMenuPreferences: - { - open_preferences(); - break; - } - case ConfigMenuLanguage: - { - /* Before change application language, let's check unsaved changes on 3D-Scene - * and draw user's attention to the application restarting after a language change - */ - { - // the dialog needs to be destroyed before the call to switch_language() - // or sometimes the application crashes into wxDialogBase() destructor - // so we put it into an inner scope - wxString title = is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME); - title += " - " + _L("Language selection"); - //wxMessageDialog dialog(nullptr, - MessageDialog dialog(nullptr, - _L("Switching the language will trigger application restart.\n" - "You will lose content of the plater.") + "\n\n" + - _L("Do you want to proceed?"), - title, - wxICON_QUESTION | wxOK | wxCANCEL); - if (dialog.ShowModal() == wxID_CANCEL) - return; - } - - switch_language(); - break; - } - case ConfigMenuFlashFirmware: - FirmwareDialog::run(mainframe); - break; - default: - break; - } - }); - - using std::placeholders::_1; - - if (mode_menu != nullptr) { - auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); }; - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple); - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced); - mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comExpert, _1), config_id_base + ConfigMenuModeExpert); - } - - menu->Append(local_menu, _L("&Configuration")); -} - -void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/) -{ - mainframe->preferences_dialog->show(highlight_option, tab_name); - - if (mainframe->preferences_dialog->recreate_GUI()) - recreate_GUI(_L("Restart application") + dots); - -#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER - if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) -#else - if (mainframe->preferences_dialog->seq_top_layer_only_changed()) -#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER - this->plater_->refresh_print(); - -#ifdef _WIN32 - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#endif // _WIN32 - - if (mainframe->preferences_dialog->settings_layout_changed()) { - // hide full main_sizer for mainFrame - mainframe->GetSizer()->Show(false); - mainframe->update_layout(); - mainframe->select_tab(size_t(0)); - } -} - -bool GUI_App::has_unsaved_preset_changes() const -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* const tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty()) - return true; - } - return false; -} - -bool GUI_App::has_current_preset_changes() const -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* const tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) - return true; - } - return false; -} - -void GUI_App::update_saved_preset_from_current_preset() -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (Tab* tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology)) - tab->update_saved_preset_from_current_preset(); - } -} - -std::vector GUI_App::get_active_preset_collections() const -{ - std::vector ret; - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* tab : tabs_list) - if (tab->supports_printer_technology(printer_technology)) - ret.push_back(tab->get_presets()); - return ret; -} - -// To notify the user whether he is aware that some preset changes will be lost, -// UnsavedChangesDialog: "Discard / Save / Cancel" -// This is called when: -// - Close Application & Current project isn't saved -// - Load Project & Current project isn't saved -// - Undo / Redo with change of print technologie -// - Loading snapshot -// - Loading config_file/bundle -// UnsavedChangesDialog: "Don't save / Save / Cancel" -// This is called when: -// - Exporting config_bundle -// - Taking snapshot -bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice/* = true*/, bool dont_save_insted_of_discard/* = false*/) -{ - if (has_current_preset_changes()) { - const std::string app_config_key = remember_choice ? "default_action_on_close_application" : ""; - int act_buttons = UnsavedChangesDialog::ActionButtons::SAVE; - if (dont_save_insted_of_discard) - act_buttons |= UnsavedChangesDialog::ActionButtons::DONT_SAVE; - UnsavedChangesDialog dlg(caption, header, app_config_key, act_buttons); - std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); - if (act == "none" && dlg.ShowModal() == wxID_CANCEL) - return false; - - if (dlg.save_preset()) // save selected changes - { - for (const std::pair& nt : dlg.get_names_and_types()) - preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); - - load_current_presets(false); - - // if we saved changes to the new presets, we should to - // synchronize config.ini with the current selections. - preset_bundle->export_selections(*app_config); - - MessageDialog(nullptr, _L_PLURAL("The preset modifications are successfully saved", - "The presets modifications are successfully saved", dlg.get_names_and_types().size())).ShowModal(); - } - } - - return true; -} - -void GUI_App::apply_keeped_preset_modifications() -{ - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (Tab* tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology)) - tab->apply_config_from_cache(); - } - load_current_presets(false); -} - -// This is called when creating new project or load another project -// OR close ConfigWizard -// to ask the user what should we do with unsaved changes for presets. -// New Project => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Cancel" -// => Current project isn't saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" -// Close ConfigWizard => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" -// Note: no_nullptr postponed_apply_of_keeped_changes indicates that thie function is called after ConfigWizard is closed -bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes/* = nullptr*/) -{ - if (has_current_preset_changes()) { - bool is_called_from_configwizard = postponed_apply_of_keeped_changes != nullptr; - - const std::string app_config_key = is_called_from_configwizard ? "" : "default_action_on_new_project"; - UnsavedChangesDialog dlg(caption, header, app_config_key, action_buttons); - std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); - if (act == "none" && dlg.ShowModal() == wxID_CANCEL) - return false; - - auto reset_modifications = [this, is_called_from_configwizard]() { - if (is_called_from_configwizard) - return; // no need to discared changes. It will be done fromConfigWizard closing - - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (const Tab* const tab : tabs_list) { - if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) - tab->m_presets->discard_current_changes(); - } - load_current_presets(false); - }; - - if (dlg.discard()) - reset_modifications(); - else // save selected changes - { - const auto& preset_names_and_types = dlg.get_names_and_types(); - if (dlg.save_preset()) { - for (const std::pair& nt : preset_names_and_types) - preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); - - // if we saved changes to the new presets, we should to - // synchronize config.ini with the current selections. - preset_bundle->export_selections(*app_config); - - wxString text = _L_PLURAL("The preset modifications are successfully saved", - "The presets modifications are successfully saved", preset_names_and_types.size()); - if (!is_called_from_configwizard) - text += "\n\n" + _L("For new project all modifications will be reseted"); - - MessageDialog(nullptr, text).ShowModal(); - reset_modifications(); - } - else if (dlg.transfer_changes() && (dlg.has_unselected_options() || is_called_from_configwizard)) { - // execute this part of code only if not all modifications are keeping to the new project - // OR this function is called when ConfigWizard is closed and "Keep modifications" is selected - for (const std::pair& nt : preset_names_and_types) { - Preset::Type type = nt.second; - Tab* tab = get_tab(type); - std::vector selected_options = dlg.get_selected_options(type); - if (type == Preset::TYPE_PRINTER) { - auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); - if (it != selected_options.end()) { - // erase "extruders_count" option from the list - selected_options.erase(it); - // cache the extruders count - static_cast(tab)->cache_extruder_cnt(); - } - } - tab->cache_config_diff(selected_options); - if (!is_called_from_configwizard) - tab->m_presets->discard_current_changes(); - } - if (is_called_from_configwizard) - *postponed_apply_of_keeped_changes = true; - else - apply_keeped_preset_modifications(); - } - } - } - - return true; -} - -bool GUI_App::can_load_project() -{ - int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified.")); - if (saved_project == wxID_CANCEL || - (plater()->is_project_dirty() && saved_project == wxID_NO && - !check_and_save_current_preset_changes(_L("Project is loading"), _L("Opening new project while some presets are unsaved.")))) - return false; - return true; -} - -bool GUI_App::check_print_host_queue() -{ - wxString dirty; - std::vector> jobs; - // Get ongoing jobs from dialog - mainframe->m_printhost_queue_dlg->get_active_jobs(jobs); - if (jobs.empty()) - return true; - // Show dialog - wxString job_string = wxString(); - for (const auto& job : jobs) { - job_string += format_wxstr(" %1% : %2% \n", job.first, job.second); - } - wxString message; - message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?")); - //wxMessageDialog dialog(mainframe, - MessageDialog dialog(mainframe, - message, - wxString(SLIC3R_APP_NAME) + " - " + _(L("Ongoing uploads")), - wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - if (dialog.ShowModal() == wxID_YES) - return true; - - // TODO: If already shown, bring forward - mainframe->m_printhost_queue_dlg->Show(); - return false; -} - -bool GUI_App::checked_tab(Tab* tab) -{ - bool ret = true; - if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end()) - ret = false; - return ret; -} - -// Update UI / Tabs to reflect changes in the currently loaded presets -void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/) -{ - // check printer_presets for the containing information about "Print Host upload" - // and create physical printer from it, if any exists - if (check_printer_presets_) - check_printer_presets(); - - PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - this->plater()->set_printer_technology(printer_technology); - for (Tab *tab : tabs_list) - if (tab->supports_printer_technology(printer_technology)) { - if (tab->type() == Preset::TYPE_PRINTER) { - static_cast(tab)->update_pages(); - // Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change(). - this->plater()->force_print_bed_update(); - } - tab->load_current_preset(); - } -} - -bool GUI_App::OnExceptionInMainLoop() -{ - generic_exception_handle(); - return false; -} - -#ifdef __APPLE__ -// This callback is called from wxEntry()->wxApp::CallOnInit()->NSApplication run -// that is, before GUI_App::OnInit(), so we have a chance to switch GUI_App -// to a G-code viewer. -void GUI_App::OSXStoreOpenFiles(const wxArrayString &fileNames) -{ - size_t num_gcodes = 0; - for (const wxString &filename : fileNames) - if (is_gcode_file(into_u8(filename))) - ++ num_gcodes; - if (fileNames.size() == num_gcodes) { - // Opening PrusaSlicer by drag & dropping a G-Code onto PrusaSlicer icon in Finder, - // just G-codes were passed. Switch to G-code viewer mode. - m_app_mode = EAppMode::GCodeViewer; - unlock_lockfile(get_instance_hash_string() + ".lock", data_dir() + "/cache/"); - if(app_config != nullptr) - delete app_config; - app_config = nullptr; - init_app_config(); - } - wxApp::OSXStoreOpenFiles(fileNames); -} -// wxWidgets override to get an event on open files. -void GUI_App::MacOpenFiles(const wxArrayString &fileNames) -{ - std::vector files; - std::vector gcode_files; - std::vector non_gcode_files; - for (const auto& filename : fileNames) { - if (is_gcode_file(into_u8(filename))) - gcode_files.emplace_back(filename); - else { - files.emplace_back(into_u8(filename)); - non_gcode_files.emplace_back(filename); - } - } - if (m_app_mode == EAppMode::GCodeViewer) { - // Running in G-code viewer. - // Load the first G-code into the G-code viewer. - // Or if no G-codes, send other files to slicer. - if (! gcode_files.empty()) { - if (m_post_initialized) - this->plater()->load_gcode(gcode_files.front()); - else - this->init_params->input_files = { into_u8(gcode_files.front()) }; - } - if (!non_gcode_files.empty()) - start_new_slicer(non_gcode_files, true); - } else { - if (! files.empty()) { - if (m_post_initialized) { - wxArrayString input_files; - for (size_t i = 0; i < non_gcode_files.size(); ++i) - input_files.push_back(non_gcode_files[i]); - this->plater()->load_files(input_files); - } else { - for (const auto &f : non_gcode_files) - this->init_params->input_files.emplace_back(into_u8(f)); - } - } - for (const wxString &filename : gcode_files) - start_new_gcodeviewer(&filename); - } -} -#endif /* __APPLE */ - -Sidebar& GUI_App::sidebar() -{ - return plater_->sidebar(); -} - -ObjectManipulation* GUI_App::obj_manipul() -{ - // If this method is called before plater_ has been initialized, return nullptr (to avoid a crash) - return (plater_ != nullptr) ? sidebar().obj_manipul() : nullptr; -} - -ObjectSettings* GUI_App::obj_settings() -{ - return sidebar().obj_settings(); -} - -ObjectList* GUI_App::obj_list() -{ - return sidebar().obj_list(); -} - -ObjectLayers* GUI_App::obj_layers() -{ - return sidebar().obj_layers(); -} - -Plater* GUI_App::plater() -{ - return plater_; -} - -const Plater* GUI_App::plater() const -{ - return plater_; -} - -Model& GUI_App::model() -{ - return plater_->model(); -} -wxBookCtrlBase* GUI_App::tab_panel() const -{ - return mainframe->m_tabpanel; -} - -NotificationManager * GUI_App::notification_manager() -{ - return plater_->get_notification_manager(); -} - -// extruders count from selected printer preset -int GUI_App::extruders_cnt() const -{ - const Preset& preset = preset_bundle->printers.get_selected_preset(); - return preset.printer_technology() == ptSLA ? 1 : - preset.config.option("nozzle_diameter")->values.size(); -} - -// extruders count from edited printer preset -int GUI_App::extruders_edited_cnt() const -{ - const Preset& preset = preset_bundle->printers.get_edited_preset(); - return preset.printer_technology() == ptSLA ? 1 : - preset.config.option("nozzle_diameter")->values.size(); -} - -wxString GUI_App::current_language_code_safe() const -{ - // Translate the language code to a code, for which Prusa Research maintains translations. - const std::map mapping { - { "cs", "cs_CZ", }, - { "sk", "cs_CZ", }, - { "de", "de_DE", }, - { "es", "es_ES", }, - { "fr", "fr_FR", }, - { "it", "it_IT", }, - { "ja", "ja_JP", }, - { "ko", "ko_KR", }, - { "pl", "pl_PL", }, - { "uk", "uk_UA", }, - { "zh", "zh_CN", }, - { "ru", "ru_RU", }, - }; - wxString language_code = this->current_language_code().BeforeFirst('_'); - auto it = mapping.find(language_code); - if (it != mapping.end()) - language_code = it->second; - else - language_code = "en_US"; - return language_code; -} - -void GUI_App::open_web_page_localized(const std::string &http_address) -{ - open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe(), nullptr, false); -} - -// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). -// Because of we can't to print the multi-part objects with SLA technology. -bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) -{ - if (model_has_multi_part_objects(model())) { - show_info(nullptr, - _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + - _L("Please check your object list before preset changing."), - caption); - return false; - } - return true; -} - -bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page) -{ - wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); - - if (reason == ConfigWizard::RR_USER) { - if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) - return false; - } - - auto wizard = new ConfigWizard(mainframe); - const bool res = wizard->run(reason, start_page); - - if (res) { - load_current_presets(); - - // #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config() - if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) - may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard")); - } - - return res; -} - -void GUI_App::show_desktop_integration_dialog() -{ -#ifdef __linux__ - //wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); - DesktopIntegrationDialog dialog(mainframe); - dialog.ShowModal(); -#endif //__linux__ -} - -#if ENABLE_THUMBNAIL_GENERATOR_DEBUG -void GUI_App::gcode_thumbnails_debug() -{ - const std::string BEGIN_MASK = "; thumbnail begin"; - const std::string END_MASK = "; thumbnail end"; - std::string gcode_line; - bool reading_image = false; - unsigned int width = 0; - unsigned int height = 0; - - wxFileDialog dialog(GetTopWindow(), _L("Select a gcode file:"), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dialog.ShowModal() != wxID_OK) - return; - - std::string in_filename = into_u8(dialog.GetPath()); - std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string(); - - boost::nowide::ifstream in_file(in_filename.c_str()); - std::vector rows; - std::string row; - if (in_file.good()) - { - while (std::getline(in_file, gcode_line)) - { - if (in_file.good()) - { - if (boost::starts_with(gcode_line, BEGIN_MASK)) - { - reading_image = true; - gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1); - std::string::size_type x_pos = gcode_line.find('x'); - std::string width_str = gcode_line.substr(0, x_pos); - width = (unsigned int)::atoi(width_str.c_str()); - std::string height_str = gcode_line.substr(x_pos + 1); - height = (unsigned int)::atoi(height_str.c_str()); - row.clear(); - } - else if (reading_image && boost::starts_with(gcode_line, END_MASK)) - { - std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png"; - boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary); - if (out_file.good()) - { - std::string decoded; - decoded.resize(boost::beast::detail::base64::decoded_size(row.size())); - decoded.resize(boost::beast::detail::base64::decode((void*)&decoded[0], row.data(), row.size()).first); - - out_file.write(decoded.c_str(), decoded.size()); - out_file.close(); - } - - reading_image = false; - width = 0; - height = 0; - rows.clear(); - } - else if (reading_image) - row += gcode_line.substr(2); - } - } - - in_file.close(); - } -} -#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG - -void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name) -{ - if (name.empty()) { return; } - const auto config_key = (boost::format("window_%1%") % name).str(); - - WindowMetrics metrics = WindowMetrics::from_window(window); - app_config->set(config_key, metrics.serialize()); - app_config->save(); -} - -void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized) -{ - if (name.empty()) { return; } - const auto config_key = (boost::format("window_%1%") % name).str(); - - if (! app_config->has(config_key)) { - window->Maximize(default_maximized); - return; - } - - auto metrics = WindowMetrics::deserialize(app_config->get(config_key)); - if (! metrics) { - window->Maximize(default_maximized); - return; - } - - const wxRect& rect = metrics->get_rect(); - - if (app_config->get("restore_win_position") == "1") { - // workaround for crash related to the positioning of the window on secondary monitor - app_config->set("restore_win_position", (boost::format("crashed_at_%1%_pos") % name).str()); - app_config->save(); - window->SetPosition(rect.GetPosition()); - - // workaround for crash related to the positioning of the window on secondary monitor - app_config->set("restore_win_position", (boost::format("crashed_at_%1%_size") % name).str()); - app_config->save(); - window->SetSize(rect.GetSize()); - - // revert "restore_win_position" value if application wasn't crashed - app_config->set("restore_win_position", "1"); - app_config->save(); - } - else - window->CenterOnScreen(); - - window->Maximize(metrics->get_maximized()); -} - -void GUI_App::window_pos_sanitize(wxTopLevelWindow* window) -{ - /*unsigned*/int display_idx = wxDisplay::GetFromWindow(window); - wxRect display; - if (display_idx == wxNOT_FOUND) { - display = wxDisplay(0u).GetClientArea(); - window->Move(display.GetTopLeft()); - } else { - display = wxDisplay(display_idx).GetClientArea(); - } - - auto metrics = WindowMetrics::from_window(window); - metrics.sanitize_for_display(display); - if (window->GetScreenRect() != metrics.get_rect()) { - window->SetSize(metrics.get_rect()); - } -} - -bool GUI_App::config_wizard_startup() -{ - if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) { - run_wizard(ConfigWizard::RR_DATA_EMPTY); - return true; - } else if (get_app_config()->legacy_datadir()) { - // Looks like user has legacy pre-vendorbundle data directory, - // explain what this is and run the wizard - - MsgDataLegacy dlg; - dlg.ShowModal(); - - run_wizard(ConfigWizard::RR_DATA_LEGACY); - return true; - } - return false; -} - -bool GUI_App::check_updates(const bool verbose) -{ - PresetUpdater::UpdateResult updater_result; - try { - updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION); - if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { - mainframe->Close(); - // Applicaiton is closing. - return false; - } - else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) { - m_app_conf_exists = true; - } - else if (verbose && updater_result == PresetUpdater::R_NOOP) { - MsgNoUpdates dlg; - dlg.ShowModal(); - } - } - catch (const std::exception & ex) { - show_error(nullptr, ex.what()); - } - // Applicaiton will continue. - return true; -} - -bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/) -{ - bool launch = true; - - // warning dialog containes a "Remember my choice" checkbox - std::string option_key = "suppress_hyperlinks"; - if (force_remember_choice || app_config->get(option_key).empty()) { - if (app_config->get(option_key).empty()) { - RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); - dialog.ShowCheckBox(_L("Remember my choice")); - auto answer = dialog.ShowModal(); - launch = answer == wxID_YES; - if (dialog.IsCheckBoxChecked()) { - wxString preferences_item = _L("Suppress to open hyperlink in browser"); - wxString msg = - _L("PrusaSlicer will remember your choice.") + "\n\n" + - _L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" + - format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item); - - MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); - if (msg_dlg.ShowModal() == wxID_CANCEL) - return false; - app_config->set(option_key, answer == wxID_NO ? "1" : "0"); - } - } - if (launch) - launch = app_config->get(option_key) != "1"; - } - // warning dialog doesn't containe a "Remember my choice" checkbox - // and will be shown only when "Suppress to open hyperlink in browser" is ON. - else if (app_config->get(option_key) == "1") { - MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); - launch = dialog.ShowModal() == wxID_YES; - } - - return launch && wxLaunchDefaultBrowser(url, flags); -} - -// static method accepting a wxWindow object as first parameter -// void warning_catcher{ -// my($self, $message_dialog) = @_; -// return sub{ -// my $message = shift; -// return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ; -// my @params = ($message, 'Warning', wxOK | wxICON_WARNING); -// $message_dialog -// ? $message_dialog->(@params) -// : Wx::MessageDialog->new($self, @params)->ShowModal; -// }; -// } - -// Do we need this function??? -// void GUI_App::notify(message) { -// auto frame = GetTopWindow(); -// // try harder to attract user attention on OS X -// if (!frame->IsActive()) -// frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO); -// -// // There used to be notifier using a Growl application for OSX, but Growl is dead. -// // The notifier also supported the Linux X D - bus notifications, but that support was broken. -// //TODO use wxNotificationMessage ? -// } - - -#ifdef __WXMSW__ -static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue) -{ - // see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association - wchar_t szValueCurrent[1000]; - DWORD dwType; - DWORD dwSize = sizeof(szValueCurrent); - - int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize); - - bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND; - - if ((iRC != ERROR_SUCCESS) && !bDidntExist) - // an error occurred - return false; - - if (!bDidntExist) { - if (dwType != REG_SZ) - // invalid type - return false; - - if (::wcscmp(szValueCurrent, pszValue) == 0) - // value already set - return false; - } - - DWORD dwDisposition; - HKEY hkey; - iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition); - bool ret = false; - if (iRC == ERROR_SUCCESS) { - iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t)); - if (iRC == ERROR_SUCCESS) - ret = true; - } - - RegCloseKey(hkey); - return ret; -} - -void GUI_App::associate_3mf_files() -{ - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"Prusa.Slicer.1"; - std::wstring prog_desc = L"PrusaSlicer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.3mf"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); -} - -void GUI_App::associate_stl_files() -{ - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"Prusa.Slicer.1"; - std::wstring prog_desc = L"PrusaSlicer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.stl"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); -} - -void GUI_App::associate_gcode_files() -{ - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"PrusaSlicer.GCodeViewer.1"; - std::wstring prog_desc = L"PrusaSlicerGCodeViewer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.gcode"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); -} -#endif // __WXMSW__ - - -void GUI_App::on_version_read(wxCommandEvent& evt) -{ - app_config->set("version_online", into_u8(evt.GetString())); - app_config->save(); - std::string opt = app_config->get("notify_release"); - if (this->plater_ == nullptr || (opt != "all" && opt != "release")) { - return; - } - if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) { - return; - } - // notification - /* - this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable - , NotificationManager::NotificationLevel::ImportantNotificationLevel - , Slic3r::format(_u8L("New release version %1% is available."), evt.GetString()) - , _u8L("See Download page.") - , [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; } - ); - */ - // updater - // read triggered_by_user that was set when calling GUI_App::app_version_check - app_updater(m_app_updater->get_triggered_by_user()); -} - -void GUI_App::app_updater(bool from_user) -{ - DownloadAppData app_data = m_app_updater->get_app_data(); - - if (from_user && (!app_data.version || *app_data.version <= *Semver::parse(SLIC3R_VERSION))) - { - BOOST_LOG_TRIVIAL(info) << "There is no newer version online."; - MsgNoAppUpdates no_update_dialog; - no_update_dialog.ShowModal(); - return; - - } - - assert(!app_data.url.empty()); - assert(!app_data.target_path.empty()); - - // dialog with new version info - AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version); - auto dialog_result = dialog.ShowModal(); - // checkbox "do not show again" - if (dialog.disable_version_check()) { - app_config->set("notify_release", "none"); - } - // Doesn't wish to update - if (dialog_result != wxID_OK) { - return; - } - // dialog with new version download (installer or app dependent on system) including path selection - AppUpdateDownloadDialog dwnld_dlg(*app_data.version, app_data.target_path); - dialog_result = dwnld_dlg.ShowModal(); - // Doesn't wish to download - if (dialog_result != wxID_OK) { - return; - } - app_data.target_path =dwnld_dlg.get_download_path(); - - // start download - this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get())); - app_data.start_after = dwnld_dlg.run_after_download(); - m_app_updater->set_app_data(std::move(app_data)); - m_app_updater->sync_download(); -} - -void GUI_App::app_version_check(bool from_user) -{ - if (from_user) { - if (m_app_updater->get_download_ongoing()) { - MessageDialog msgdlg(nullptr, _L("Download of new version is already ongoing. Do you wish to continue?"), _L("Notice"), wxYES_NO); - if (msgdlg.ShowModal() != wxID_YES) - return; - } - } - std::string version_check_url = app_config->version_check_url(); - m_app_updater->sync_version(version_check_url, from_user); -} - -} // GUI -} //Slic3r +#include "libslic3r/Technologies.hpp" +#include "GUI_App.hpp" +#include "GUI_Init.hpp" +#include "GUI_ObjectList.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "GUI_Factories.hpp" +#include "format.hpp" +#include "I18N.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libslic3r/Utils.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/I18N.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Color.hpp" + +#include "GUI.hpp" +#include "GUI_Utils.hpp" +#include "3DScene.hpp" +#include "MainFrame.hpp" +#include "Plater.hpp" +#include "GLCanvas3D.hpp" + +#include "../Utils/PresetUpdater.hpp" +#include "../Utils/PrintHost.hpp" +#include "../Utils/Process.hpp" +#include "../Utils/MacDarkMode.hpp" +#include "../Utils/AppUpdater.hpp" +#include "slic3r/Config/Snapshot.hpp" +#include "ConfigSnapshotDialog.hpp" +#include "FirmwareDialog.hpp" +#include "Preferences.hpp" +#include "Tab.hpp" +#include "SysInfoDialog.hpp" +#include "KBShortcutsDialog.hpp" +#include "UpdateDialogs.hpp" +#include "Mouse3DController.hpp" +#include "RemovableDriveManager.hpp" +#include "InstanceCheck.hpp" +#include "NotificationManager.hpp" +#include "UnsavedChangesDialog.hpp" +#include "SavePresetDialog.hpp" +#include "PrintHostDialogs.hpp" +#include "DesktopIntegrationDialog.hpp" +#include "SendSystemInfoDialog.hpp" + +#include "BitmapCache.hpp" +#include "Notebook.hpp" + +#ifdef __WXMSW__ +#include +#include +#ifdef _MSW_DARK_MODE +#include +#endif // _MSW_DARK_MODE +#endif +#ifdef _WIN32 +#include +#endif + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG +#include +#include +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG + +// Needed for forcing menu icons back under gtk2 and gtk3 +#if defined(__WXGTK20__) || defined(__WXGTK3__) + #include +#endif + +using namespace std::literals; + +namespace Slic3r { +namespace GUI { + +class MainFrame; + +class SplashScreen : public wxSplashScreen +{ +public: + SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition) + : wxSplashScreen(bitmap, splashStyle, milliseconds, static_cast(wxGetApp().mainframe), wxID_ANY, wxDefaultPosition, wxDefaultSize, +#ifdef __APPLE__ + wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP +#else + wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR +#endif // !__APPLE__ + ) + { + wxASSERT(bitmap.IsOk()); + + int init_dpi = get_dpi_for_window(this); + this->SetPosition(pos); + this->CenterOnScreen(); + int new_dpi = get_dpi_for_window(this); + + m_scale = (float)(new_dpi) / (float)(init_dpi); + m_main_bitmap = bitmap; + + scale_bitmap(m_main_bitmap, m_scale); + + // init constant texts and scale fonts + init_constant_text(); + + // this font will be used for the action string + m_action_font = m_constant_text.credits_font.Bold(); + + // draw logo and constant info text + Decorate(m_main_bitmap); + } + + void SetText(const wxString& text) + { + set_bitmap(m_main_bitmap); + if (!text.empty()) { + wxBitmap bitmap(m_main_bitmap); + + wxMemoryDC memDC; + memDC.SelectObject(bitmap); + + memDC.SetFont(m_action_font); + memDC.SetTextForeground(wxColour(237, 107, 33)); + memDC.DrawText(text, int(m_scale * 60), m_action_line_y_position); + + memDC.SelectObject(wxNullBitmap); + set_bitmap(bitmap); +#ifdef __WXOSX__ + // without this code splash screen wouldn't be updated under OSX + wxYield(); +#endif + } + } + + static wxBitmap MakeBitmap(wxBitmap bmp) + { + if (!bmp.IsOk()) + return wxNullBitmap; + + // create dark grey background for the splashscreen + // It will be 5/3 of the weight of the bitmap + int width = lround((double)5 / 3 * bmp.GetWidth()); + int height = bmp.GetHeight(); + + wxImage image(width, height); + unsigned char* imgdata_ = image.GetData(); + for (int i = 0; i < width * height; ++i) { + *imgdata_++ = 51; + *imgdata_++ = 51; + *imgdata_++ = 51; + } + + wxBitmap new_bmp(image); + + wxMemoryDC memDC; + memDC.SelectObject(new_bmp); + memDC.DrawBitmap(bmp, width - bmp.GetWidth(), 0, true); + + return new_bmp; + } + + void Decorate(wxBitmap& bmp) + { + if (!bmp.IsOk()) + return; + + // draw text to the box at the left of the splashscreen. + // this box will be 2/5 of the weight of the bitmap, and be at the left. + int width = lround(bmp.GetWidth() * 0.4); + + // load bitmap for logo + BitmapCache bmp_cache; + int logo_size = lround(width * 0.25); + wxBitmap logo_bmp = *bmp_cache.load_svg(wxGetApp().logo_name(), logo_size, logo_size); + + wxCoord margin = int(m_scale * 20); + + wxRect banner_rect(wxPoint(0, logo_size), wxPoint(width, bmp.GetHeight())); + banner_rect.Deflate(margin, 2 * margin); + + // use a memory DC to draw directly onto the bitmap + wxMemoryDC memDc(bmp); + + // draw logo + memDc.DrawBitmap(logo_bmp, margin, margin, true); + + // draw the (white) labels inside of our black box (at the left of the splashscreen) + memDc.SetTextForeground(wxColour(255, 255, 255)); + + memDc.SetFont(m_constant_text.title_font); + memDc.DrawLabel(m_constant_text.title, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); + + int title_height = memDc.GetTextExtent(m_constant_text.title).GetY(); + banner_rect.SetTop(banner_rect.GetTop() + title_height); + banner_rect.SetHeight(banner_rect.GetHeight() - title_height); + + memDc.SetFont(m_constant_text.version_font); + memDc.DrawLabel(m_constant_text.version, banner_rect, wxALIGN_TOP | wxALIGN_LEFT); + int version_height = memDc.GetTextExtent(m_constant_text.version).GetY(); + + memDc.SetFont(m_constant_text.credits_font); + memDc.DrawLabel(m_constant_text.credits, banner_rect, wxALIGN_BOTTOM | wxALIGN_LEFT); + int credits_height = memDc.GetMultiLineTextExtent(m_constant_text.credits).GetY(); + int text_height = memDc.GetTextExtent("text").GetY(); + + // calculate position for the dynamic text + int logo_and_header_height = margin + logo_size + title_height + version_height; + m_action_line_y_position = logo_and_header_height + 0.5 * (bmp.GetHeight() - margin - credits_height - logo_and_header_height - text_height); + } + +private: + wxBitmap m_main_bitmap; + wxFont m_action_font; + int m_action_line_y_position; + float m_scale {1.0}; + + struct ConstantText + { + wxString title; + wxString version; + wxString credits; + + wxFont title_font; + wxFont version_font; + wxFont credits_font; + + void init(wxFont init_font) + { + // title + title = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME; + + // dynamically get the version to display + version = _L("Version") + " " + std::string(SLIC3R_VERSION); + + // credits infornation + credits = title + " " + + _L("is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n" + + _L("Developed by Prusa Research.")+ "\n\n" + + title + " " + _L("is licensed under the") + " " + _L("GNU Affero General Public License, version 3") + "\n\n" + + _L("Contributions by Vojtech Bubnik, Enrico Turri, Oleksandra Iushchenko, Tamas Meszaros, Lukas Matena, Vojtech Kral, David Kocik and numerous others.") + "\n\n" + + _L("Artwork model by M Boyer"); + + title_font = version_font = credits_font = init_font; + } + } + m_constant_text; + + void init_constant_text() + { + m_constant_text.init(get_default_font(this)); + + // As default we use a system font for current display. + // Scale fonts in respect to banner width + + int text_banner_width = lround(0.4 * m_main_bitmap.GetWidth()) - roundl(m_scale * 50); // banner_width - margins + + float title_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.title).GetX(); + scale_font(m_constant_text.title_font, title_font_scale > 3.5f ? 3.5f : title_font_scale); + + float version_font_scale = (float)text_banner_width / GetTextExtent(m_constant_text.version).GetX(); + scale_font(m_constant_text.version_font, version_font_scale > 2.f ? 2.f : version_font_scale); + + // The width of the credits information string doesn't respect to the banner width some times. + // So, scale credits_font in the respect to the longest string width + int longest_string_width = word_wrap_string(m_constant_text.credits); + float font_scale = (float)text_banner_width / longest_string_width; + scale_font(m_constant_text.credits_font, font_scale); + } + + void set_bitmap(wxBitmap& bmp) + { + m_window->SetBitmap(bmp); + m_window->Refresh(); + m_window->Update(); + } + + void scale_bitmap(wxBitmap& bmp, float scale) + { + if (scale == 1.0) + return; + + wxImage image = bmp.ConvertToImage(); + if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0) + return; + + int width = int(scale * image.GetWidth()); + int height = int(scale * image.GetHeight()); + image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); + + bmp = wxBitmap(std::move(image)); + } + + void scale_font(wxFont& font, float scale) + { +#ifdef __WXMSW__ + // Workaround for the font scaling in respect to the current active display, + // not for the primary display, as it's implemented in Font.cpp + // See https://github.com/wxWidgets/wxWidgets/blob/master/src/msw/font.cpp + // void wxNativeFontInfo::SetFractionalPointSize(float pointSizeNew) + wxNativeFontInfo nfi= *font.GetNativeFontInfo(); + float pointSizeNew = scale * font.GetPointSize(); + nfi.lf.lfHeight = nfi.GetLogFontHeightAtPPI(pointSizeNew, get_dpi_for_window(this)); + nfi.pointSize = pointSizeNew; + font = wxFont(nfi); +#else + font.Scale(scale); +#endif //__WXMSW__ + } + + // wrap a string for the strings no longer then 55 symbols + // return extent of the longest string + int word_wrap_string(wxString& input) + { + size_t line_len = 55;// count of symbols in one line + int idx = -1; + size_t cur_len = 0; + + wxString longest_sub_string; + auto get_longest_sub_string = [input](wxString &longest_sub_str, size_t cur_len, size_t i) { + if (cur_len > longest_sub_str.Len()) + longest_sub_str = input.SubString(i - cur_len + 1, i); + }; + + for (size_t i = 0; i < input.Len(); i++) + { + cur_len++; + if (input[i] == ' ') + idx = i; + if (input[i] == '\n') + { + get_longest_sub_string(longest_sub_string, cur_len, i); + idx = -1; + cur_len = 0; + } + if (cur_len >= line_len && idx >= 0) + { + get_longest_sub_string(longest_sub_string, cur_len, i); + input[idx] = '\n'; + cur_len = i - static_cast(idx); + } + } + + return GetTextExtent(longest_sub_string).GetX(); + } +}; + + +#ifdef __linux__ +bool static check_old_linux_datadir(const wxString& app_name) { + // If we are on Linux and the datadir does not exist yet, look into the old + // location where the datadir was before version 2.3. If we find it there, + // tell the user that he might wanna migrate to the new location. + // (https://github.com/prusa3d/PrusaSlicer/issues/2911) + // To be precise, the datadir should exist, it is created when single instance + // lock happens. Instead of checking for existence, check the contents. + + namespace fs = boost::filesystem; + + std::string new_path = Slic3r::data_dir(); + + wxString dir; + if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) + dir = wxFileName::GetHomeDir() + wxS("/.config"); + std::string default_path = (dir + "/" + app_name).ToUTF8().data(); + + if (new_path != default_path) { + // This happens when the user specifies a custom --datadir. + // Do not show anything in that case. + return true; + } + + fs::path data_dir = fs::path(new_path); + if (! fs::is_directory(data_dir)) + return true; // This should not happen. + + int file_count = std::distance(fs::directory_iterator(data_dir), fs::directory_iterator()); + + if (file_count <= 1) { // just cache dir with an instance lock + std::string old_path = wxStandardPaths::Get().GetUserDataDir().ToUTF8().data(); + + if (fs::is_directory(old_path)) { + wxString msg = from_u8((boost::format(_u8L("Starting with %1% 2.3, configuration " + "directory on Linux has changed (according to XDG Base Directory Specification) to \n%2%.\n\n" + "This directory did not exist yet (maybe you run the new version for the first time).\nHowever, " + "an old %1% configuration directory was detected in \n%3%.\n\n" + "Consider moving the contents of the old directory to the new location in order to access " + "your profiles, etc.\nNote that if you decide to downgrade %1% in future, it will use the old " + "location again.\n\n" + "What do you want to do now?")) % SLIC3R_APP_NAME % new_path % old_path).str()); + wxString caption = from_u8((boost::format(_u8L("%s - BREAKING CHANGE")) % SLIC3R_APP_NAME).str()); + RichMessageDialog dlg(nullptr, msg, caption, wxYES_NO); + dlg.SetYesNoLabels(_L("Quit, I will move my data now"), _L("Start the application")); + if (dlg.ShowModal() != wxID_NO) + return false; + } + } else { + // If the new directory exists, be silent. The user likely already saw the message. + } + return true; +} +#endif + +#ifdef _WIN32 +#if 0 // External Updater is replaced with AppUpdater.cpp +static bool run_updater_win() +{ + // find updater exe + boost::filesystem::path path_updater = boost::dll::program_location().parent_path() / "prusaslicer-updater.exe"; + // run updater. Original args: /silent -restartapp prusa-slicer.exe -startappfirst + std::string msg; + bool res = create_process(path_updater, L"/silent", msg); + if (!res) + BOOST_LOG_TRIVIAL(error) << msg; + return res; +} +#endif // 0 +#endif // _WIN32 + +struct FileWildcards { + std::string_view title; + std::vector file_extensions; +}; + +static const FileWildcards file_wildcards_by_type[FT_SIZE] = { + /* FT_STL */ { "STL files"sv, { ".stl"sv } }, + /* FT_OBJ */ { "OBJ files"sv, { ".obj"sv } }, + /* FT_OBJECT */ { "Object files"sv, { ".stl"sv, ".obj"sv } }, + /* FT_AMF */ { "AMF files"sv, { ".amf"sv, ".zip.amf"sv, ".xml"sv } }, + /* FT_3MF */ { "3MF files"sv, { ".3mf"sv } }, + /* FT_GCODE */ { "G-code files"sv, { ".gcode"sv, ".gco"sv, ".g"sv, ".ngc"sv } }, + /* FT_MODEL */ { "Known files"sv, { ".stl"sv, ".obj"sv, ".3mf"sv, ".amf"sv, ".zip.amf"sv, ".xml"sv } }, + /* FT_PROJECT */ { "Project files"sv, { ".3mf"sv, ".amf"sv, ".zip.amf"sv } }, + /* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } }, + + /* FT_INI */ { "INI files"sv, { ".ini"sv } }, + /* FT_SVG */ { "SVG files"sv, { ".svg"sv } }, + + /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, + + /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv, ".pwmx"sv } }, +}; + +#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR +wxString file_wildcards(FileType file_type) +{ + const FileWildcards& data = file_wildcards_by_type[file_type]; + std::string title; + std::string mask; + + // Generate cumulative first item + for (const std::string_view& ext : data.file_extensions) { + if (title.empty()) { + title = "*"; + title += ext; + mask = title; + } + else { + title += ", *"; + title += ext; + mask += ";*"; + mask += ext; + } + mask += ";*"; + mask += boost::to_upper_copy(std::string(ext)); + } + + wxString ret = GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); + + // Adds an item for each of the extensions + if (data.file_extensions.size() > 1) { + for (const std::string_view& ext : data.file_extensions) { + title = "*"; + title += ext; + ret += GUI::format_wxstr("|%s (%s)|%s", data.title, title, title); + } + } + + return ret; +} +#else +// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. +// The function accepts a custom extension parameter. If the parameter is provided, the custom extension +// will be added as a fist to the list. This is important for a "file save" dialog on OSX, which strips +// an extension from the provided initial file name and substitutes it with the default extension (the first one in the template). +wxString file_wildcards(FileType file_type, const std::string &custom_extension) +{ + const FileWildcards& data = file_wildcards_by_type[file_type]; + std::string title; + std::string mask; + std::string custom_ext_lower; + + if (! custom_extension.empty()) { + // Generate an extension into the title mask and into the list of extensions. + custom_ext_lower = boost::to_lower_copy(custom_extension); + const std::string custom_ext_upper = boost::to_upper_copy(custom_extension); + if (custom_ext_lower == custom_extension) { + // Add a lower case version. + title = std::string("*") + custom_ext_lower; + mask = title; + // Add an upper case version. + mask += ";*"; + mask += custom_ext_upper; + } else if (custom_ext_upper == custom_extension) { + // Add an upper case version. + title = std::string("*") + custom_ext_upper; + mask = title; + // Add a lower case version. + mask += ";*"; + mask += custom_ext_lower; + } else { + // Add the mixed case version only. + title = std::string("*") + custom_extension; + mask = title; + } + } + + for (const std::string_view &ext : data.file_extensions) + // Only add an extension if it was not added first as the custom extension. + if (ext != custom_ext_lower) { + if (title.empty()) { + title = "*"; + title += ext; + mask = title; + } else { + title += ", *"; + title += ext; + mask += ";*"; + mask += ext; + } + mask += ";*"; + mask += boost::to_upper_copy(std::string(ext)); + } + + return GUI::format_wxstr("%s (%s)|%s", data.title, title, mask); +} +#endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR + +static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } + +#ifdef WIN32 +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) +static void register_win32_dpi_event() +{ + enum { WM_DPICHANGED_ = 0x02e0 }; + + wxWindow::MSWRegisterMessageHandler(WM_DPICHANGED_, [](wxWindow *win, WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) { + const int dpi = wParam & 0xffff; + const auto rect = reinterpret_cast(lParam); + const wxRect wxrect(wxPoint(rect->top, rect->left), wxPoint(rect->bottom, rect->right)); + + DpiChangedEvent evt(EVT_DPI_CHANGED_SLICER, dpi, wxrect); + win->GetEventHandler()->AddPendingEvent(evt); + + return true; + }); +} +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN + +static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; + +static void register_win32_device_notification_event() +{ + wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. + auto main_frame = dynamic_cast(win); + auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); + if (plater == nullptr) + // Maybe some other top level window like a dialog or maybe a pop-up menu? + return true; + PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; + switch (wParam) { + case DBT_DEVICEARRIVAL: + if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) + plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); + else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { + PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; +// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) { +// printf("DBT_DEVICEARRIVAL %d - Media has arrived: %ws\n", msg_count, lpdbi->dbcc_name); + if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) + plater->GetEventHandler()->AddPendingEvent(HIDDeviceAttachedEvent(EVT_HID_DEVICE_ATTACHED, boost::nowide::narrow(lpdbi->dbcc_name))); + } + break; + case DBT_DEVICEREMOVECOMPLETE: + if (lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) + plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); + else if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { + PDEV_BROADCAST_DEVICEINTERFACE lpdbi = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb; +// if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_VOLUME) +// printf("DBT_DEVICEARRIVAL %d - Media was removed: %ws\n", msg_count, lpdbi->dbcc_name); + if (lpdbi->dbcc_classguid == GUID_DEVINTERFACE_HID) + plater->GetEventHandler()->AddPendingEvent(HIDDeviceDetachedEvent(EVT_HID_DEVICE_DETACHED, boost::nowide::narrow(lpdbi->dbcc_name))); + } + break; + default: + break; + } + return true; + }); + + wxWindow::MSWRegisterMessageHandler(MainFrame::WM_USER_MEDIACHANGED, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. + auto main_frame = dynamic_cast(win); + auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); + if (plater == nullptr) + // Maybe some other top level window like a dialog or maybe a pop-up menu? + return true; + wchar_t sPath[MAX_PATH]; + if (lParam == SHCNE_MEDIAINSERTED || lParam == SHCNE_MEDIAREMOVED) { + struct _ITEMIDLIST* pidl = *reinterpret_cast(wParam); + if (! SHGetPathFromIDList(pidl, sPath)) { + BOOST_LOG_TRIVIAL(error) << "MediaInserted: SHGetPathFromIDList failed"; + return false; + } + } + switch (lParam) { + case SHCNE_MEDIAINSERTED: + { + //printf("SHCNE_MEDIAINSERTED %S\n", sPath); + plater->GetEventHandler()->AddPendingEvent(VolumeAttachedEvent(EVT_VOLUME_ATTACHED)); + break; + } + case SHCNE_MEDIAREMOVED: + { + //printf("SHCNE_MEDIAREMOVED %S\n", sPath); + plater->GetEventHandler()->AddPendingEvent(VolumeDetachedEvent(EVT_VOLUME_DETACHED)); + break; + } + default: +// printf("Unknown\n"); + break; + } + return true; + }); + + wxWindow::MSWRegisterMessageHandler(WM_INPUT, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + auto main_frame = dynamic_cast(Slic3r::GUI::find_toplevel_parent(win)); + auto plater = (main_frame == nullptr) ? nullptr : main_frame->plater(); +// if (wParam == RIM_INPUTSINK && plater != nullptr && main_frame->IsActive()) { + if (wParam == RIM_INPUT && plater != nullptr && main_frame->IsActive()) { + RAWINPUT raw; + UINT rawSize = sizeof(RAWINPUT); + ::GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &raw, &rawSize, sizeof(RAWINPUTHEADER)); + if (raw.header.dwType == RIM_TYPEHID && plater->get_mouse3d_controller().handle_raw_input_win32(raw.data.hid.bRawData, raw.data.hid.dwSizeHid)) + return true; + } + return false; + }); + + wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { + COPYDATASTRUCT* copy_data_structure = { 0 }; + copy_data_structure = (COPYDATASTRUCT*)lParam; + if (copy_data_structure->dwData == 1) { + LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData; + Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments)); + } + return true; + }); +} +#endif // WIN32 + +static void generic_exception_handle() +{ + // Note: Some wxWidgets APIs use wxLogError() to report errors, eg. wxImage + // - see https://docs.wxwidgets.org/3.1/classwx_image.html#aa249e657259fe6518d68a5208b9043d0 + // + // wxLogError typically goes around exception handling and display an error dialog some time + // after an error is logged even if exception handling and OnExceptionInMainLoop() take place. + // This is why we use wxLogError() here as well instead of a custom dialog, because it accumulates + // errors if multiple have been collected and displays just one error message for all of them. + // Otherwise we would get multiple error messages for one missing png, for example. + // + // If a custom error message window (or some other solution) were to be used, it would be necessary + // to turn off wxLogError() usage in wx APIs, most notably in wxImage + // - see https://docs.wxwidgets.org/trunk/classwx_image.html#aa32e5d3507cc0f8c3330135bc0befc6a + + try { + throw; + } catch (const std::bad_alloc& ex) { + // bad_alloc in main thread is most likely fatal. Report immediately to the user (wxLogError would be delayed) + // and terminate the app so it is at least certain to happen now. + wxString errmsg = wxString::Format(_L("%s has encountered an error. It was likely caused by running out of memory. " + "If you are sure you have enough RAM on your system, this may also be a bug and we would " + "be glad if you reported it.\n\nThe application will now terminate."), SLIC3R_APP_NAME); + wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Fatal error"), wxOK | wxICON_ERROR); + BOOST_LOG_TRIVIAL(error) << boost::format("std::bad_alloc exception: %1%") % ex.what(); + std::terminate(); + } catch (const boost::io::bad_format_string& ex) { + wxString errmsg = _L("PrusaSlicer has encountered a localization error. " + "Please report to PrusaSlicer team, what language was active and in which scenario " + "this issue happened. Thank you.\n\nThe application will now terminate."); + wxMessageBox(errmsg + "\n\n" + wxString(ex.what()), _L("Critical error"), wxOK | wxICON_ERROR); + BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); + std::terminate(); + throw; + } catch (const std::exception& ex) { + wxLogError(format_wxstr(_L("Internal error: %1%"), ex.what())); + BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what(); + throw; + } +} + +void GUI_App::post_init() +{ + assert(initialized()); + if (! this->initialized()) + throw Slic3r::RuntimeError("Calling post_init() while not yet initialized"); + + if (this->init_params->start_as_gcodeviewer) { + if (! this->init_params->input_files.empty()) + this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str())); + } + else { + if (! this->init_params->preset_substitutions.empty()) + show_substitutions_info(this->init_params->preset_substitutions); + +#if 0 + // Load the cummulative config over the currently active profiles. + //FIXME if multiple configs are loaded, only the last one will have an effect. + // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). + // As of now only the full configs are supported here. + if (!m_print_config.empty()) + this->gui->mainframe->load_config(m_print_config); +#endif + if (! this->init_params->load_configs.empty()) + // Load the last config to give it a name at the UI. The name of the preset may be later + // changed by loading an AMF or 3MF. + //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. + this->mainframe->load_config_file(this->init_params->load_configs.back()); + // If loading a 3MF file, the config is loaded from the last one. + if (!this->init_params->input_files.empty()) { + const std::vector res = this->plater()->load_files(this->init_params->input_files, true, true); + if (!res.empty() && this->init_params->input_files.size() == 1) { + // Update application titlebar when opening a project file + const std::string& filename = this->init_params->input_files.front(); + if (boost::algorithm::iends_with(filename, ".amf") || + boost::algorithm::iends_with(filename, ".amf.xml") || + boost::algorithm::iends_with(filename, ".3mf")) + this->plater()->set_project_filename(from_u8(filename)); + } + } + if (! this->init_params->extra_config.empty()) + this->mainframe->load_config(this->init_params->extra_config); + } + + // show "Did you know" notification + if (app_config->get("show_hints") == "1" && ! is_gcode_viewer()) + plater_->get_notification_manager()->push_hint_notification(true); + + // The extra CallAfter() is needed because of Mac, where this is the only way + // to popup a modal dialog on start without screwing combo boxes. + // This is ugly but I honestly found no better way to do it. + // Neither wxShowEvent nor wxWindowCreateEvent work reliably. + if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater. + if (! this->check_updates(false)) + // Configuration is not compatible and reconfigure was refused by the user. Application is closing. + return; + CallAfter([this] { + bool cw_showed = this->config_wizard_startup(); + this->preset_updater->sync(preset_bundle); + this->app_version_check(false); + if (! cw_showed) { + // The CallAfter is needed as well, without it, GL extensions did not show. + // Also, we only want to show this when the wizard does not, so the new user + // sees something else than "we want something" on the first start. + show_send_system_info_dialog_if_needed(); + } + }); + } + + // Set PrusaSlicer version and save to PrusaSlicer.ini or PrusaSlicerGcodeViewer.ini. + app_config->set("version", SLIC3R_VERSION); + app_config->save(); + +#ifdef _WIN32 + // Sets window property to mainframe so other instances can indentify it. + OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); +#endif //WIN32 +} + +IMPLEMENT_APP(GUI_App) + +GUI_App::GUI_App(EAppMode mode) + : wxApp() + , m_app_mode(mode) + , m_em_unit(10) + , m_imgui(new ImGuiWrapper()) + , m_removable_drive_manager(std::make_unique()) + , m_other_instance_message_handler(std::make_unique()) +{ + //app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp + this->init_app_config(); + // init app downloader after path to datadir is set + m_app_updater = std::make_unique(); +} + +GUI_App::~GUI_App() +{ + if (app_config != nullptr) + delete app_config; + + if (preset_bundle != nullptr) + delete preset_bundle; + + if (preset_updater != nullptr) + delete preset_updater; +} + +// If formatted for github, plaintext with OpenGL extensions enclosed into
. +// Otherwise HTML formatted for the system info dialog. +std::string GUI_App::get_gl_info(bool for_github) +{ + return OpenGLManager::get_gl_info().to_string(for_github); +} + +wxGLContext* GUI_App::init_glcontext(wxGLCanvas& canvas) +{ + return m_opengl_mgr.init_glcontext(canvas); +} + +bool GUI_App::init_opengl() +{ +#ifdef __linux__ + bool status = m_opengl_mgr.init_gl(); + m_opengl_initialized = true; + return status; +#else + return m_opengl_mgr.init_gl(); +#endif +} + +// gets path to PrusaSlicer.ini, returns semver from first line comment +static boost::optional parse_semver_from_ini(std::string path) +{ + std::ifstream stream(path); + std::stringstream buffer; + buffer << stream.rdbuf(); + std::string body = buffer.str(); + size_t start = body.find("PrusaSlicer "); + if (start == std::string::npos) + return boost::none; + body = body.substr(start + 12); + size_t end = body.find_first_of(" \n"); + if (end < body.size()) + body.resize(end); + return Semver::parse(body); +} + +void GUI_App::init_app_config() +{ + // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release. +// SetAppName(SLIC3R_APP_KEY); + SetAppName(SLIC3R_APP_KEY "-alpha"); +// SetAppName(SLIC3R_APP_KEY "-beta"); + + +// SetAppDisplayName(SLIC3R_APP_NAME); + + // Set the Slic3r data directory at the Slic3r XS module. + // Unix: ~/ .Slic3r + // Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r" + // Mac : "~/Library/Application Support/Slic3r" + + if (data_dir().empty()) { + #ifndef __linux__ + set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()); + #else + // Since version 2.3, config dir on Linux is in ${XDG_CONFIG_HOME}. + // https://github.com/prusa3d/PrusaSlicer/issues/2911 + wxString dir; + if (! wxGetEnv(wxS("XDG_CONFIG_HOME"), &dir) || dir.empty() ) + dir = wxFileName::GetHomeDir() + wxS("/.config"); + set_data_dir((dir + "/" + GetAppName()).ToUTF8().data()); + #endif + } else { + m_datadir_redefined = true; + } + + if (!app_config) + app_config = new AppConfig(is_editor() ? AppConfig::EAppMode::Editor : AppConfig::EAppMode::GCodeViewer); + + // load settings + m_app_conf_exists = app_config->exists(); + if (m_app_conf_exists) { + std::string error = app_config->load(); + if (!error.empty()) { + // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + if (is_editor()) { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + else { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + } + } +} + +// returns old config path to copy from if such exists, +// returns an empty string if such config path does not exists or if it cannot be loaded. +std::string GUI_App::check_older_app_config(Semver current_version, bool backup) +{ + std::string older_data_dir_path; + + // If the config folder is redefined - do not check + if (m_datadir_redefined) + return {}; + + // find other version app config (alpha / beta / release) + std::string config_path = app_config->config_path(); + boost::filesystem::path parent_file_path(config_path); + std::string filename = parent_file_path.filename().string(); + parent_file_path.remove_filename().remove_filename(); + + std::vector candidates; + + if (SLIC3R_APP_KEY "-alpha" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-alpha" / filename); + if (SLIC3R_APP_KEY "-beta" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-beta" / filename); + if (SLIC3R_APP_KEY != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY / filename); + + Semver last_semver = current_version; + for (const auto& candidate : candidates) { + if (boost::filesystem::exists(candidate)) { + // parse + boost::optionalother_semver = parse_semver_from_ini(candidate.string()); + if (other_semver && *other_semver > last_semver) { + last_semver = *other_semver; + older_data_dir_path = candidate.parent_path().string(); + } + } + } + if (older_data_dir_path.empty()) + return {}; + BOOST_LOG_TRIVIAL(info) << "last app config file used: " << older_data_dir_path; + // ask about using older data folder + + InfoDialog msg(nullptr + , format_wxstr(_L("You are opening %1% version %2%."), SLIC3R_APP_NAME, SLIC3R_VERSION) + , backup ? + format_wxstr(_L( + "The active configuration was created by %1% %2%," + "\nwhile a newer configuration was found in %3%" + "\ncreated by %1% %4%." + "\n\nShall the newer configuration be imported?" + "\nIf so, your active configuration will be backed up before importing the new configuration." + ) + , SLIC3R_APP_NAME, current_version.to_string(), older_data_dir_path, last_semver.to_string()) + : format_wxstr(_L( + "An existing configuration was found in %3%" + "\ncreated by %1% %2%." + "\n\nShall this configuration be imported?" + ) + , SLIC3R_APP_NAME, last_semver.to_string(), older_data_dir_path) + , true, wxYES_NO); + + if (backup) { + msg.SetButtonLabel(wxID_YES, _L("Import")); + msg.SetButtonLabel(wxID_NO, _L("Don't import")); + } + + if (msg.ShowModal() == wxID_YES) { + std::string snapshot_id; + if (backup) { + const Config::Snapshot* snapshot{ nullptr }; + if (! GUI::Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_USER, "", + _u8L("Continue and import newer configuration?"), &snapshot)) + return {}; + if (snapshot) { + // Save snapshot ID before loading the alternate AppConfig, as loading the alternate AppConfig may fail. + snapshot_id = snapshot->id; + assert(! snapshot_id.empty()); + app_config->set("on_snapshot", snapshot_id); + } else + BOOST_LOG_TRIVIAL(error) << "Failed to take congiguration snapshot"; + } + + // load app config from older file + std::string error = app_config->load((boost::filesystem::path(older_data_dir_path) / filename).string()); + if (!error.empty()) { + // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + if (is_editor()) { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + else { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + } + if (!snapshot_id.empty()) + app_config->set("on_snapshot", snapshot_id); + m_app_conf_exists = true; + return older_data_dir_path; + } + return {}; +} + +void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path) +{ + BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path; + m_single_instance_checker = std::make_unique(boost::nowide::widen(name), boost::nowide::widen(path)); +} + +bool GUI_App::OnInit() +{ + try { + return on_init_inner(); + } catch (const std::exception&) { + generic_exception_handle(); + return false; + } +} + +bool GUI_App::on_init_inner() +{ + // Set initialization of image handlers before any UI actions - See GH issue #7469 + wxInitAllImageHandlers(); + +#if defined(_WIN32) && ! defined(_WIN64) + // Win32 32bit build. + if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") { + RichMessageDialog dlg(nullptr, + _L("You are running a 32 bit build of PrusaSlicer on 64-bit Windows." + "\n32 bit build of PrusaSlicer will likely not be able to utilize all the RAM available in the system." + "\nPlease download and install a 64 bit build of PrusaSlicer from https://www.prusa3d.cz/prusaslicer/." + "\nDo you wish to continue?"), + "PrusaSlicer", wxICON_QUESTION | wxYES_NO); + if (dlg.ShowModal() != wxID_YES) + return false; + } +#endif // _WIN64 + + // Forcing back menu icons under gtk2 and gtk3. Solution is based on: + // https://docs.gtk.org/gtk3/class.Settings.html + // see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb + // TODO: Find workaround for GTK4 +#if defined(__WXGTK20__) || defined(__WXGTK3__) + g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL); +#endif + + // Verify resources path + const wxString resources_dir = from_u8(Slic3r::resources_dir()); + wxCHECK_MSG(wxDirExists(resources_dir), false, + wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); + +#ifdef __linux__ + if (! check_old_linux_datadir(GetAppName())) { + std::cerr << "Quitting, user chose to move their data to new location." << std::endl; + return false; + } +#endif + + // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. +// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0); + // Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible + // performance when working on high resolution multi-display setups. +// wxSystemOptions::SetOption("msw.notebook.themed-background", 0); + +// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION; + + // !!! Initialization of UI settings as a language, application color mode, fonts... have to be done before first UI action. + // Like here, before the show InfoDialog in check_older_app_config() + + // If load_language() fails, the application closes. + load_language(wxString(), true); +#ifdef _MSW_DARK_MODE + bool init_dark_color_mode = app_config->get("dark_color_mode") == "1"; + bool init_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; + NppDarkMode::InitDarkMode(init_dark_color_mode, init_sys_menu_enabled); +#endif + // initialize label colors and fonts + init_label_colours(); + init_fonts(); + + std::string older_data_dir_path; + if (m_app_conf_exists) { + if (app_config->orig_version().valid() && app_config->orig_version() < *Semver::parse(SLIC3R_VERSION)) + // Only copying configuration if it was saved with a newer slicer than the one currently running. + older_data_dir_path = check_older_app_config(app_config->orig_version(), true); + } else { + // No AppConfig exists, fresh install. Always try to copy from an alternate location, don't make backup of the current configuration. + older_data_dir_path = check_older_app_config(Semver(), false); + } + +#ifdef _MSW_DARK_MODE + // app_config can be updated in check_older_app_config(), so check if dark_color_mode and sys_menu_enabled was changed + 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(); + } + if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1"; + init_sys_menu_enabled != new_sys_menu_enabled) + NppDarkMode::SetSystemMenuForApp(new_sys_menu_enabled); +#endif + + if (is_editor()) { + std::string msg = Http::tls_global_init(); + std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location"); + bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store(); + + if (!msg.empty() && !ssl_accept) { + RichMessageDialog + dlg(nullptr, + wxString::Format(_L("%s\nDo you want to continue?"), msg), + "PrusaSlicer", wxICON_QUESTION | wxYES_NO); + dlg.ShowCheckBox(_L("Remember my choice")); + if (dlg.ShowModal() != wxID_YES) return false; + + app_config->set("tls_cert_store_accepted", + dlg.IsCheckBoxChecked() ? "yes" : "no"); + app_config->set("tls_accepted_cert_store_location", + dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); + } + } + + SplashScreen* scrn = nullptr; + if (app_config->get("show_splash_screen") == "1") { + // make a bitmap with dark grey banner on the left side + wxBitmap bmp = SplashScreen::MakeBitmap(wxBitmap(from_u8(var(is_editor() ? "splashscreen.jpg" : "splashscreen-gcodepreview.jpg")), wxBITMAP_TYPE_JPEG)); + + // Detect position (display) to show the splash screen + // Now this position is equal to the mainframe position + wxPoint splashscreen_pos = wxDefaultPosition; + bool default_splashscreen_pos = true; + if (app_config->has("window_mainframe") && app_config->get("restore_win_position") == "1") { + auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe")); + default_splashscreen_pos = metrics == boost::none; + if (!default_splashscreen_pos) + splashscreen_pos = metrics->get_rect().GetPosition(); + } + + if (!default_splashscreen_pos) { + // workaround for crash related to the positioning of the window on secondary monitor + get_app_config()->set("restore_win_position", "crashed_at_splashscreen_pos"); + get_app_config()->save(); + } + + // create splash screen with updated bmp + scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("PrusaSlicer", nullptr, 400), + wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos); + + if (!default_splashscreen_pos) + // revert "restore_win_position" value if application wasn't crashed + get_app_config()->set("restore_win_position", "1"); +#ifndef __linux__ + wxYield(); +#endif + scrn->SetText(_L("Loading configuration")+ dots); + } + + preset_bundle = new PresetBundle(); + + // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory + // supplied as argument to --datadir; in that case we should still run the wizard + preset_bundle->setup_directories(); + + if (! older_data_dir_path.empty()) { + preset_bundle->import_newer_configs(older_data_dir_path); + app_config->save(); + } + + if (is_editor()) { +#ifdef __WXMSW__ +#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + // file association is not possible anymore starting with Win 8 + if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { +#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); +#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + } +#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER +#endif // __WXMSW__ + + preset_updater = new PresetUpdater(); + Bind(EVT_SLIC3R_VERSION_ONLINE, &GUI_App::on_version_read, this); + Bind(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, [this](const wxCommandEvent& evt) { + if (this->plater_ != nullptr && app_config->get("notify_release") == "all") { + std::string evt_string = into_u8(evt.GetString()); + if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(evt_string)) { + auto notif_type = (evt_string.find("beta") != std::string::npos ? NotificationType::NewBetaAvailable : NotificationType::NewAlphaAvailable); + this->plater_->get_notification_manager()->push_notification( notif_type + , NotificationManager::NotificationLevel::ImportantNotificationLevel + , Slic3r::format(_u8L("New prerelease version %1% is available."), evt_string) + , _u8L("See Releases page.") + , [](wxEvtHandler* evnthndlr) {wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; } + ); + } + } + }); + Bind(EVT_SLIC3R_APP_DOWNLOAD_PROGRESS, [this](const wxCommandEvent& evt) { + //lm:This does not force a render. The progress bar only updateswhen the mouse is moved. + if (this->plater_ != nullptr) + this->plater_->get_notification_manager()->set_download_progress_percentage((float)std::stoi(into_u8(evt.GetString())) / 100.f ); + }); + + Bind(EVT_SLIC3R_APP_DOWNLOAD_FAILED, [this](const wxCommandEvent& evt) { + if (this->plater_ != nullptr) + this->plater_->get_notification_manager()->close_notification_of_type(NotificationType::AppDownload); + if(!evt.GetString().IsEmpty()) + show_error(nullptr, evt.GetString()); + }); + + Bind(EVT_SLIC3R_APP_OPEN_FAILED, [](const wxCommandEvent& evt) { + show_error(nullptr, evt.GetString()); + }); + } + else { +#ifdef __WXMSW__ +#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + // file association is not possible anymore starting with Win 8 + if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { +#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); +#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + } +#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER +#endif // __WXMSW__ + } + + // Suppress the '- default -' presets. + preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1"); + try { + // Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only. + // If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force + // installation of a compatible system preset, thus nullifying the system preset substitutions. + init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent); + } catch (const std::exception &ex) { + show_error(nullptr, ex.what()); + } + +#ifdef WIN32 +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + register_win32_dpi_event(); +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN + register_win32_device_notification_event(); +#endif // WIN32 + + // Let the libslic3r know the callback, which will translate messages on demand. + Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); + + // application frame + if (scrn && is_editor()) + scrn->SetText(_L("Preparing settings tabs") + dots); + + mainframe = new MainFrame(); + // hide settings tabs after first Layout + if (is_editor()) + mainframe->select_tab(size_t(0)); + + sidebar().obj_list()->init_objects(); // propagate model objects to object list +// update_mode(); // !!! do that later + SetTopWindow(mainframe); + + plater_->init_notification_manager(); + + m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); + + if (is_gcode_viewer()) { + mainframe->update_layout(); + if (plater_ != nullptr) + // ensure the selected technology is ptFFF + plater_->set_printer_technology(ptFFF); + } + else + load_current_presets(); + + // Save the active profiles as a "saved into project". + update_saved_preset_from_current_preset(); + + if (plater_ != nullptr) { + // Save the names of active presets and project specific config into ProjectDirtyStateManager. + plater_->reset_project_dirty_initial_presets(); + // Update Project dirty state, update application title bar. + plater_->update_project_dirty_from_presets(); + } + + mainframe->Show(true); + + obj_list()->set_min_height(); + + update_mode(); // update view mode after fix of the object_list size + +#ifdef __APPLE__ + other_instance_message_handler()->bring_instance_forward(); +#endif //__APPLE__ + + Bind(wxEVT_IDLE, [this](wxIdleEvent& event) + { + if (! plater_) + return; + + this->obj_manipul()->update_if_dirty(); + + // An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT + // and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized. +#ifdef __linux__ + if (! m_post_initialized && m_opengl_initialized) { +#else + if (! m_post_initialized) { +#endif + m_post_initialized = true; +#ifdef WIN32 + this->mainframe->register_win32_callbacks(); +#endif + this->post_init(); + } + + if (m_post_initialized && app_config->dirty() && app_config->get("autosave") == "1") + app_config->save(); + }); + + m_initialized = true; + + if (const std::string& crash_reason = app_config->get("restore_win_position"); + boost::starts_with(crash_reason,"crashed")) + { + wxString preferences_item = _L("Restore window position on start"); + InfoDialog dialog(nullptr, + _L("PrusaSlicer started after a crash"), + format_wxstr(_L("PrusaSlicer crashed last time when attempting to set window position.\n" + "We are sorry for the inconvenience, it unfortunately happens with certain multiple-monitor setups.\n" + "More precise reason for the crash: \"%1%\".\n" + "For more information see our GitHub issue tracker: \"%2%\" and \"%3%\"\n\n" + "To avoid this problem, consider disabling \"%4%\" in \"Preferences\". " + "Otherwise, the application will most likely crash again next time."), + "" + from_u8(crash_reason) + "", + "#2939", + "#5573", + "" + preferences_item + ""), + true, wxYES_NO); + + dialog.SetButtonLabel(wxID_YES, format_wxstr(_L("Disable \"%1%\""), preferences_item)); + dialog.SetButtonLabel(wxID_NO, format_wxstr(_L("Leave \"%1%\" enabled") , preferences_item)); + + auto answer = dialog.ShowModal(); + if (answer == wxID_YES) + app_config->set("restore_win_position", "0"); + else if (answer == wxID_NO) + app_config->set("restore_win_position", "1"); + app_config->save(); + } + + return true; +} + +unsigned GUI_App::get_colour_approx_luma(const wxColour &colour) +{ + double r = colour.Red(); + double g = colour.Green(); + double b = colour.Blue(); + + return std::round(std::sqrt( + r * r * .241 + + g * g * .691 + + b * b * .068 + )); +} + +bool GUI_App::dark_mode() +{ +#if __APPLE__ + // The check for dark mode returns false positive on 10.12 and 10.13, + // which allowed setting dark menu bar and dock area, which is + // is detected as dark mode. We must run on at least 10.14 where the + // proper dark mode was first introduced. + return wxPlatformInfo::Get().CheckOSVersion(10, 14) && mac_dark_mode(); +#else + return wxGetApp().app_config->get("dark_color_mode") == "1" ? true : check_dark_mode(); + //const unsigned luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + //return luma < 128; +#endif +} + +const wxColour GUI_App::get_label_default_clr_system() +{ + return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57); +} + +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() +{ + m_color_label_modified = get_label_default_clr_modified(); + m_color_label_sys = get_label_default_clr_system(); + + bool is_dark_mode = dark_mode(); +#ifdef _WIN32 + m_color_label_default = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); + m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT); + m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); + m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1); + m_color_default_btn_label = is_dark_mode ? wxColour(255, 181, 100): wxColour(203, 61, 0); + m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216); +#else + m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); +#endif + m_color_window_default = is_dark_mode ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); +} + +void GUI_App::update_label_colours_from_appconfig() +{ + if (app_config->has("label_clr_sys")) { + auto str = app_config->get("label_clr_sys"); + if (str != "") + m_color_label_sys = wxColour(str); + } + + if (app_config->has("label_clr_modified")) { + auto str = app_config->get("label_clr_modified"); + if (str != "") + m_color_label_modified = wxColour(str); + } +} + +void GUI_App::update_label_colours() +{ + for (Tab* tab : tabs_list) + tab->update_label_colours(); +} + +#ifdef _WIN32 +static bool is_focused(HWND hWnd) +{ + HWND hFocusedWnd = ::GetFocus(); + return hFocusedWnd && hWnd == hFocusedWnd; +} + +static bool is_default(wxWindow* win) +{ + wxTopLevelWindow* tlw = find_toplevel_parent(win); + if (!tlw) + return false; + + return win == tlw->GetDefaultItem(); +} +#endif + +void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/) +{ +#ifdef _WIN32 + bool is_focused_button = false; + bool is_default_button = false; + if (wxButton* btn = dynamic_cast(window)) { + if (!(btn->GetWindowStyle() & wxNO_BORDER)) { + btn->SetWindowStyle(btn->GetWindowStyle() | wxNO_BORDER); + highlited = true; + } + // button marking + { + auto mark_button = [this, btn, highlited](const bool mark) { + if (btn->GetLabel().IsEmpty()) + btn->SetBackgroundColour(mark ? m_color_selected_btn_bg : highlited ? m_color_highlight_default : m_color_window_default); + else + btn->SetForegroundColour(mark ? m_color_hovered_btn_label : (is_default(btn) ? m_color_default_btn_label : m_color_label_default)); + btn->Refresh(); + btn->Update(); + }; + + // hovering + btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); }); + btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); }); + // focusing + btn->Bind(wxEVT_SET_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); }); + btn->Bind(wxEVT_KILL_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); }); + + is_focused_button = is_focused(btn->GetHWND()); + is_default_button = is_default(btn); + if (is_focused_button || is_default_button) + mark_button(is_focused_button); + } + } + else if (wxTextCtrl* text = dynamic_cast(window)) { + if (text->GetBorder() != wxBORDER_SIMPLE) + text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE); + } + else if (wxCheckListBox* list = dynamic_cast(window)) { + list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE); + list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); + for (size_t i = 0; i < list->GetCount(); i++) + if (wxOwnerDrawn* item = list->GetItem(i)) { + item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); + item->SetTextColour(m_color_label_default); + } + return; + } + else if (dynamic_cast(window)) + window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE); + + if (!just_font) + window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); + if (!is_focused_button && !is_default_button) + window->SetForegroundColour(m_color_label_default); +#endif +} + +// recursive function for scaling fonts for all controls in Window +#ifdef _WIN32 +static void update_dark_children_ui(wxWindow* window, bool just_buttons_update = false) +{ + bool is_btn = dynamic_cast(window) != nullptr; + if (!(just_buttons_update && !is_btn)) + wxGetApp().UpdateDarkUI(window, is_btn); + + auto children = window->GetChildren(); + for (auto child : children) { + update_dark_children_ui(child); + } +} +#endif + +// Note: Don't use this function for Dialog contains ScalableButtons +void GUI_App::UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update/* = false*/) +{ +#ifdef _WIN32 + update_dark_children_ui(dlg, just_buttons_update); +#endif +} +void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/) +{ +#ifdef _WIN32 + UpdateDarkUI(dvc, highlited ? dark_mode() : false); +#ifdef _MSW_DARK_MODE + dvc->RefreshHeaderDarkMode(&m_normal_font); +#endif //_MSW_DARK_MODE + if (dvc->HasFlag(wxDV_ROW_LINES)) + dvc->SetAlternateRowColour(m_color_highlight_default); + if (dvc->GetBorder() != wxBORDER_SIMPLE) + dvc->SetWindowStyle(dvc->GetWindowStyle() | wxBORDER_SIMPLE); +#endif +} + +void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent) +{ +#ifdef _WIN32 + wxGetApp().UpdateDarkUI(parent); + + auto children = parent->GetChildren(); + for (auto child : children) { + if (dynamic_cast(child)) + child->SetForegroundColour(m_color_label_default); + } +#endif +} + +void GUI_App::init_fonts() +{ + m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold(); + m_normal_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + +#ifdef __WXMAC__ + m_small_font.SetPointSize(11); + m_bold_font.SetPointSize(13); +#endif /*__WXMAC__*/ + + // wxSYS_OEM_FIXED_FONT and wxSYS_ANSI_FIXED_FONT use the same as + // DEFAULT in wxGtk. Use the TELETYPE family as a work-around + m_code_font = wxFont(wxFontInfo().Family(wxFONTFAMILY_TELETYPE)); + m_code_font.SetPointSize(m_normal_font.GetPointSize()); +} + +void GUI_App::update_fonts(const MainFrame *main_frame) +{ + /* Only normal and bold fonts are used for an application rescale, + * because of under MSW small and normal fonts are the same. + * To avoid same rescaling twice, just fill this values + * from rescaled MainFrame + */ + if (main_frame == nullptr) + main_frame = this->mainframe; + m_normal_font = main_frame->normal_font(); + m_small_font = m_normal_font; + m_bold_font = main_frame->normal_font().Bold(); + m_link_font = m_bold_font.Underlined(); + m_em_unit = main_frame->em_unit(); + m_code_font.SetPointSize(m_normal_font.GetPointSize()); +} + +void GUI_App::set_label_clr_modified(const wxColour& clr) +{ + if (m_color_label_modified == clr) + return; + m_color_label_modified = clr; + const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); + app_config->set("label_clr_modified", str); + app_config->save(); +} + +void GUI_App::set_label_clr_sys(const wxColour& clr) +{ + if (m_color_label_sys == clr) + return; + m_color_label_sys = clr; + const std::string str = encode_color(ColorRGB(clr.Red(), clr.Green(), clr.Blue())); + app_config->set("label_clr_sys", str); + app_config->save(); +} + +bool GUI_App::tabs_as_menu() const +{ + return app_config->get("tabs_as_menu") == "1"; // || dark_mode(); +} + +wxSize GUI_App::get_min_size() const +{ + return wxSize(76*m_em_unit, 49 * m_em_unit); +} + +float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const +{ +#ifdef __APPLE__ + const float icon_sc = 1.0f; // for Retina display will be used its own scale +#else + const float icon_sc = m_em_unit*0.1f; +#endif // __APPLE__ + + const std::string& use_val = app_config->get("use_custom_toolbar_size"); + const std::string& val = app_config->get("custom_toolbar_size"); + const std::string& auto_val = app_config->get("auto_toolbar_size"); + + if (val.empty() || auto_val.empty() || use_val.empty()) + return icon_sc; + + int int_val = use_val == "0" ? 100 : atoi(val.c_str()); + // correct value in respect to auto_toolbar_size + int_val = std::min(atoi(auto_val.c_str()), int_val); + + if (is_limited && int_val < 50) + int_val = 50; + + return 0.01f * int_val * icon_sc; +} + +void GUI_App::set_auto_toolbar_icon_scale(float scale) const +{ +#ifdef __APPLE__ + const float icon_sc = 1.0f; // for Retina display will be used its own scale +#else + const float icon_sc = m_em_unit * 0.1f; +#endif // __APPLE__ + + long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100); + std::string val = std::to_string(int_val); + + app_config->set("auto_toolbar_size", val); +} + +// check user printer_presets for the containing information about "Print Host upload" +void GUI_App::check_printer_presets() +{ + std::vector preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers); + if (preset_names.empty()) + return; + + wxString msg_text = _L("You have the following presets with saved options for \"Print Host upload\"") + ":"; + for (const std::string& preset_name : preset_names) + msg_text += "\n \"" + from_u8(preset_name) + "\","; + msg_text.RemoveLast(); + msg_text += "\n\n" + _L("But since this version of PrusaSlicer we don't show this information in Printer Settings anymore.\n" + "Settings will be available in physical printers settings.") + "\n\n" + + _L("By default new Printer devices will be named as \"Printer N\" during its creation.\n" + "Note: This name can be changed later from the physical printers settings"); + + //wxMessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); + MessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal(); + + preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers); +} + +void GUI_App::recreate_GUI(const wxString& msg_name) +{ + m_is_recreating_gui = true; + + mainframe->shutdown(); + + wxProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE); + dlg.Pulse(); + dlg.Update(10, _L("Recreating") + dots); + + MainFrame *old_main_frame = mainframe; + mainframe = new MainFrame(); + if (is_editor()) + // hide settings tabs after first Layout + mainframe->select_tab(size_t(0)); + // Propagate model objects to object list. + sidebar().obj_list()->init_objects(); + SetTopWindow(mainframe); + + dlg.Update(30, _L("Recreating") + dots); + old_main_frame->Destroy(); + + dlg.Update(80, _L("Loading of current presets") + dots); + m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); + load_current_presets(); + mainframe->Show(true); + + dlg.Update(90, _L("Loading of a mode view") + dots); + + obj_list()->set_min_height(); + update_mode(); + + // #ys_FIXME_delete_after_testing Do we still need this ? +// CallAfter([]() { +// // Run the config wizard, don't offer the "reset user profile" checkbox. +// config_wizard_startup(true); +// }); + + m_is_recreating_gui = false; +} + +void GUI_App::system_info() +{ + SysInfoDialog dlg; + dlg.ShowModal(); +} + +void GUI_App::keyboard_shortcuts() +{ + KBShortcutsDialog dlg; + dlg.ShowModal(); +} + +// static method accepting a wxWindow object as first parameter +bool GUI_App::catch_error(std::function cb, + // wxMessageDialog* message_dialog, + const std::string& err /*= ""*/) +{ + if (!err.empty()) { + if (cb) + cb(); + // if (message_dialog) + // message_dialog->(err, "Error", wxOK | wxICON_ERROR); + show_error(/*this*/nullptr, err); + return true; + } + return false; +} + +// static method accepting a wxWindow object as first parameter +void fatal_error(wxWindow* parent) +{ + show_error(parent, ""); + // exit 1; // #ys_FIXME +} + +#ifdef _WIN32 + +#ifdef _MSW_DARK_MODE +static void update_scrolls(wxWindow* window) +{ + wxWindowList::compatibility_iterator node = window->GetChildren().GetFirst(); + while (node) + { + wxWindow* win = node->GetData(); + if (dynamic_cast(win) || + dynamic_cast(win) || + dynamic_cast(win)) + NppDarkMode::SetDarkExplorerTheme(win->GetHWND()); + + update_scrolls(win); + node = node->GetNext(); + } +} +#endif //_MSW_DARK_MODE + + +#ifdef _MSW_DARK_MODE +void GUI_App::force_menu_update() +{ + NppDarkMode::SetSystemMenuForApp(app_config->get("sys_menu_enabled") == "1"); +} +#endif //_MSW_DARK_MODE + +void GUI_App::force_colors_update() +{ +#ifdef _MSW_DARK_MODE + NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1"); + if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl()) + NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND); + NppDarkMode::SetDarkTitleBar(mainframe->GetHWND()); + NppDarkMode::SetDarkTitleBar(mainframe->m_settings_dialog.GetHWND()); +#endif //_MSW_DARK_MODE + m_force_colors_update = true; +} +#endif //_WIN32 + +// Called after the Preferences dialog is closed and the program settings are saved. +// Update the UI based on the current preferences. +void GUI_App::update_ui_from_settings() +{ + update_label_colours(); +#ifdef _WIN32 + // Upadte UI colors before Update UI from settings + if (m_force_colors_update) { + m_force_colors_update = false; + mainframe->force_color_changed(); + mainframe->diff_dialog.force_color_changed(); + mainframe->preferences_dialog->force_color_changed(); + mainframe->printhost_queue_dlg()->force_color_changed(); +#ifdef _MSW_DARK_MODE + update_scrolls(mainframe); + if (mainframe->is_dlg_layout()) { + // update for tabs bar + UpdateDarkUI(&mainframe->m_settings_dialog); + mainframe->m_settings_dialog.Fit(); + mainframe->m_settings_dialog.Refresh(); + // update scrollbars + update_scrolls(&mainframe->m_settings_dialog); + } +#endif //_MSW_DARK_MODE + } +#endif + mainframe->update_ui_from_settings(); +} + +void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized) +{ + const std::string name = into_u8(window->GetName()); + + window->Bind(wxEVT_CLOSE_WINDOW, [=](wxCloseEvent &event) { + window_pos_save(window, name); + event.Skip(); + }); + + window_pos_restore(window, name, default_maximized); + + on_window_geometry(window, [=]() { + window_pos_sanitize(window); + }); +} + +void GUI_App::load_project(wxWindow *parent, wxString& input_file) const +{ + input_file.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _L("Choose one file (3MF/AMF):"), + app_config->get_last_dir(), "", + file_wildcards(FT_PROJECT), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + input_file = dialog.GetPath(); +} + +void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const +{ + input_files.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):"), + from_u8(app_config->get_last_dir()), "", + file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + dialog.GetPaths(input_files); +} + +void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const +{ + input_file.Clear(); + wxFileDialog dialog(parent ? parent : GetTopWindow(), + _L("Choose one file (GCODE/.GCO/.G/.ngc/NGC):"), + app_config->get_last_dir(), "", + file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + input_file = dialog.GetPath(); +} + +bool GUI_App::switch_language() +{ + if (select_language()) { + recreate_GUI(_L("Changing of an application language") + dots); + return true; + } else { + return false; + } +} + +#ifdef __linux__ +static const wxLanguageInfo* linux_get_existing_locale_language(const wxLanguageInfo* language, + const wxLanguageInfo* system_language) +{ + constexpr size_t max_len = 50; + char path[max_len] = ""; + std::vector locales; + const std::string lang_prefix = into_u8(language->CanonicalName.BeforeFirst('_')); + + // Call locale -a so we can parse the output to get the list of available locales + // We expect lines such as "en_US.utf8". Pick ones starting with the language code + // we are switching to. Lines with different formatting will be removed later. + FILE* fp = popen("locale -a", "r"); + if (fp != NULL) { + while (fgets(path, max_len, fp) != NULL) { + std::string line(path); + line = line.substr(0, line.find('\n')); + if (boost::starts_with(line, lang_prefix)) + locales.push_back(line); + } + pclose(fp); + } + + // locales now contain all candidates for this language. + // Sort them so ones containing anything about UTF-8 are at the end. + std::sort(locales.begin(), locales.end(), [](const std::string& a, const std::string& b) + { + auto has_utf8 = [](const std::string & s) { + auto S = boost::to_upper_copy(s); + return S.find("UTF8") != std::string::npos || S.find("UTF-8") != std::string::npos; + }; + return ! has_utf8(a) && has_utf8(b); + }); + + // Remove the suffix behind a dot, if there is one. + for (std::string& s : locales) + s = s.substr(0, s.find(".")); + + // We just hope that dear Linux "locale -a" returns country codes + // in ISO 3166-1 alpha-2 code (two letter) format. + // https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes + // To be sure, remove anything not looking as expected + // (any number of lowercase letters, underscore, two uppercase letters). + locales.erase(std::remove_if(locales.begin(), + locales.end(), + [](const std::string& s) { + return ! std::regex_match(s, + std::regex("^[a-z]+_[A-Z]{2}$")); + }), + locales.end()); + + // Is there a candidate matching a country code of a system language? Move it to the end, + // while maintaining the order of matches, so that the best match ends up at the very end. + std::string system_country = "_" + into_u8(system_language->CanonicalName.AfterFirst('_')).substr(0, 2); + int cnt = locales.size(); + for (int i=0; iempty()) { + const std::string &locale = *it; + const wxLanguageInfo* lang = wxLocale::FindLanguageInfo(from_u8(locale)); + if (wxLocale::IsAvailable(lang->Language)) + return lang; + } + return language; +} +#endif + +int GUI_App::GetSingleChoiceIndex(const wxString& message, + const wxString& caption, + const wxArrayString& choices, + int initialSelection) +{ +#ifdef _WIN32 + wxSingleChoiceDialog dialog(nullptr, message, caption, choices); + wxGetApp().UpdateDlgDarkUI(&dialog); + + dialog.SetSelection(initialSelection); + return dialog.ShowModal() == wxID_OK ? dialog.GetSelection() : -1; +#else + return wxGetSingleChoiceIndex(message, caption, choices, initialSelection); +#endif +} + +// select language from the list of installed languages +bool GUI_App::select_language() +{ + wxArrayString translations = wxTranslations::Get()->GetAvailableTranslations(SLIC3R_APP_KEY); + std::vector language_infos; + language_infos.emplace_back(wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH)); + for (size_t i = 0; i < translations.GetCount(); ++ i) { + const wxLanguageInfo *langinfo = wxLocale::FindLanguageInfo(translations[i]); + if (langinfo != nullptr) + language_infos.emplace_back(langinfo); + } + sort_remove_duplicates(language_infos); + std::sort(language_infos.begin(), language_infos.end(), [](const wxLanguageInfo* l, const wxLanguageInfo* r) { return l->Description < r->Description; }); + + wxArrayString names; + names.Alloc(language_infos.size()); + + // Some valid language should be selected since the application start up. + const wxLanguage current_language = wxLanguage(m_wxLocale->GetLanguage()); + int init_selection = -1; + int init_selection_alt = -1; + int init_selection_default = -1; + for (size_t i = 0; i < language_infos.size(); ++ i) { + if (wxLanguage(language_infos[i]->Language) == current_language) + // The dictionary matches the active language and country. + init_selection = i; + else if ((language_infos[i]->CanonicalName.BeforeFirst('_') == m_wxLocale->GetCanonicalName().BeforeFirst('_')) || + // if the active language is Slovak, mark the Czech language as active. + (language_infos[i]->CanonicalName.BeforeFirst('_') == "cs" && m_wxLocale->GetCanonicalName().BeforeFirst('_') == "sk")) + // The dictionary matches the active language, it does not necessarily match the country. + init_selection_alt = i; + if (language_infos[i]->CanonicalName.BeforeFirst('_') == "en") + // This will be the default selection if the active language does not match any dictionary. + init_selection_default = i; + names.Add(language_infos[i]->Description); + } + if (init_selection == -1) + // This is the dictionary matching the active language. + init_selection = init_selection_alt; + if (init_selection != -1) + // This is the language to highlight in the choice dialog initially. + init_selection_default = init_selection; + + const long index = GetSingleChoiceIndex(_L("Select the language"), _L("Language"), names, init_selection_default); + // Try to load a new language. + if (index != -1 && (init_selection == -1 || init_selection != index)) { + const wxLanguageInfo *new_language_info = language_infos[index]; + if (this->load_language(new_language_info->CanonicalName, false)) { + // Save language at application config. + // Which language to save as the selected dictionary language? + // 1) Hopefully the language set to wxTranslations by this->load_language(), but that API is weird and we don't want to rely on its + // stability in the future: + // wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); + // 2) Current locale language may not match the dictionary name, see GH issue #3901 + // m_wxLocale->GetCanonicalName() + // 3) new_language_info->CanonicalName is a safe bet. It points to a valid dictionary name. + app_config->set("translation_language", new_language_info->CanonicalName.ToUTF8().data()); + app_config->save(); + return true; + } + } + + return false; +} + +// Load gettext translation files and activate them at the start of the application, +// based on the "translation_language" key stored in the application config. +bool GUI_App::load_language(wxString language, bool initial) +{ + if (initial) { + // There is a static list of lookup path prefixes in wxWidgets. Add ours. + wxFileTranslationsLoader::AddCatalogLookupPathPrefix(from_u8(localization_dir())); + // Get the active language from PrusaSlicer.ini, or empty string if the key does not exist. + language = app_config->get("translation_language"); + if (! language.empty()) + BOOST_LOG_TRIVIAL(trace) << boost::format("translation_language provided by PrusaSlicer.ini: %1%") % language; + + // Get the system language. + { + const wxLanguage lang_system = wxLanguage(wxLocale::GetSystemLanguage()); + if (lang_system != wxLANGUAGE_UNKNOWN) { + m_language_info_system = wxLocale::GetLanguageInfo(lang_system); + BOOST_LOG_TRIVIAL(trace) << boost::format("System language detected (user locales and such): %1%") % m_language_info_system->CanonicalName.ToUTF8().data(); + } + } + { + // Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance. + wxLocale temp_locale; + // Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code). + wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT); + // Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer + // and try to match them with the system specific "preferred languages". + // There seems to be a support for that on Windows and OSX, while on Linuxes the code just returns wxLocale::GetSystemLanguage(). + // The last parameter gets added to the list of detected dictionaries. This is a workaround + // for not having the English dictionary. Let's hope wxWidgets of various versions process this call the same way. + wxString best_language = wxTranslations::Get()->GetBestTranslation(SLIC3R_APP_KEY, wxLANGUAGE_ENGLISH); + if (! best_language.IsEmpty()) { + m_language_info_best = wxLocale::FindLanguageInfo(best_language); + BOOST_LOG_TRIVIAL(trace) << boost::format("Best translation language detected (may be different from user locales): %1%") % m_language_info_best->CanonicalName.ToUTF8().data(); + } + #ifdef __linux__ + wxString lc_all; + if (wxGetEnv("LC_ALL", &lc_all) && ! lc_all.IsEmpty()) { + // Best language returned by wxWidgets on Linux apparently does not respect LC_ALL. + // Disregard the "best" suggestion in case LC_ALL is provided. + m_language_info_best = nullptr; + } + #endif + } + } + + const wxLanguageInfo *language_info = language.empty() ? nullptr : wxLocale::FindLanguageInfo(language); + if (! language.empty() && (language_info == nullptr || language_info->CanonicalName.empty())) { + // Fix for wxWidgets issue, where the FindLanguageInfo() returns locales with undefined ANSII code (wxLANGUAGE_KONKANI or wxLANGUAGE_MANIPURI). + language_info = nullptr; + BOOST_LOG_TRIVIAL(error) << boost::format("Language code \"%1%\" is not supported") % language.ToUTF8().data(); + } + + if (language_info != nullptr && language_info->LayoutDirection == wxLayout_RightToLeft) { + BOOST_LOG_TRIVIAL(trace) << boost::format("The following language code requires right to left layout, which is not supported by PrusaSlicer: %1%") % language_info->CanonicalName.ToUTF8().data(); + language_info = nullptr; + } + + if (language_info == nullptr) { + // PrusaSlicer does not support the Right to Left languages yet. + if (m_language_info_system != nullptr && m_language_info_system->LayoutDirection != wxLayout_RightToLeft) + language_info = m_language_info_system; + if (m_language_info_best != nullptr && m_language_info_best->LayoutDirection != wxLayout_RightToLeft) + language_info = m_language_info_best; + if (language_info == nullptr) + language_info = wxLocale::GetLanguageInfo(wxLANGUAGE_ENGLISH_US); + } + + BOOST_LOG_TRIVIAL(trace) << boost::format("Switching wxLocales to %1%") % language_info->CanonicalName.ToUTF8().data(); + + // Alternate language code. + wxLanguage language_dict = wxLanguage(language_info->Language); + if (language_info->CanonicalName.BeforeFirst('_') == "sk") { + // Slovaks understand Czech well. Give them the Czech translation. + language_dict = wxLANGUAGE_CZECH; + BOOST_LOG_TRIVIAL(trace) << "Using Czech dictionaries for Slovak language"; + } + + // Select language for locales. This language may be different from the language of the dictionary. + if (language_info == m_language_info_best || language_info == m_language_info_system) { + // The current language matches user's default profile exactly. That's great. + } else if (m_language_info_best != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_best->CanonicalName.BeforeFirst('_')) { + // Use whatever the operating system recommends, if it the language code of the dictionary matches the recommended language. + // This allows a Swiss guy to use a German dictionary without forcing him to German locales. + language_info = m_language_info_best; + } else if (m_language_info_system != nullptr && language_info->CanonicalName.BeforeFirst('_') == m_language_info_system->CanonicalName.BeforeFirst('_')) + language_info = m_language_info_system; + +#ifdef __linux__ + // If we can't find this locale , try to use different one for the language + // instead of just reporting that it is impossible to switch. + if (! wxLocale::IsAvailable(language_info->Language)) { + std::string original_lang = into_u8(language_info->CanonicalName); + language_info = linux_get_existing_locale_language(language_info, m_language_info_system); + BOOST_LOG_TRIVIAL(trace) << boost::format("Can't switch language to %1% (missing locales). Using %2% instead.") + % original_lang % language_info->CanonicalName.ToUTF8().data(); + } +#endif + + if (! wxLocale::IsAvailable(language_info->Language)) { + // Loading the language dictionary failed. + wxString message = "Switching PrusaSlicer to language " + language_info->CanonicalName + " failed."; +#if !defined(_WIN32) && !defined(__APPLE__) + // likely some linux system + message += "\nYou may need to reconfigure the missing locales, likely by running the \"locale-gen\" and \"dpkg-reconfigure locales\" commands.\n"; +#endif + if (initial) + message + "\n\nApplication will close."; + wxMessageBox(message, "PrusaSlicer - Switching language failed", wxOK | wxICON_ERROR); + if (initial) + std::exit(EXIT_FAILURE); + else + return false; + } + + // Release the old locales, create new locales. + //FIXME wxWidgets cause havoc if the current locale is deleted. We just forget it causing memory leaks for now. + m_wxLocale.release(); + m_wxLocale = Slic3r::make_unique(); + m_wxLocale->Init(language_info->Language); + // 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); + 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. + //wxSetlocale(LC_NUMERIC, "C"); + Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data()); + return true; +} + +Tab* GUI_App::get_tab(Preset::Type type) +{ + for (Tab* tab: tabs_list) + if (tab->type() == type) + return tab->completed() ? tab : nullptr; // To avoid actions with no-completed Tab + return nullptr; +} + +ConfigOptionMode GUI_App::get_mode() +{ + if (!app_config->has("view_mode")) + return comSimple; + + const auto mode = app_config->get("view_mode"); + return mode == "expert" ? comExpert : + mode == "simple" ? comSimple : comAdvanced; +} + +void GUI_App::save_mode(const /*ConfigOptionMode*/int mode) +{ + const std::string mode_str = mode == comExpert ? "expert" : + mode == comSimple ? "simple" : "advanced"; + app_config->set("view_mode", mode_str); + app_config->save(); + update_mode(); +} + +// Update view mode according to selected menu +void GUI_App::update_mode() +{ + sidebar().update_mode(); + +#ifdef _MSW_DARK_MODE + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); +#endif + + for (auto tab : tabs_list) + tab->update_mode(); + + plater()->update_menus(); + plater()->canvas3D()->update_gizmos_on_off_state(); +} + +void GUI_App::add_config_menu(wxMenuBar *menu) +{ + auto local_menu = new wxMenu(); + wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt)); + + const auto config_wizard_name = _(ConfigWizard::name(true)); + const auto config_wizard_tooltip = from_u8((boost::format(_utf8(L("Run %s"))) % config_wizard_name).str()); + // Cmd+, is standard on OS X - what about other operating systems? + if (is_editor()) { + local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); + local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots")); + local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot")); + local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates")); + local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application")); +#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) + //if (DesktopIntegrationDialog::integration_possible()) + local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration")); +#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) + local_menu->AppendSeparator(); + } + local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots + +#ifdef __APPLE__ + "\tCtrl+,", +#else + "\tCtrl+P", +#endif + _L("Application preferences")); + wxMenu* mode_menu = nullptr; + if (is_editor()) { + local_menu->AppendSeparator(); + mode_menu = new wxMenu(); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple View Mode")); +// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced View Mode")); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX(L_CONTEXT("Advanced", "Mode"), "Mode"), _L("Advanced View Mode")); + mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _L("Expert"), _L("Expert View Mode")); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comExpert) evt.Check(true); }, config_id_base + ConfigMenuModeExpert); + + local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s View Mode"), SLIC3R_APP_NAME)); + } + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuLanguage, _L("&Language")); + if (is_editor()) { + local_menu->AppendSeparator(); + local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _L("Flash Printer &Firmware"), _L("Upload a firmware image into an Arduino based printer")); + // TODO: for when we're able to flash dictionaries + // local_menu->Append(config_id_base + FirmwareMenuDict, _L("Flash Language File"), _L("Upload a language dictionary file into a Prusa printer")); + } + + local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event) { + switch (event.GetId() - config_id_base) { + case ConfigMenuWizard: + run_wizard(ConfigWizard::RR_USER); + break; + case ConfigMenuUpdateConf: + check_updates(true); + break; + case ConfigMenuUpdateApp: + app_version_check(true); + break; +#ifdef __linux__ + case ConfigMenuDesktopIntegration: + show_desktop_integration_dialog(); + break; +#endif + case ConfigMenuTakeSnapshot: + // Take a configuration snapshot. + if (wxString action_name = _L("Taking a configuration snapshot"); + check_and_save_current_preset_changes(action_name, _L("Some presets are modified and the unsaved changes will not be captured by the configuration snapshot."), false, true)) { + wxTextEntryDialog dlg(nullptr, action_name, _L("Snapshot name")); + UpdateDlgDarkUI(&dlg); + + // set current normal font for dialog children, + // because of just dlg.SetFont(normal_font()) has no result; + for (auto child : dlg.GetChildren()) + child->SetFont(normal_font()); + + if (dlg.ShowModal() == wxID_OK) + if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error( + *app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()); + snapshot != nullptr) + app_config->set("on_snapshot", snapshot->id); + } + break; + case ConfigMenuSnapshots: + if (check_and_save_current_preset_changes(_L("Loading a configuration snapshot"), "", false)) { + std::string on_snapshot; + if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) + on_snapshot = app_config->get("on_snapshot"); + ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot); + dlg.ShowModal(); + if (!dlg.snapshot_to_activate().empty()) { + if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) && + ! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "", + GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate()))) + break; + try { + app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); + // Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system + // presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users + // are known to be creative and mess with the config files in various ways. + if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable); + ! all_substitutions.empty()) + show_substitutions_info(all_substitutions); + + // Load the currently selected preset into the GUI, update the preset selection box. + load_current_presets(); + } catch (std::exception &ex) { + GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what())); + } + } + } + break; + case ConfigMenuPreferences: + { + open_preferences(); + break; + } + case ConfigMenuLanguage: + { + /* Before change application language, let's check unsaved changes on 3D-Scene + * and draw user's attention to the application restarting after a language change + */ + { + // the dialog needs to be destroyed before the call to switch_language() + // or sometimes the application crashes into wxDialogBase() destructor + // so we put it into an inner scope + wxString title = is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME); + title += " - " + _L("Language selection"); + //wxMessageDialog dialog(nullptr, + MessageDialog dialog(nullptr, + _L("Switching the language will trigger application restart.\n" + "You will lose content of the plater.") + "\n\n" + + _L("Do you want to proceed?"), + title, + wxICON_QUESTION | wxOK | wxCANCEL); + if (dialog.ShowModal() == wxID_CANCEL) + return; + } + + switch_language(); + break; + } + case ConfigMenuFlashFirmware: + FirmwareDialog::run(mainframe); + break; + default: + break; + } + }); + + using std::placeholders::_1; + + if (mode_menu != nullptr) { + auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); }; + mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple); + mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced); + mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comExpert, _1), config_id_base + ConfigMenuModeExpert); + } + + menu->Append(local_menu, _L("&Configuration")); +} + +void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/) +{ + mainframe->preferences_dialog->show(highlight_option, tab_name); + + if (mainframe->preferences_dialog->recreate_GUI()) + recreate_GUI(_L("Restart application") + dots); + +#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER + if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed()) +#else + if (mainframe->preferences_dialog->seq_top_layer_only_changed()) +#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER + this->plater_->refresh_print(); + +#ifdef _WIN32 +#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + // file association is not possible anymore starting with Win 8 + if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { +#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (is_editor()) { + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); + } + else { + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); + } +#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + } +#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER +#endif // _WIN32 + + if (mainframe->preferences_dialog->settings_layout_changed()) { + // hide full main_sizer for mainFrame + mainframe->GetSizer()->Show(false); + mainframe->update_layout(); + mainframe->select_tab(size_t(0)); + } +} + +bool GUI_App::has_unsaved_preset_changes() const +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty()) + return true; + } + return false; +} + +bool GUI_App::has_current_preset_changes() const +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + return true; + } + return false; +} + +void GUI_App::update_saved_preset_from_current_preset() +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology)) + tab->update_saved_preset_from_current_preset(); + } +} + +std::vector GUI_App::get_active_preset_collections() const +{ + std::vector ret; + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* tab : tabs_list) + if (tab->supports_printer_technology(printer_technology)) + ret.push_back(tab->get_presets()); + return ret; +} + +// To notify the user whether he is aware that some preset changes will be lost, +// UnsavedChangesDialog: "Discard / Save / Cancel" +// This is called when: +// - Close Application & Current project isn't saved +// - Load Project & Current project isn't saved +// - Undo / Redo with change of print technologie +// - Loading snapshot +// - Loading config_file/bundle +// UnsavedChangesDialog: "Don't save / Save / Cancel" +// This is called when: +// - Exporting config_bundle +// - Taking snapshot +bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice/* = true*/, bool dont_save_insted_of_discard/* = false*/) +{ + if (has_current_preset_changes()) { + const std::string app_config_key = remember_choice ? "default_action_on_close_application" : ""; + int act_buttons = UnsavedChangesDialog::ActionButtons::SAVE; + if (dont_save_insted_of_discard) + act_buttons |= UnsavedChangesDialog::ActionButtons::DONT_SAVE; + UnsavedChangesDialog dlg(caption, header, app_config_key, act_buttons); + std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); + if (act == "none" && dlg.ShowModal() == wxID_CANCEL) + return false; + + if (dlg.save_preset()) // save selected changes + { + for (const std::pair& nt : dlg.get_names_and_types()) + preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); + + load_current_presets(false); + + // if we saved changes to the new presets, we should to + // synchronize config.ini with the current selections. + preset_bundle->export_selections(*app_config); + + MessageDialog(nullptr, _L_PLURAL("The preset modifications are successfully saved", + "The presets modifications are successfully saved", dlg.get_names_and_types().size())).ShowModal(); + } + } + + return true; +} + +void GUI_App::apply_keeped_preset_modifications() +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology)) + tab->apply_config_from_cache(); + } + load_current_presets(false); +} + +// This is called when creating new project or load another project +// OR close ConfigWizard +// to ask the user what should we do with unsaved changes for presets. +// New Project => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Cancel" +// => Current project isn't saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" +// Close ConfigWizard => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" +// Note: no_nullptr postponed_apply_of_keeped_changes indicates that thie function is called after ConfigWizard is closed +bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes/* = nullptr*/) +{ + if (has_current_preset_changes()) { + bool is_called_from_configwizard = postponed_apply_of_keeped_changes != nullptr; + + const std::string app_config_key = is_called_from_configwizard ? "" : "default_action_on_new_project"; + UnsavedChangesDialog dlg(caption, header, app_config_key, action_buttons); + std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); + if (act == "none" && dlg.ShowModal() == wxID_CANCEL) + return false; + + auto reset_modifications = [this, is_called_from_configwizard]() { + if (is_called_from_configwizard) + return; // no need to discared changes. It will be done fromConfigWizard closing + + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + tab->m_presets->discard_current_changes(); + } + load_current_presets(false); + }; + + if (dlg.discard()) + reset_modifications(); + else // save selected changes + { + const auto& preset_names_and_types = dlg.get_names_and_types(); + if (dlg.save_preset()) { + for (const std::pair& nt : preset_names_and_types) + preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); + + // if we saved changes to the new presets, we should to + // synchronize config.ini with the current selections. + preset_bundle->export_selections(*app_config); + + wxString text = _L_PLURAL("The preset modifications are successfully saved", + "The presets modifications are successfully saved", preset_names_and_types.size()); + if (!is_called_from_configwizard) + text += "\n\n" + _L("For new project all modifications will be reseted"); + + MessageDialog(nullptr, text).ShowModal(); + reset_modifications(); + } + else if (dlg.transfer_changes() && (dlg.has_unselected_options() || is_called_from_configwizard)) { + // execute this part of code only if not all modifications are keeping to the new project + // OR this function is called when ConfigWizard is closed and "Keep modifications" is selected + for (const std::pair& nt : preset_names_and_types) { + Preset::Type type = nt.second; + Tab* tab = get_tab(type); + std::vector selected_options = dlg.get_selected_options(type); + if (type == Preset::TYPE_PRINTER) { + auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); + if (it != selected_options.end()) { + // erase "extruders_count" option from the list + selected_options.erase(it); + // cache the extruders count + static_cast(tab)->cache_extruder_cnt(); + } + } + tab->cache_config_diff(selected_options); + if (!is_called_from_configwizard) + tab->m_presets->discard_current_changes(); + } + if (is_called_from_configwizard) + *postponed_apply_of_keeped_changes = true; + else + apply_keeped_preset_modifications(); + } + } + } + + return true; +} + +bool GUI_App::can_load_project() +{ + int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified.")); + if (saved_project == wxID_CANCEL || + (plater()->is_project_dirty() && saved_project == wxID_NO && + !check_and_save_current_preset_changes(_L("Project is loading"), _L("Opening new project while some presets are unsaved.")))) + return false; + return true; +} + +bool GUI_App::check_print_host_queue() +{ + wxString dirty; + std::vector> jobs; + // Get ongoing jobs from dialog + mainframe->m_printhost_queue_dlg->get_active_jobs(jobs); + if (jobs.empty()) + return true; + // Show dialog + wxString job_string = wxString(); + for (const auto& job : jobs) { + job_string += format_wxstr(" %1% : %2% \n", job.first, job.second); + } + wxString message; + message += _(L("The uploads are still ongoing")) + ":\n\n" + job_string +"\n" + _(L("Stop them and continue anyway?")); + //wxMessageDialog dialog(mainframe, + MessageDialog dialog(mainframe, + message, + wxString(SLIC3R_APP_NAME) + " - " + _(L("Ongoing uploads")), + wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); + if (dialog.ShowModal() == wxID_YES) + return true; + + // TODO: If already shown, bring forward + mainframe->m_printhost_queue_dlg->Show(); + return false; +} + +bool GUI_App::checked_tab(Tab* tab) +{ + bool ret = true; + if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end()) + ret = false; + return ret; +} + +// Update UI / Tabs to reflect changes in the currently loaded presets +void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/) +{ + // check printer_presets for the containing information about "Print Host upload" + // and create physical printer from it, if any exists + if (check_printer_presets_) + check_printer_presets(); + + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + this->plater()->set_printer_technology(printer_technology); + for (Tab *tab : tabs_list) + if (tab->supports_printer_technology(printer_technology)) { + if (tab->type() == Preset::TYPE_PRINTER) { + static_cast(tab)->update_pages(); + // Mark the plater to update print bed by tab->load_current_preset() from Plater::on_config_change(). + this->plater()->force_print_bed_update(); + } + tab->load_current_preset(); + } +} + +bool GUI_App::OnExceptionInMainLoop() +{ + generic_exception_handle(); + return false; +} + +#ifdef __APPLE__ +// This callback is called from wxEntry()->wxApp::CallOnInit()->NSApplication run +// that is, before GUI_App::OnInit(), so we have a chance to switch GUI_App +// to a G-code viewer. +void GUI_App::OSXStoreOpenFiles(const wxArrayString &fileNames) +{ + size_t num_gcodes = 0; + for (const wxString &filename : fileNames) + if (is_gcode_file(into_u8(filename))) + ++ num_gcodes; + if (fileNames.size() == num_gcodes) { + // Opening PrusaSlicer by drag & dropping a G-Code onto PrusaSlicer icon in Finder, + // just G-codes were passed. Switch to G-code viewer mode. + m_app_mode = EAppMode::GCodeViewer; + unlock_lockfile(get_instance_hash_string() + ".lock", data_dir() + "/cache/"); + if(app_config != nullptr) + delete app_config; + app_config = nullptr; + init_app_config(); + } + wxApp::OSXStoreOpenFiles(fileNames); +} +// wxWidgets override to get an event on open files. +void GUI_App::MacOpenFiles(const wxArrayString &fileNames) +{ + std::vector files; + std::vector gcode_files; + std::vector non_gcode_files; + for (const auto& filename : fileNames) { + if (is_gcode_file(into_u8(filename))) + gcode_files.emplace_back(filename); + else { + files.emplace_back(into_u8(filename)); + non_gcode_files.emplace_back(filename); + } + } + if (m_app_mode == EAppMode::GCodeViewer) { + // Running in G-code viewer. + // Load the first G-code into the G-code viewer. + // Or if no G-codes, send other files to slicer. + if (! gcode_files.empty()) { + if (m_post_initialized) + this->plater()->load_gcode(gcode_files.front()); + else + this->init_params->input_files = { into_u8(gcode_files.front()) }; + } + if (!non_gcode_files.empty()) + start_new_slicer(non_gcode_files, true); + } else { + if (! files.empty()) { + if (m_post_initialized) { + wxArrayString input_files; + for (size_t i = 0; i < non_gcode_files.size(); ++i) + input_files.push_back(non_gcode_files[i]); + this->plater()->load_files(input_files); + } else { + for (const auto &f : non_gcode_files) + this->init_params->input_files.emplace_back(into_u8(f)); + } + } + for (const wxString &filename : gcode_files) + start_new_gcodeviewer(&filename); + } +} +#endif /* __APPLE */ + +Sidebar& GUI_App::sidebar() +{ + return plater_->sidebar(); +} + +ObjectManipulation* GUI_App::obj_manipul() +{ + // If this method is called before plater_ has been initialized, return nullptr (to avoid a crash) + return (plater_ != nullptr) ? sidebar().obj_manipul() : nullptr; +} + +ObjectSettings* GUI_App::obj_settings() +{ + return sidebar().obj_settings(); +} + +ObjectList* GUI_App::obj_list() +{ + return sidebar().obj_list(); +} + +ObjectLayers* GUI_App::obj_layers() +{ + return sidebar().obj_layers(); +} + +Plater* GUI_App::plater() +{ + return plater_; +} + +const Plater* GUI_App::plater() const +{ + return plater_; +} + +Model& GUI_App::model() +{ + return plater_->model(); +} +wxBookCtrlBase* GUI_App::tab_panel() const +{ + return mainframe->m_tabpanel; +} + +NotificationManager * GUI_App::notification_manager() +{ + return plater_->get_notification_manager(); +} + +// extruders count from selected printer preset +int GUI_App::extruders_cnt() const +{ + const Preset& preset = preset_bundle->printers.get_selected_preset(); + return preset.printer_technology() == ptSLA ? 1 : + preset.config.option("nozzle_diameter")->values.size(); +} + +// extruders count from edited printer preset +int GUI_App::extruders_edited_cnt() const +{ + const Preset& preset = preset_bundle->printers.get_edited_preset(); + return preset.printer_technology() == ptSLA ? 1 : + preset.config.option("nozzle_diameter")->values.size(); +} + +wxString GUI_App::current_language_code_safe() const +{ + // Translate the language code to a code, for which Prusa Research maintains translations. + const std::map mapping { + { "cs", "cs_CZ", }, + { "sk", "cs_CZ", }, + { "de", "de_DE", }, + { "es", "es_ES", }, + { "fr", "fr_FR", }, + { "it", "it_IT", }, + { "ja", "ja_JP", }, + { "ko", "ko_KR", }, + { "pl", "pl_PL", }, + { "uk", "uk_UA", }, + { "zh", "zh_CN", }, + { "ru", "ru_RU", }, + }; + wxString language_code = this->current_language_code().BeforeFirst('_'); + auto it = mapping.find(language_code); + if (it != mapping.end()) + language_code = it->second; + else + language_code = "en_US"; + return language_code; +} + +void GUI_App::open_web_page_localized(const std::string &http_address) +{ + open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe(), nullptr, false); +} + +// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). +// Because of we can't to print the multi-part objects with SLA technology. +bool GUI_App::may_switch_to_SLA_preset(const wxString& caption) +{ + if (model_has_multi_part_objects(model())) { + show_info(nullptr, + _L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" + + _L("Please check your object list before preset changing."), + caption); + return false; + } + return true; +} + +bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page) +{ + wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); + + if (reason == ConfigWizard::RR_USER) { + if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) + return false; + } + + auto wizard = new ConfigWizard(mainframe); + const bool res = wizard->run(reason, start_page); + + if (res) { + load_current_presets(); + + // #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config() + if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) + may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard")); + } + + return res; +} + +void GUI_App::show_desktop_integration_dialog() +{ +#ifdef __linux__ + //wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); + DesktopIntegrationDialog dialog(mainframe); + dialog.ShowModal(); +#endif //__linux__ +} + +#if ENABLE_THUMBNAIL_GENERATOR_DEBUG +void GUI_App::gcode_thumbnails_debug() +{ + const std::string BEGIN_MASK = "; thumbnail begin"; + const std::string END_MASK = "; thumbnail end"; + std::string gcode_line; + bool reading_image = false; + unsigned int width = 0; + unsigned int height = 0; + + wxFileDialog dialog(GetTopWindow(), _L("Select a gcode file:"), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() != wxID_OK) + return; + + std::string in_filename = into_u8(dialog.GetPath()); + std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string(); + + boost::nowide::ifstream in_file(in_filename.c_str()); + std::vector rows; + std::string row; + if (in_file.good()) + { + while (std::getline(in_file, gcode_line)) + { + if (in_file.good()) + { + if (boost::starts_with(gcode_line, BEGIN_MASK)) + { + reading_image = true; + gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1); + std::string::size_type x_pos = gcode_line.find('x'); + std::string width_str = gcode_line.substr(0, x_pos); + width = (unsigned int)::atoi(width_str.c_str()); + std::string height_str = gcode_line.substr(x_pos + 1); + height = (unsigned int)::atoi(height_str.c_str()); + row.clear(); + } + else if (reading_image && boost::starts_with(gcode_line, END_MASK)) + { + std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png"; + boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary); + if (out_file.good()) + { + std::string decoded; + decoded.resize(boost::beast::detail::base64::decoded_size(row.size())); + decoded.resize(boost::beast::detail::base64::decode((void*)&decoded[0], row.data(), row.size()).first); + + out_file.write(decoded.c_str(), decoded.size()); + out_file.close(); + } + + reading_image = false; + width = 0; + height = 0; + rows.clear(); + } + else if (reading_image) + row += gcode_line.substr(2); + } + } + + in_file.close(); + } +} +#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG + +void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name) +{ + if (name.empty()) { return; } + const auto config_key = (boost::format("window_%1%") % name).str(); + + WindowMetrics metrics = WindowMetrics::from_window(window); + app_config->set(config_key, metrics.serialize()); + app_config->save(); +} + +void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized) +{ + if (name.empty()) { return; } + const auto config_key = (boost::format("window_%1%") % name).str(); + + if (! app_config->has(config_key)) { + window->Maximize(default_maximized); + return; + } + + auto metrics = WindowMetrics::deserialize(app_config->get(config_key)); + if (! metrics) { + window->Maximize(default_maximized); + return; + } + + const wxRect& rect = metrics->get_rect(); + + if (app_config->get("restore_win_position") == "1") { + // workaround for crash related to the positioning of the window on secondary monitor + app_config->set("restore_win_position", (boost::format("crashed_at_%1%_pos") % name).str()); + app_config->save(); + window->SetPosition(rect.GetPosition()); + + // workaround for crash related to the positioning of the window on secondary monitor + app_config->set("restore_win_position", (boost::format("crashed_at_%1%_size") % name).str()); + app_config->save(); + window->SetSize(rect.GetSize()); + + // revert "restore_win_position" value if application wasn't crashed + app_config->set("restore_win_position", "1"); + app_config->save(); + } + else + window->CenterOnScreen(); + + window->Maximize(metrics->get_maximized()); +} + +void GUI_App::window_pos_sanitize(wxTopLevelWindow* window) +{ + /*unsigned*/int display_idx = wxDisplay::GetFromWindow(window); + wxRect display; + if (display_idx == wxNOT_FOUND) { + display = wxDisplay(0u).GetClientArea(); + window->Move(display.GetTopLeft()); + } else { + display = wxDisplay(display_idx).GetClientArea(); + } + + auto metrics = WindowMetrics::from_window(window); + metrics.sanitize_for_display(display); + if (window->GetScreenRect() != metrics.get_rect()) { + window->SetSize(metrics.get_rect()); + } +} + +bool GUI_App::config_wizard_startup() +{ + if (!m_app_conf_exists || preset_bundle->printers.only_default_printers()) { + run_wizard(ConfigWizard::RR_DATA_EMPTY); + return true; + } else if (get_app_config()->legacy_datadir()) { + // Looks like user has legacy pre-vendorbundle data directory, + // explain what this is and run the wizard + + MsgDataLegacy dlg; + dlg.ShowModal(); + + run_wizard(ConfigWizard::RR_DATA_LEGACY); + return true; + } + return false; +} + +bool GUI_App::check_updates(const bool verbose) +{ + PresetUpdater::UpdateResult updater_result; + try { + updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION); + if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { + mainframe->Close(); + // Applicaiton is closing. + return false; + } + else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) { + m_app_conf_exists = true; + } + else if (verbose && updater_result == PresetUpdater::R_NOOP) { + MsgNoUpdates dlg; + dlg.ShowModal(); + } + } + catch (const std::exception & ex) { + show_error(nullptr, ex.what()); + } + // Applicaiton will continue. + return true; +} + +bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/) +{ + bool launch = true; + + // warning dialog containes a "Remember my choice" checkbox + std::string option_key = "suppress_hyperlinks"; + if (force_remember_choice || app_config->get(option_key).empty()) { + if (app_config->get(option_key).empty()) { + RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); + dialog.ShowCheckBox(_L("Remember my choice")); + auto answer = dialog.ShowModal(); + launch = answer == wxID_YES; + if (dialog.IsCheckBoxChecked()) { + wxString preferences_item = _L("Suppress to open hyperlink in browser"); + wxString msg = + _L("PrusaSlicer will remember your choice.") + "\n\n" + + _L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" + + format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item); + + MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); + if (msg_dlg.ShowModal() == wxID_CANCEL) + return false; + app_config->set(option_key, answer == wxID_NO ? "1" : "0"); + } + } + if (launch) + launch = app_config->get(option_key) != "1"; + } + // warning dialog doesn't containe a "Remember my choice" checkbox + // and will be shown only when "Suppress to open hyperlink in browser" is ON. + else if (app_config->get(option_key) == "1") { + MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); + launch = dialog.ShowModal() == wxID_YES; + } + + return launch && wxLaunchDefaultBrowser(url, flags); +} + +// static method accepting a wxWindow object as first parameter +// void warning_catcher{ +// my($self, $message_dialog) = @_; +// return sub{ +// my $message = shift; +// return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ; +// my @params = ($message, 'Warning', wxOK | wxICON_WARNING); +// $message_dialog +// ? $message_dialog->(@params) +// : Wx::MessageDialog->new($self, @params)->ShowModal; +// }; +// } + +// Do we need this function??? +// void GUI_App::notify(message) { +// auto frame = GetTopWindow(); +// // try harder to attract user attention on OS X +// if (!frame->IsActive()) +// frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO); +// +// // There used to be notifier using a Growl application for OSX, but Growl is dead. +// // The notifier also supported the Linux X D - bus notifications, but that support was broken. +// //TODO use wxNotificationMessage ? +// } + + +#ifdef __WXMSW__ +static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue) +{ + // see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association + wchar_t szValueCurrent[1000]; + DWORD dwType; + DWORD dwSize = sizeof(szValueCurrent); + + int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize); + + bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND; + + if ((iRC != ERROR_SUCCESS) && !bDidntExist) + // an error occurred + return false; + + if (!bDidntExist) { + if (dwType != REG_SZ) + // invalid type + return false; + + if (::wcscmp(szValueCurrent, pszValue) == 0) + // value already set + return false; + } + + DWORD dwDisposition; + HKEY hkey; + iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition); + bool ret = false; + if (iRC == ERROR_SUCCESS) { + iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t)); + if (iRC == ERROR_SUCCESS) + ret = true; + } + + RegCloseKey(hkey); + return ret; +} + +void GUI_App::associate_3mf_files() +{ + wchar_t app_path[MAX_PATH]; + ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); + + std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; + std::wstring prog_id = L"Prusa.Slicer.1"; + std::wstring prog_desc = L"PrusaSlicer"; + std::wstring prog_command = prog_path + L" \"%1\""; + std::wstring reg_base = L"Software\\Classes"; + std::wstring reg_extension = reg_base + L"\\.3mf"; + std::wstring reg_prog_id = reg_base + L"\\" + prog_id; + std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; + + bool is_new = false; + is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); + is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); + is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); + + if (is_new) + // notify Windows only when any of the values gets changed + ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); +} + +void GUI_App::associate_stl_files() +{ + wchar_t app_path[MAX_PATH]; + ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); + + std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; + std::wstring prog_id = L"Prusa.Slicer.1"; + std::wstring prog_desc = L"PrusaSlicer"; + std::wstring prog_command = prog_path + L" \"%1\""; + std::wstring reg_base = L"Software\\Classes"; + std::wstring reg_extension = reg_base + L"\\.stl"; + std::wstring reg_prog_id = reg_base + L"\\" + prog_id; + std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; + + bool is_new = false; + is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); + is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); + is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); + + if (is_new) + // notify Windows only when any of the values gets changed + ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); +} + +void GUI_App::associate_gcode_files() +{ + wchar_t app_path[MAX_PATH]; + ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); + + std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; + std::wstring prog_id = L"PrusaSlicer.GCodeViewer.1"; + std::wstring prog_desc = L"PrusaSlicerGCodeViewer"; + std::wstring prog_command = prog_path + L" \"%1\""; + std::wstring reg_base = L"Software\\Classes"; + std::wstring reg_extension = reg_base + L"\\.gcode"; + std::wstring reg_prog_id = reg_base + L"\\" + prog_id; + std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; + + bool is_new = false; + is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); + is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); + is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); + + if (is_new) + // notify Windows only when any of the values gets changed + ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); +} +#endif // __WXMSW__ + + +void GUI_App::on_version_read(wxCommandEvent& evt) +{ + app_config->set("version_online", into_u8(evt.GetString())); + app_config->save(); + std::string opt = app_config->get("notify_release"); + if (this->plater_ == nullptr || (opt != "all" && opt != "release")) { + return; + } + if (*Semver::parse(SLIC3R_VERSION) >= *Semver::parse(into_u8(evt.GetString()))) { + return; + } + // notification + /* + this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAvailable + , NotificationManager::NotificationLevel::ImportantNotificationLevel + , Slic3r::format(_u8L("New release version %1% is available."), evt.GetString()) + , _u8L("See Download page.") + , [](wxEvtHandler* evnthndlr) {wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); return true; } + ); + */ + // updater + // read triggered_by_user that was set when calling GUI_App::app_version_check + app_updater(m_app_updater->get_triggered_by_user()); +} + +void GUI_App::app_updater(bool from_user) +{ + DownloadAppData app_data = m_app_updater->get_app_data(); + + if (from_user && (!app_data.version || *app_data.version <= *Semver::parse(SLIC3R_VERSION))) + { + BOOST_LOG_TRIVIAL(info) << "There is no newer version online."; + MsgNoAppUpdates no_update_dialog; + no_update_dialog.ShowModal(); + return; + + } + + assert(!app_data.url.empty()); + assert(!app_data.target_path.empty()); + + // dialog with new version info + AppUpdateAvailableDialog dialog(*Semver::parse(SLIC3R_VERSION), *app_data.version); + auto dialog_result = dialog.ShowModal(); + // checkbox "do not show again" + if (dialog.disable_version_check()) { + app_config->set("notify_release", "none"); + } + // Doesn't wish to update + if (dialog_result != wxID_OK) { + return; + } + // dialog with new version download (installer or app dependent on system) including path selection + AppUpdateDownloadDialog dwnld_dlg(*app_data.version, app_data.target_path); + dialog_result = dwnld_dlg.ShowModal(); + // Doesn't wish to download + if (dialog_result != wxID_OK) { + return; + } + app_data.target_path =dwnld_dlg.get_download_path(); + + // start download + this->plater_->get_notification_manager()->push_download_progress_notification(_utf8("Download"), std::bind(&AppUpdater::cancel_callback, this->m_app_updater.get())); + app_data.start_after = dwnld_dlg.run_after_download(); + m_app_updater->set_app_data(std::move(app_data)); + m_app_updater->sync_download(); +} + +void GUI_App::app_version_check(bool from_user) +{ + if (from_user) { + if (m_app_updater->get_download_ongoing()) { + MessageDialog msgdlg(nullptr, _L("Download of new version is already ongoing. Do you wish to continue?"), _L("Notice"), wxYES_NO); + if (msgdlg.ShowModal() != wxID_YES) + return; + } + } + std::string version_check_url = app_config->version_check_url(); + m_app_updater->sync_version(version_check_url, from_user); +} + +} // GUI +} //Slic3r diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 660cfc7f0..3da843f89 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1,960 +1,967 @@ -#include "Preferences.hpp" -#include "OptionsGroup.hpp" -#include "GUI_App.hpp" -#include "Plater.hpp" -#include "MsgDialog.hpp" -#include "I18N.hpp" -#include "libslic3r/AppConfig.hpp" -#include -#include "Notebook.hpp" -#include "ButtonsDescription.hpp" -#include "OG_CustomCtrl.hpp" -#include "GLCanvas3D.hpp" - -namespace Slic3r { - - static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values& enum_keys_map) - { - t_config_enum_names names; - int cnt = 0; - for (const auto& kvp : enum_keys_map) - cnt = std::max(cnt, kvp.second); - cnt += 1; - names.assign(cnt, ""); - for (const auto& kvp : enum_keys_map) - names[kvp.second] = kvp.first; - return names; - } - -#define CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NAME) \ - static t_config_enum_names s_keys_names_##NAME = enum_names_from_keys_map(s_keys_map_##NAME); \ - template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values() { return s_keys_map_##NAME; } \ - template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names() { return s_keys_names_##NAME; } - - - - static const t_config_enum_values s_keys_map_NotifyReleaseMode = { - {"all", NotifyReleaseAll}, - {"release", NotifyReleaseOnly}, - {"none", NotifyReleaseNone}, - }; - - CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NotifyReleaseMode) - -namespace GUI { - -PreferencesDialog::PreferencesDialog(wxWindow* parent) : - DPIDialog(parent, wxID_ANY, _L("Preferences"), wxDefaultPosition, - wxDefaultSize, wxDEFAULT_DIALOG_STYLE) -{ -#ifdef __WXOSX__ - isOSX = true; -#endif - build(); - - m_highlighter.set_timer_owner(this, 0); -} - -static void update_color(wxColourPickerCtrl* color_pckr, const wxColour& color) -{ - if (color_pckr->GetColour() != color) { - color_pckr->SetColour(color); - wxPostEvent(color_pckr, wxCommandEvent(wxEVT_COLOURPICKER_CHANGED)); - } -} - -void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::string()*/, const std::string& tab_name/*= std::string()*/) -{ - int selected_tab = 0; - for ( ; selected_tab < int(tabs->GetPageCount()); selected_tab++) - if (tabs->GetPageText(selected_tab) == _(tab_name)) - break; - if (selected_tab < int(tabs->GetPageCount())) - tabs->SetSelection(selected_tab); - - if (!highlight_opt_key.empty()) - init_highlighter(highlight_opt_key); - - // cache input values for custom toolbar size - m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str()); - m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1"; - - // update colors for color pickers - update_color(m_sys_colour, wxGetApp().get_label_clr_sys()); - update_color(m_mod_colour, wxGetApp().get_label_clr_modified()); - - this->ShowModal(); -} - -static std::shared_ptrcreate_options_tab(const wxString& title, wxBookCtrlBase* tabs) -{ - wxPanel* tab = new wxPanel(tabs, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); - tabs->AddPage(tab, _(title)); - tab->SetFont(wxGetApp().normal_font()); - - wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); - sizer->SetSizeHints(tab); - tab->SetSizer(sizer); - - std::shared_ptr optgroup = std::make_shared(tab); - optgroup->label_width = 40; - optgroup->set_config_category_and_type(title, int(Preset::TYPE_PREFERENCES)); - return optgroup; -} - -static void activate_options_tab(std::shared_ptr optgroup) -{ - optgroup->activate([](){}, wxALIGN_RIGHT); - optgroup->update_visibility(comSimple); - wxBoxSizer* sizer = static_cast(static_cast(optgroup->parent())->GetSizer()); - sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); - - // apply sercher - wxGetApp().sidebar().get_searcher().append_preferences_options(optgroup->get_lines()); -} - -static void append_bool_option( std::shared_ptr optgroup, - const std::string& opt_key, - const std::string& label, - const std::string& tooltip, - bool def_val, - ConfigOptionMode mode = comSimple) -{ - ConfigOptionDef def = {opt_key, coBool}; - def.label = label; - def.tooltip = tooltip; - def.mode = mode; - def.set_default_value(new ConfigOptionBool{ def_val }); - Option option(def, opt_key); - optgroup->append_single_option_line(option); - - // fill data to the Search Dialog - wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); -} - -static void append_enum_option( std::shared_ptr optgroup, - const std::string& opt_key, - const std::string& label, - const std::string& tooltip, - const ConfigOption* def_val, - const t_config_enum_values *enum_keys_map, - std::initializer_list enum_values, - std::initializer_list enum_labels, - ConfigOptionMode mode = comSimple) -{ - ConfigOptionDef def = {opt_key, coEnum }; - def.label = label; - def.tooltip = tooltip; - def.mode = mode; - def.enum_keys_map = enum_keys_map; - def.enum_values = std::vector(enum_values); - def.enum_labels = std::vector(enum_labels); - - def.set_default_value(def_val); - Option option(def, opt_key); - optgroup->append_single_option_line(option); - - // fill data to the Search Dialog - wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); -} - -static void append_preferences_option_to_searcer(std::shared_ptr optgroup, - const std::string& opt_key, - const wxString& label) -{ - // fill data to the Search Dialog - wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); - // apply sercher - wxGetApp().sidebar().get_searcher().append_preferences_option(Line(opt_key, label, "")); -} - -void PreferencesDialog::build() -{ -#ifdef _WIN32 - wxGetApp().UpdateDarkUI(this); -#else - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); -#endif - const wxFont& font = wxGetApp().normal_font(); - SetFont(font); - - auto app_config = get_app_config(); - -#ifdef _MSW_DARK_MODE - tabs = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNB_DEFAULT); -#else - tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL |wxNB_NOPAGETHEME | wxNB_DEFAULT ); - tabs->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); -#endif - - // Add "General" tab - m_optgroup_general = create_options_tab(L("General"), tabs); - m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (auto it = m_values.find(opt_key); it != m_values.end()) { - m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected - return; - } - if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset" || opt_key == "default_action_on_new_project") - m_values[opt_key] = boost::any_cast(value) ? "none" : "discard"; - else if (opt_key == "default_action_on_dirty_project") - m_values[opt_key] = boost::any_cast(value) ? "" : "0"; - else - m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; - }; - - bool is_editor = wxGetApp().is_editor(); - - if (is_editor) { - append_bool_option(m_optgroup_general, "remember_output_path", - L("Remember output directory"), - L("If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files."), - app_config->has("remember_output_path") ? app_config->get("remember_output_path") == "1" : true); - - append_bool_option(m_optgroup_general, "autocenter", - L("Auto-center parts"), - L("If this is enabled, Slic3r will auto-center objects around the print bed center."), - app_config->get("autocenter") == "1"); - - append_bool_option(m_optgroup_general, "background_processing", - L("Background processing"), - L("If this is enabled, Slic3r will pre-process objects as soon " - "as they\'re loaded in order to save time when exporting G-code."), - app_config->get("background_processing") == "1"); - - m_optgroup_general->append_separator(); - - // Please keep in sync with ConfigWizard - append_bool_option(m_optgroup_general, "export_sources_full_pathnames", - L("Export sources full pathnames to 3mf and amf"), - L("If enabled, allows the Reload from disk command to automatically find and load the files when invoked."), - app_config->get("export_sources_full_pathnames") == "1"); - -#ifdef _WIN32 - // Please keep in sync with ConfigWizard - append_bool_option(m_optgroup_general, "associate_3mf", - L("Associate .3mf files to PrusaSlicer"), - L("If enabled, sets PrusaSlicer as default application to open .3mf files."), - app_config->get("associate_3mf") == "1"); - - append_bool_option(m_optgroup_general, "associate_stl", - L("Associate .stl files to PrusaSlicer"), - L("If enabled, sets PrusaSlicer as default application to open .stl files."), - app_config->get("associate_stl") == "1"); -#endif // _WIN32 - - m_optgroup_general->append_separator(); - - // Please keep in sync with ConfigWizard - append_bool_option(m_optgroup_general, "preset_update", - L("Update built-in Presets automatically"), - L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded " - "into a separate temporary location. When a new preset version becomes available it is offered at application startup."), - app_config->get("preset_update") == "1"); - - append_bool_option(m_optgroup_general, "no_defaults", - L("Suppress \" - default - \" presets"), - L("Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available."), - app_config->get("no_defaults") == "1"); - - append_bool_option(m_optgroup_general, "show_incompatible_presets", - L("Show incompatible print and filament presets"), - L("When checked, the print and filament presets are shown in the preset editor " - "even if they are marked as incompatible with the active printer"), - app_config->get("show_incompatible_presets") == "1"); - - m_optgroup_general->append_separator(); - - append_bool_option(m_optgroup_general, "show_drop_project_dialog", - L("Show drop project dialog"), - L("When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load."), - app_config->get("show_drop_project_dialog") == "1"); - - append_bool_option(m_optgroup_general, "single_instance", -#if __APPLE__ - L("Allow just a single PrusaSlicer instance"), - L("On OSX there is always only one instance of app running by default. However it is allowed to run multiple instances " - "of same app from the command line. In such case this settings will allow only one instance."), -#else - L("Allow just a single PrusaSlicer instance"), - L("If this is enabled, when starting PrusaSlicer and another instance of the same PrusaSlicer is already running, that instance will be reactivated instead."), -#endif - app_config->has("single_instance") ? app_config->get("single_instance") == "1" : false ); - - m_optgroup_general->append_separator(); - - append_bool_option(m_optgroup_general, "default_action_on_dirty_project", - L("Ask for unsaved changes in project"), - L("Always ask for unsaved changes in project, when: \n" - "- Closing PrusaSlicer,\n" - "- Loading or creating a new project"), - app_config->get("default_action_on_dirty_project").empty()); - - m_optgroup_general->append_separator(); - - append_bool_option(m_optgroup_general, "default_action_on_close_application", - L("Ask to save unsaved changes in presets when closing the application or when loading a new project"), - L("Always ask for unsaved changes in presets, when: \n" - "- Closing PrusaSlicer while some presets are modified,\n" - "- Loading a new project while some presets are modified"), - app_config->get("default_action_on_close_application") == "none"); - - append_bool_option(m_optgroup_general, "default_action_on_select_preset", - L("Ask for unsaved changes in presets when selecting new preset"), - L("Always ask for unsaved changes in presets when selecting new preset or resetting a preset"), - app_config->get("default_action_on_select_preset") == "none"); - - append_bool_option(m_optgroup_general, "default_action_on_new_project", - L("Ask for unsaved changes in presets when creating new project"), - L("Always ask for unsaved changes in presets when creating new project"), - app_config->get("default_action_on_new_project") == "none"); - } -#ifdef _WIN32 - else { - append_bool_option(m_optgroup_general, "associate_gcode", - L("Associate .gcode files to PrusaSlicer G-code Viewer"), - L("If enabled, sets PrusaSlicer G-code Viewer as default application to open .gcode files."), - app_config->get("associate_gcode") == "1"); - } -#endif // _WIN32 - -#if __APPLE__ - append_bool_option(m_optgroup_general, "use_retina_opengl", - L("Use Retina resolution for the 3D scene"), - L("If enabled, the 3D scene will be rendered in Retina resolution. " - "If you are experiencing 3D performance problems, disabling this option may help."), - app_config->get("use_retina_opengl") == "1"); -#endif - - m_optgroup_general->append_separator(); - - // Show/Hide splash screen - append_bool_option(m_optgroup_general, "show_splash_screen", - L("Show splash screen"), - L("Show splash screen"), - app_config->get("show_splash_screen") == "1"); - - append_bool_option(m_optgroup_general, "restore_win_position", - L("Restore window position on start"), - L("If enabled, PrusaSlicer will be open at the position it was closed"), - app_config->get("restore_win_position") == "1"); - - // Clear Undo / Redo stack on new project - append_bool_option(m_optgroup_general, "clear_undo_redo_stack_on_new_project", - L("Clear Undo / Redo stack on new project"), - L("Clear Undo / Redo stack on new project or when an existing project is loaded."), - app_config->get("clear_undo_redo_stack_on_new_project") == "1"); - -#if defined(_WIN32) || defined(__APPLE__) - append_bool_option(m_optgroup_general, "use_legacy_3DConnexion", - L("Enable support for legacy 3DConnexion devices"), - L("If enabled, the legacy 3DConnexion devices settings dialog is available by pressing CTRL+M"), - app_config->get("use_legacy_3DConnexion") == "1"); -#endif // _WIN32 || __APPLE__ - - activate_options_tab(m_optgroup_general); - - // Add "Camera" tab - m_optgroup_camera = create_options_tab(L("Camera"), tabs); - m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (auto it = m_values.find(opt_key);it != m_values.end()) { - m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected - return; - } - m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; - }; - - append_bool_option(m_optgroup_camera, "use_perspective_camera", - L("Use perspective camera"), - L("If enabled, use perspective camera. If not enabled, use orthographic camera."), - app_config->get("use_perspective_camera") == "1"); - - append_bool_option(m_optgroup_camera, "use_free_camera", - L("Use free camera"), - L("If enabled, use free camera. If not enabled, use constrained camera."), - app_config->get("use_free_camera") == "1"); - - append_bool_option(m_optgroup_camera, "reverse_mouse_wheel_zoom", - L("Reverse direction of zoom with mouse wheel"), - L("If enabled, reverses the direction of zoom with mouse wheel"), - app_config->get("reverse_mouse_wheel_zoom") == "1"); - - activate_options_tab(m_optgroup_camera); - - // Add "GUI" tab - m_optgroup_gui = create_options_tab(L("GUI"), tabs); - m_optgroup_gui->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "notify_release") { - int val_int = boost::any_cast(value); - for (const auto& item : s_keys_map_NotifyReleaseMode) { - if (item.second == val_int) { - m_values[opt_key] = item.first; - return; - } - } - } - if (opt_key == "use_custom_toolbar_size") { - m_icon_size_sizer->ShowItems(boost::any_cast(value)); - refresh_og(m_optgroup_gui); - get_app_config()->set("use_custom_toolbar_size", boost::any_cast(value) ? "1" : "0"); - get_app_config()->save(); - wxGetApp().plater()->get_current_canvas3D()->render(); - return; - } - if (opt_key == "tabs_as_menu") { - bool disable_new_layout = boost::any_cast(value); - m_rb_new_settings_layout_mode->Show(!disable_new_layout); - if (disable_new_layout && m_rb_new_settings_layout_mode->GetValue()) { - m_rb_new_settings_layout_mode->SetValue(false); - m_rb_old_settings_layout_mode->SetValue(true); - } - refresh_og(m_optgroup_gui); - } - - if (auto it = m_values.find(opt_key); it != m_values.end()) { - m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected - return; - } - - if (opt_key == "suppress_hyperlinks") - m_values[opt_key] = boost::any_cast(value) ? "1" : ""; - else - m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; - }; - - append_bool_option(m_optgroup_gui, "seq_top_layer_only", - L("Sequential slider applied only to top layer"), - L("If enabled, changes made using the sequential slider, in preview, apply only to gcode top layer." - "If disabled, changes made using the sequential slider, in preview, apply to the whole gcode."), - app_config->get("seq_top_layer_only") == "1"); - - if (is_editor) { - append_bool_option(m_optgroup_gui, "show_collapse_button", - L("Show sidebar collapse/expand button"), - L("If enabled, the button for the collapse sidebar will be appeared in top right corner of the 3D Scene"), - app_config->get("show_collapse_button") == "1"); - - append_bool_option(m_optgroup_gui, "suppress_hyperlinks", - L("Suppress to open hyperlink in browser"), - L("If enabled, PrusaSlicer will not open a hyperlinks in your browser."), - //L("If enabled, the descriptions of configuration parameters in settings tabs wouldn't work as hyperlinks. " - // "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."), - app_config->get("suppress_hyperlinks") == "1"); - - append_bool_option(m_optgroup_gui, "color_mapinulation_panel", - L("Use colors for axes values in Manipulation panel"), - L("If enabled, the axes names and axes values will be colorized according to the axes colors. " - "If disabled, old UI will be used."), - app_config->get("color_mapinulation_panel") == "1"); - - append_bool_option(m_optgroup_gui, "order_volumes", - L("Order object volumes by types"), - L("If enabled, volumes will be always ordered inside the object. Correct order is Model Part, Negative Volume, Modifier, Support Blocker and Support Enforcer. " - "If disabled, you can reorder Model Parts, Negative Volumes and Modifiers. But one of the model parts have to be on the first place."), - app_config->get("order_volumes") == "1"); - -#if ENABLE_SHOW_NON_MANIFOLD_EDGES - append_bool_option(m_optgroup_gui, "non_manifold_edges", - L("Show non-manifold edges"), - L("If enabled, shows non-manifold edges."), - app_config->get("non_manifold_edges") == "1"); -#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES - -#ifdef _MSW_DARK_MODE - append_bool_option(m_optgroup_gui, "tabs_as_menu", - L("Set settings tabs as menu items (experimental)"), - L("If enabled, Settings Tabs will be placed as menu items. If disabled, old UI will be used."), - app_config->get("tabs_as_menu") == "1"); -#endif - - m_optgroup_gui->append_separator(); - - append_bool_option(m_optgroup_gui, "show_hints", - L("Show \"Tip of the day\" notification after start"), - L("If enabled, useful hints are displayed at startup."), - app_config->get("show_hints") == "1"); - - append_enum_option(m_optgroup_gui, "notify_release", - L("Notify about new releases"), - L("You will be notified about new release after startup acordingly: All = Regular release and alpha / beta releases. Release only = regular release."), - new ConfigOptionEnum(static_cast(s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")))), - &ConfigOptionEnum::get_enum_values(), - {"all", "release", "none"}, - {L("All"), L("Release only"), L("None")}); - - m_optgroup_gui->append_separator(); - - append_bool_option(m_optgroup_gui, "use_custom_toolbar_size", - L("Use custom size for toolbar icons"), - L("If enabled, you can change size of toolbar icons manually."), - app_config->get("use_custom_toolbar_size") == "1"); - } - - activate_options_tab(m_optgroup_gui); - - if (is_editor) { - // set Field for notify_release to its value to activate the object - boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")); - m_optgroup_gui->get_field("notify_release")->set_value(val, false); - - create_icon_size_slider(); - m_icon_size_sizer->ShowItems(app_config->get("use_custom_toolbar_size") == "1"); - - create_settings_mode_widget(); - create_settings_text_color_widget(); - -#if ENABLE_ENVIRONMENT_MAP - // Add "Render" tab - m_optgroup_render = create_options_tab(L("Render"), tabs); - m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (auto it = m_values.find(opt_key); it != m_values.end()) { - m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected - return; - } - m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; - }; - - append_bool_option(m_optgroup_render, "use_environment_map", - L("Use environment map"), - L("If enabled, renders object using the environment map."), - app_config->get("use_environment_map") == "1"); - - activate_options_tab(m_optgroup_render); -#endif // ENABLE_ENVIRONMENT_MAP - -#ifdef _WIN32 - // Add "Dark Mode" tab - m_optgroup_dark_mode = create_options_tab(_L("Dark mode (experimental)"), tabs); - m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (auto it = m_values.find(opt_key); it != m_values.end()) { - m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected - return; - } - m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; - }; - - append_bool_option(m_optgroup_dark_mode, "dark_color_mode", - L("Enable dark mode"), - L("If enabled, UI will use Dark mode colors. If disabled, old UI will be used."), - app_config->get("dark_color_mode") == "1"); - - if (wxPlatformInfo::Get().GetOSMajorVersion() >= 10) // Use system menu just for Window newer then Windows 10 - // Use menu with ownerdrawn items by default on systems older then Windows 10 - { - append_bool_option(m_optgroup_dark_mode, "sys_menu_enabled", - L("Use system menu for application"), - L("If enabled, application will use the standart Windows system menu,\n" - "but on some combination od display scales it can look ugly. If disabled, old UI will be used."), - app_config->get("sys_menu_enabled") == "1"); - } - - activate_options_tab(m_optgroup_dark_mode); -#endif //_WIN32 - } - - // update alignment of the controls for all tabs - update_ctrls_alignment(); - - auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(tabs, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); - - auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); - this->Bind(wxEVT_BUTTON, &PreferencesDialog::accept, this, wxID_OK); - this->Bind(wxEVT_BUTTON, &PreferencesDialog::revert, this, wxID_CANCEL); - - for (int id : {wxID_OK, wxID_CANCEL}) - wxGetApp().UpdateDarkUI(static_cast(FindWindowById(id, this))); - - sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM | wxTOP, 10); - - SetSizer(sizer); - sizer->SetSizeHints(this); - this->CenterOnParent(); -} - -std::vector PreferencesDialog::optgroups() -{ - std::vector out; - out.reserve(4); - for (ConfigOptionsGroup* opt : { m_optgroup_general.get(), m_optgroup_camera.get(), m_optgroup_gui.get() -#ifdef _WIN32 - , m_optgroup_dark_mode.get() -#endif // _WIN32 -#if ENABLE_ENVIRONMENT_MAP - , m_optgroup_render.get() -#endif // ENABLE_ENVIRONMENT_MAP - }) - if (opt) - out.emplace_back(opt); - return out; -} - -void PreferencesDialog::update_ctrls_alignment() -{ - int max_ctrl_width{ 0 }; - for (ConfigOptionsGroup* og : this->optgroups()) - if (int max = og->custom_ctrl->get_max_win_width(); - max_ctrl_width < max) - max_ctrl_width = max; - if (max_ctrl_width) - for (ConfigOptionsGroup* og : this->optgroups()) - og->custom_ctrl->set_max_win_width(max_ctrl_width); -} - -void PreferencesDialog::accept(wxEvent&) -{ - std::vector options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled" }; - - for (const std::string& option : options_to_recreate_GUI) { - if (m_values.find(option) != m_values.end()) { - wxString title = wxGetApp().is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME); - title += " - " + _L("Changes for the critical options"); - MessageDialog dialog(nullptr, - _L("Changing some options will trigger application restart.\n" - "You will lose the content of the plater.") + "\n\n" + - _L("Do you want to proceed?"), - title, - wxICON_QUESTION | wxYES | wxNO); - if (dialog.ShowModal() == wxID_YES) { - m_recreate_GUI = true; - } - else { - for (const std::string& option : options_to_recreate_GUI) - m_values.erase(option); - } - break; - } - } - - auto app_config = get_app_config(); - - m_seq_top_layer_only_changed = false; - if (auto it = m_values.find("seq_top_layer_only"); it != m_values.end()) - m_seq_top_layer_only_changed = app_config->get("seq_top_layer_only") != it->second; - - m_settings_layout_changed = false; - for (const std::string& key : { "old_settings_layout_mode", - "new_settings_layout_mode", - "dlg_settings_layout_mode" }) - { - auto it = m_values.find(key); - if (it != m_values.end() && app_config->get(key) != it->second) { - m_settings_layout_changed = true; - break; - } - } - -#if 0 //#ifdef _WIN32 // #ysDarkMSW - Allow it when we deside to support the sustem colors for application - if (m_values.find("always_dark_color_mode") != m_values.end()) - wxGetApp().force_sys_colors_update(); -#endif - - for (std::map::iterator it = m_values.begin(); it != m_values.end(); ++it) - app_config->set(it->first, it->second); - - app_config->save(); - if (wxGetApp().is_editor()) { - wxGetApp().set_label_clr_sys(m_sys_colour->GetColour()); - wxGetApp().set_label_clr_modified(m_mod_colour->GetColour()); - } - - EndModal(wxID_OK); - -#ifdef _WIN32 - if (m_values.find("dark_color_mode") != m_values.end()) - wxGetApp().force_colors_update(); -#ifdef _MSW_DARK_MODE - if (m_values.find("sys_menu_enabled") != m_values.end()) - wxGetApp().force_menu_update(); -#endif //_MSW_DARK_MODE -#endif // _WIN32 - - wxGetApp().update_ui_from_settings(); - clear_cache(); -} - -void PreferencesDialog::revert(wxEvent&) -{ - auto app_config = get_app_config(); - - bool save_app_config = false; - if (m_custom_toolbar_size != atoi(app_config->get("custom_toolbar_size").c_str())) { - app_config->set("custom_toolbar_size", (boost::format("%d") % m_custom_toolbar_size).str()); - m_icon_size_slider->SetValue(m_custom_toolbar_size); - save_app_config |= true; - } - if (m_use_custom_toolbar_size != (get_app_config()->get("use_custom_toolbar_size") == "1")) { - app_config->set("use_custom_toolbar_size", m_use_custom_toolbar_size ? "1" : "0"); - save_app_config |= true; - - m_optgroup_gui->set_value("use_custom_toolbar_size", m_use_custom_toolbar_size); - m_icon_size_sizer->ShowItems(m_use_custom_toolbar_size); - refresh_og(m_optgroup_gui); - } - if (save_app_config) - app_config->save(); - - - for (auto value : m_values) { - bool reverted = false; - const std::string& key = value.first; - - if (key == "default_action_on_dirty_project") { - m_optgroup_general->set_value(key, app_config->get(key).empty()); - continue; - } - if (key == "default_action_on_close_application" || key == "default_action_on_select_preset" || key == "default_action_on_new_project") { - m_optgroup_general->set_value(key, app_config->get(key) == "none"); - continue; - } - if (key == "notify_release") { - m_optgroup_gui->set_value(key, s_keys_map_NotifyReleaseMode.at(app_config->get(key))); - continue; - } - if (key == "old_settings_layout_mode") { - m_rb_old_settings_layout_mode->SetValue(app_config->get(key) == "1"); - continue; - } - if (key == "new_settings_layout_mode") { - m_rb_new_settings_layout_mode->SetValue(app_config->get(key) == "1"); - continue; - } - if (key == "dlg_settings_layout_mode") { - m_rb_dlg_settings_layout_mode->SetValue(app_config->get(key) == "1"); - continue; - } - - for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui -#ifdef _WIN32 - , m_optgroup_dark_mode -#endif // _WIN32 -#if ENABLE_ENVIRONMENT_MAP - , m_optgroup_render -#endif // ENABLE_ENVIRONMENT_MAP - }) { - if (opt_group->set_value(key, app_config->get(key) == "1")) - break; - } - if (key == "tabs_as_menu") { - m_rb_new_settings_layout_mode->Show(app_config->get(key) != "1"); - refresh_og(m_optgroup_gui); - continue; - } - } - - clear_cache(); - EndModal(wxID_CANCEL); -} - -void PreferencesDialog::msw_rescale() -{ - for (ConfigOptionsGroup* og : this->optgroups()) - og->msw_rescale(); -#ifdef _WIN32 - m_optgroup_dark_mode->msw_rescale(); -#endif //_WIN32 -#if ENABLE_ENVIRONMENT_MAP - m_optgroup_render->msw_rescale(); -#endif // ENABLE_ENVIRONMENT_MAP - - msw_buttons_rescale(this, em_unit(), { wxID_OK, wxID_CANCEL }); - - layout(); -} - -void PreferencesDialog::on_sys_color_changed() -{ -#ifdef _WIN32 - wxGetApp().UpdateDlgDarkUI(this); -#endif -} - -void PreferencesDialog::layout() -{ - const int em = em_unit(); - - SetMinSize(wxSize(47 * em, 28 * em)); - Fit(); - - Refresh(); -} - -void PreferencesDialog::clear_cache() -{ - m_values.clear(); - m_custom_toolbar_size = -1; -} - -void PreferencesDialog::refresh_og(std::shared_ptr og) -{ - og->parent()->Layout(); - tabs->Layout(); - this->layout(); -} - -void PreferencesDialog::create_icon_size_slider() -{ - const auto app_config = get_app_config(); - - const int em = em_unit(); - - m_icon_size_sizer = new wxBoxSizer(wxHORIZONTAL); - - wxWindow* parent = m_optgroup_gui->parent(); - wxGetApp().UpdateDarkUI(parent); - - if (isOSX) - // For correct rendering of the slider and value label under OSX - // we should use system default background - parent->SetBackgroundStyle(wxBG_STYLE_ERASE); - - auto label = new wxStaticText(parent, wxID_ANY, _L("Icon size in a respect to the default size") + " (%) :"); - - m_icon_size_sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL| wxRIGHT | (isOSX ? 0 : wxLEFT), em); - - const int def_val = atoi(app_config->get("custom_toolbar_size").c_str()); - - long style = wxSL_HORIZONTAL; - if (!isOSX) - style |= wxSL_LABELS | wxSL_AUTOTICKS; - - m_icon_size_slider = new wxSlider(parent, wxID_ANY, def_val, 30, 100, - wxDefaultPosition, wxDefaultSize, style); - - m_icon_size_slider->SetTickFreq(10); - m_icon_size_slider->SetPageSize(10); - m_icon_size_slider->SetToolTip(_L("Select toolbar icon size in respect to the default one.")); - - m_icon_size_sizer->Add(m_icon_size_slider, 1, wxEXPAND); - - wxStaticText* val_label{ nullptr }; - if (isOSX) { - val_label = new wxStaticText(parent, wxID_ANY, wxString::Format("%d", def_val)); - m_icon_size_sizer->Add(val_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, em); - } - - m_icon_size_slider->Bind(wxEVT_SLIDER, ([this, val_label, app_config](wxCommandEvent e) { - auto val = m_icon_size_slider->GetValue(); - - app_config->set("custom_toolbar_size", (boost::format("%d") % val).str()); - app_config->save(); - wxGetApp().plater()->get_current_canvas3D()->render(); - - if (val_label) - val_label->SetLabelText(wxString::Format("%d", val)); - }), m_icon_size_slider->GetId()); - - for (wxWindow* win : std::vector{ m_icon_size_slider, label, val_label }) { - if (!win) continue; - win->SetFont(wxGetApp().normal_font()); - - if (isOSX) continue; // under OSX we use wxBG_STYLE_ERASE - win->SetBackgroundStyle(wxBG_STYLE_PAINT); - } - - m_optgroup_gui->sizer->Add(m_icon_size_sizer, 0, wxEXPAND | wxALL, em); -} - -void PreferencesDialog::create_settings_mode_widget() -{ - wxWindow* parent = m_optgroup_gui->parent(); - wxGetApp().UpdateDarkUI(parent); - - wxString title = L("Layout Options"); - wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _(title)); - wxGetApp().UpdateDarkUI(stb); - if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); - stb->SetFont(wxGetApp().normal_font()); - - wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL); - - auto app_config = get_app_config(); - std::vector choices = { _L("Old regular layout with the tab bar"), - _L("New layout, access via settings button in the top menu"), - _L("Settings in non-modal window") }; - int id = -1; - auto add_radio = [this, parent, stb_sizer, choices](wxRadioButton** rb, int id, bool select) { - *rb = new wxRadioButton(parent, wxID_ANY, choices[id], wxDefaultPosition, wxDefaultSize, id == 0 ? wxRB_GROUP : 0); - stb_sizer->Add(*rb); - (*rb)->SetValue(select); - (*rb)->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) { - m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0"; - m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0"; - m_values["dlg_settings_layout_mode"] = (id == 2) ? "1" : "0"; - }); - }; - - add_radio(&m_rb_old_settings_layout_mode, ++id, app_config->get("old_settings_layout_mode") == "1"); - add_radio(&m_rb_new_settings_layout_mode, ++id, app_config->get("new_settings_layout_mode") == "1"); - add_radio(&m_rb_dlg_settings_layout_mode, ++id, app_config->get("dlg_settings_layout_mode") == "1"); - -#ifdef _MSW_DARK_MODE - if (app_config->get("tabs_as_menu") == "1") { - m_rb_new_settings_layout_mode->Hide(); - if (m_rb_new_settings_layout_mode->GetValue()) { - m_rb_new_settings_layout_mode->SetValue(false); - m_rb_old_settings_layout_mode->SetValue(true); - } - } -#endif - - std::string opt_key = "settings_layout_mode"; - m_blinkers[opt_key] = new BlinkingBitmap(parent); - - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2); - sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); - m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); - - append_preferences_option_to_searcer(m_optgroup_gui, opt_key, title); -} - -void PreferencesDialog::create_settings_text_color_widget() -{ - wxWindow* parent = m_optgroup_gui->parent(); - - wxString title = L("Text colors"); - wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _(title)); - wxGetApp().UpdateDarkUI(stb); - if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); - - std::string opt_key = "text_colors"; - m_blinkers[opt_key] = new BlinkingBitmap(parent); - - wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL); - ButtonsDescription::FillSizerWithTextColorDescriptions(stb_sizer, parent, &m_sys_colour, &m_mod_colour); - - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2); - sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); - - m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); - - append_preferences_option_to_searcer(m_optgroup_gui, opt_key, title); -} - -void PreferencesDialog::init_highlighter(const t_config_option_key& opt_key) -{ - if (m_blinkers.find(opt_key) != m_blinkers.end()) - if (BlinkingBitmap* blinker = m_blinkers.at(opt_key); blinker) { - m_highlighter.init(blinker); - return; - } - - for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui -#ifdef _WIN32 - , m_optgroup_dark_mode -#endif // _WIN32 -#if ENABLE_ENVIRONMENT_MAP - , m_optgroup_render -#endif // ENABLE_ENVIRONMENT_MAP - }) { - std::pair ctrl = opt_group->get_custom_ctrl_with_blinking_ptr(opt_key, -1); - if (ctrl.first && ctrl.second) { - m_highlighter.init(ctrl); - break; - } - } -} - -} // GUI -} // Slic3r +#include "Preferences.hpp" +#include "OptionsGroup.hpp" +#include "GUI_App.hpp" +#include "Plater.hpp" +#include "MsgDialog.hpp" +#include "I18N.hpp" +#include "libslic3r/AppConfig.hpp" +#include +#include "Notebook.hpp" +#include "ButtonsDescription.hpp" +#include "OG_CustomCtrl.hpp" +#include "GLCanvas3D.hpp" + +namespace Slic3r { + + static t_config_enum_names enum_names_from_keys_map(const t_config_enum_values& enum_keys_map) + { + t_config_enum_names names; + int cnt = 0; + for (const auto& kvp : enum_keys_map) + cnt = std::max(cnt, kvp.second); + cnt += 1; + names.assign(cnt, ""); + for (const auto& kvp : enum_keys_map) + names[kvp.second] = kvp.first; + return names; + } + +#define CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NAME) \ + static t_config_enum_names s_keys_names_##NAME = enum_names_from_keys_map(s_keys_map_##NAME); \ + template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values() { return s_keys_map_##NAME; } \ + template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names() { return s_keys_names_##NAME; } + + + + static const t_config_enum_values s_keys_map_NotifyReleaseMode = { + {"all", NotifyReleaseAll}, + {"release", NotifyReleaseOnly}, + {"none", NotifyReleaseNone}, + }; + + CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NotifyReleaseMode) + +namespace GUI { + +PreferencesDialog::PreferencesDialog(wxWindow* parent) : + DPIDialog(parent, wxID_ANY, _L("Preferences"), wxDefaultPosition, + wxDefaultSize, wxDEFAULT_DIALOG_STYLE) +{ +#ifdef __WXOSX__ + isOSX = true; +#endif + build(); + + m_highlighter.set_timer_owner(this, 0); +} + +static void update_color(wxColourPickerCtrl* color_pckr, const wxColour& color) +{ + if (color_pckr->GetColour() != color) { + color_pckr->SetColour(color); + wxPostEvent(color_pckr, wxCommandEvent(wxEVT_COLOURPICKER_CHANGED)); + } +} + +void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::string()*/, const std::string& tab_name/*= std::string()*/) +{ + int selected_tab = 0; + for ( ; selected_tab < int(tabs->GetPageCount()); selected_tab++) + if (tabs->GetPageText(selected_tab) == _(tab_name)) + break; + if (selected_tab < int(tabs->GetPageCount())) + tabs->SetSelection(selected_tab); + + if (!highlight_opt_key.empty()) + init_highlighter(highlight_opt_key); + + // cache input values for custom toolbar size + m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str()); + m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1"; + + // update colors for color pickers + update_color(m_sys_colour, wxGetApp().get_label_clr_sys()); + update_color(m_mod_colour, wxGetApp().get_label_clr_modified()); + + this->ShowModal(); +} + +static std::shared_ptrcreate_options_tab(const wxString& title, wxBookCtrlBase* tabs) +{ + wxPanel* tab = new wxPanel(tabs, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL); + tabs->AddPage(tab, _(title)); + tab->SetFont(wxGetApp().normal_font()); + + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->SetSizeHints(tab); + tab->SetSizer(sizer); + + std::shared_ptr optgroup = std::make_shared(tab); + optgroup->label_width = 40; + optgroup->set_config_category_and_type(title, int(Preset::TYPE_PREFERENCES)); + return optgroup; +} + +static void activate_options_tab(std::shared_ptr optgroup) +{ + optgroup->activate([](){}, wxALIGN_RIGHT); + optgroup->update_visibility(comSimple); + wxBoxSizer* sizer = static_cast(static_cast(optgroup->parent())->GetSizer()); + sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); + + // apply sercher + wxGetApp().sidebar().get_searcher().append_preferences_options(optgroup->get_lines()); +} + +static void append_bool_option( std::shared_ptr optgroup, + const std::string& opt_key, + const std::string& label, + const std::string& tooltip, + bool def_val, + ConfigOptionMode mode = comSimple) +{ + ConfigOptionDef def = {opt_key, coBool}; + def.label = label; + def.tooltip = tooltip; + def.mode = mode; + def.set_default_value(new ConfigOptionBool{ def_val }); + Option option(def, opt_key); + optgroup->append_single_option_line(option); + + // fill data to the Search Dialog + wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); +} + +static void append_enum_option( std::shared_ptr optgroup, + const std::string& opt_key, + const std::string& label, + const std::string& tooltip, + const ConfigOption* def_val, + const t_config_enum_values *enum_keys_map, + std::initializer_list enum_values, + std::initializer_list enum_labels, + ConfigOptionMode mode = comSimple) +{ + ConfigOptionDef def = {opt_key, coEnum }; + def.label = label; + def.tooltip = tooltip; + def.mode = mode; + def.enum_keys_map = enum_keys_map; + def.enum_values = std::vector(enum_values); + def.enum_labels = std::vector(enum_labels); + + def.set_default_value(def_val); + Option option(def, opt_key); + optgroup->append_single_option_line(option); + + // fill data to the Search Dialog + wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); +} + +static void append_preferences_option_to_searcer(std::shared_ptr optgroup, + const std::string& opt_key, + const wxString& label) +{ + // fill data to the Search Dialog + wxGetApp().sidebar().get_searcher().add_key(opt_key, Preset::TYPE_PREFERENCES, optgroup->config_category(), L("Preferences")); + // apply sercher + wxGetApp().sidebar().get_searcher().append_preferences_option(Line(opt_key, label, "")); +} + +void PreferencesDialog::build() +{ +#ifdef _WIN32 + wxGetApp().UpdateDarkUI(this); +#else + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif + const wxFont& font = wxGetApp().normal_font(); + SetFont(font); + + auto app_config = get_app_config(); + +#ifdef _MSW_DARK_MODE + tabs = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNB_DEFAULT); +#else + tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL |wxNB_NOPAGETHEME | wxNB_DEFAULT ); + tabs->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif + + // Add "General" tab + m_optgroup_general = create_options_tab(L("General"), tabs); + m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } + if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset" || opt_key == "default_action_on_new_project") + m_values[opt_key] = boost::any_cast(value) ? "none" : "discard"; + else if (opt_key == "default_action_on_dirty_project") + m_values[opt_key] = boost::any_cast(value) ? "" : "0"; + else + m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; + }; + + bool is_editor = wxGetApp().is_editor(); + + if (is_editor) { + append_bool_option(m_optgroup_general, "remember_output_path", + L("Remember output directory"), + L("If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files."), + app_config->has("remember_output_path") ? app_config->get("remember_output_path") == "1" : true); + + append_bool_option(m_optgroup_general, "autocenter", + L("Auto-center parts"), + L("If this is enabled, Slic3r will auto-center objects around the print bed center."), + app_config->get("autocenter") == "1"); + + append_bool_option(m_optgroup_general, "background_processing", + L("Background processing"), + L("If this is enabled, Slic3r will pre-process objects as soon " + "as they\'re loaded in order to save time when exporting G-code."), + app_config->get("background_processing") == "1"); + + m_optgroup_general->append_separator(); + + // Please keep in sync with ConfigWizard + append_bool_option(m_optgroup_general, "export_sources_full_pathnames", + L("Export sources full pathnames to 3mf and amf"), + L("If enabled, allows the Reload from disk command to automatically find and load the files when invoked."), + app_config->get("export_sources_full_pathnames") == "1"); + +#ifdef _WIN32 +#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + // file association is not possible anymore starting with Win 8 + if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { +#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + // Please keep in sync with ConfigWizard + append_bool_option(m_optgroup_general, "associate_3mf", + L("Associate .3mf files to PrusaSlicer"), + L("If enabled, sets PrusaSlicer as default application to open .3mf files."), + app_config->get("associate_3mf") == "1"); + + append_bool_option(m_optgroup_general, "associate_stl", + L("Associate .stl files to PrusaSlicer"), + L("If enabled, sets PrusaSlicer as default application to open .stl files."), + app_config->get("associate_stl") == "1"); +#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + } +#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER +#endif // _WIN32 + + m_optgroup_general->append_separator(); + + // Please keep in sync with ConfigWizard + append_bool_option(m_optgroup_general, "preset_update", + L("Update built-in Presets automatically"), + L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded " + "into a separate temporary location. When a new preset version becomes available it is offered at application startup."), + app_config->get("preset_update") == "1"); + + append_bool_option(m_optgroup_general, "no_defaults", + L("Suppress \" - default - \" presets"), + L("Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available."), + app_config->get("no_defaults") == "1"); + + append_bool_option(m_optgroup_general, "show_incompatible_presets", + L("Show incompatible print and filament presets"), + L("When checked, the print and filament presets are shown in the preset editor " + "even if they are marked as incompatible with the active printer"), + app_config->get("show_incompatible_presets") == "1"); + + m_optgroup_general->append_separator(); + + append_bool_option(m_optgroup_general, "show_drop_project_dialog", + L("Show drop project dialog"), + L("When checked, whenever dragging and dropping a project file on the application, shows a dialog asking to select the action to take on the file to load."), + app_config->get("show_drop_project_dialog") == "1"); + + append_bool_option(m_optgroup_general, "single_instance", +#if __APPLE__ + L("Allow just a single PrusaSlicer instance"), + L("On OSX there is always only one instance of app running by default. However it is allowed to run multiple instances " + "of same app from the command line. In such case this settings will allow only one instance."), +#else + L("Allow just a single PrusaSlicer instance"), + L("If this is enabled, when starting PrusaSlicer and another instance of the same PrusaSlicer is already running, that instance will be reactivated instead."), +#endif + app_config->has("single_instance") ? app_config->get("single_instance") == "1" : false ); + + m_optgroup_general->append_separator(); + + append_bool_option(m_optgroup_general, "default_action_on_dirty_project", + L("Ask for unsaved changes in project"), + L("Always ask for unsaved changes in project, when: \n" + "- Closing PrusaSlicer,\n" + "- Loading or creating a new project"), + app_config->get("default_action_on_dirty_project").empty()); + + m_optgroup_general->append_separator(); + + append_bool_option(m_optgroup_general, "default_action_on_close_application", + L("Ask to save unsaved changes in presets when closing the application or when loading a new project"), + L("Always ask for unsaved changes in presets, when: \n" + "- Closing PrusaSlicer while some presets are modified,\n" + "- Loading a new project while some presets are modified"), + app_config->get("default_action_on_close_application") == "none"); + + append_bool_option(m_optgroup_general, "default_action_on_select_preset", + L("Ask for unsaved changes in presets when selecting new preset"), + L("Always ask for unsaved changes in presets when selecting new preset or resetting a preset"), + app_config->get("default_action_on_select_preset") == "none"); + + append_bool_option(m_optgroup_general, "default_action_on_new_project", + L("Ask for unsaved changes in presets when creating new project"), + L("Always ask for unsaved changes in presets when creating new project"), + app_config->get("default_action_on_new_project") == "none"); + } +#ifdef _WIN32 + else { + append_bool_option(m_optgroup_general, "associate_gcode", + L("Associate .gcode files to PrusaSlicer G-code Viewer"), + L("If enabled, sets PrusaSlicer G-code Viewer as default application to open .gcode files."), + app_config->get("associate_gcode") == "1"); + } +#endif // _WIN32 + +#if __APPLE__ + append_bool_option(m_optgroup_general, "use_retina_opengl", + L("Use Retina resolution for the 3D scene"), + L("If enabled, the 3D scene will be rendered in Retina resolution. " + "If you are experiencing 3D performance problems, disabling this option may help."), + app_config->get("use_retina_opengl") == "1"); +#endif + + m_optgroup_general->append_separator(); + + // Show/Hide splash screen + append_bool_option(m_optgroup_general, "show_splash_screen", + L("Show splash screen"), + L("Show splash screen"), + app_config->get("show_splash_screen") == "1"); + + append_bool_option(m_optgroup_general, "restore_win_position", + L("Restore window position on start"), + L("If enabled, PrusaSlicer will be open at the position it was closed"), + app_config->get("restore_win_position") == "1"); + + // Clear Undo / Redo stack on new project + append_bool_option(m_optgroup_general, "clear_undo_redo_stack_on_new_project", + L("Clear Undo / Redo stack on new project"), + L("Clear Undo / Redo stack on new project or when an existing project is loaded."), + app_config->get("clear_undo_redo_stack_on_new_project") == "1"); + +#if defined(_WIN32) || defined(__APPLE__) + append_bool_option(m_optgroup_general, "use_legacy_3DConnexion", + L("Enable support for legacy 3DConnexion devices"), + L("If enabled, the legacy 3DConnexion devices settings dialog is available by pressing CTRL+M"), + app_config->get("use_legacy_3DConnexion") == "1"); +#endif // _WIN32 || __APPLE__ + + activate_options_tab(m_optgroup_general); + + // Add "Camera" tab + m_optgroup_camera = create_options_tab(L("Camera"), tabs); + m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key);it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } + m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; + }; + + append_bool_option(m_optgroup_camera, "use_perspective_camera", + L("Use perspective camera"), + L("If enabled, use perspective camera. If not enabled, use orthographic camera."), + app_config->get("use_perspective_camera") == "1"); + + append_bool_option(m_optgroup_camera, "use_free_camera", + L("Use free camera"), + L("If enabled, use free camera. If not enabled, use constrained camera."), + app_config->get("use_free_camera") == "1"); + + append_bool_option(m_optgroup_camera, "reverse_mouse_wheel_zoom", + L("Reverse direction of zoom with mouse wheel"), + L("If enabled, reverses the direction of zoom with mouse wheel"), + app_config->get("reverse_mouse_wheel_zoom") == "1"); + + activate_options_tab(m_optgroup_camera); + + // Add "GUI" tab + m_optgroup_gui = create_options_tab(L("GUI"), tabs); + m_optgroup_gui->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (opt_key == "notify_release") { + int val_int = boost::any_cast(value); + for (const auto& item : s_keys_map_NotifyReleaseMode) { + if (item.second == val_int) { + m_values[opt_key] = item.first; + return; + } + } + } + if (opt_key == "use_custom_toolbar_size") { + m_icon_size_sizer->ShowItems(boost::any_cast(value)); + refresh_og(m_optgroup_gui); + get_app_config()->set("use_custom_toolbar_size", boost::any_cast(value) ? "1" : "0"); + get_app_config()->save(); + wxGetApp().plater()->get_current_canvas3D()->render(); + return; + } + if (opt_key == "tabs_as_menu") { + bool disable_new_layout = boost::any_cast(value); + m_rb_new_settings_layout_mode->Show(!disable_new_layout); + if (disable_new_layout && m_rb_new_settings_layout_mode->GetValue()) { + m_rb_new_settings_layout_mode->SetValue(false); + m_rb_old_settings_layout_mode->SetValue(true); + } + refresh_og(m_optgroup_gui); + } + + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } + + if (opt_key == "suppress_hyperlinks") + m_values[opt_key] = boost::any_cast(value) ? "1" : ""; + else + m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; + }; + + append_bool_option(m_optgroup_gui, "seq_top_layer_only", + L("Sequential slider applied only to top layer"), + L("If enabled, changes made using the sequential slider, in preview, apply only to gcode top layer." + "If disabled, changes made using the sequential slider, in preview, apply to the whole gcode."), + app_config->get("seq_top_layer_only") == "1"); + + if (is_editor) { + append_bool_option(m_optgroup_gui, "show_collapse_button", + L("Show sidebar collapse/expand button"), + L("If enabled, the button for the collapse sidebar will be appeared in top right corner of the 3D Scene"), + app_config->get("show_collapse_button") == "1"); + + append_bool_option(m_optgroup_gui, "suppress_hyperlinks", + L("Suppress to open hyperlink in browser"), + L("If enabled, PrusaSlicer will not open a hyperlinks in your browser."), + //L("If enabled, the descriptions of configuration parameters in settings tabs wouldn't work as hyperlinks. " + // "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."), + app_config->get("suppress_hyperlinks") == "1"); + + append_bool_option(m_optgroup_gui, "color_mapinulation_panel", + L("Use colors for axes values in Manipulation panel"), + L("If enabled, the axes names and axes values will be colorized according to the axes colors. " + "If disabled, old UI will be used."), + app_config->get("color_mapinulation_panel") == "1"); + + append_bool_option(m_optgroup_gui, "order_volumes", + L("Order object volumes by types"), + L("If enabled, volumes will be always ordered inside the object. Correct order is Model Part, Negative Volume, Modifier, Support Blocker and Support Enforcer. " + "If disabled, you can reorder Model Parts, Negative Volumes and Modifiers. But one of the model parts have to be on the first place."), + app_config->get("order_volumes") == "1"); + +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + append_bool_option(m_optgroup_gui, "non_manifold_edges", + L("Show non-manifold edges"), + L("If enabled, shows non-manifold edges."), + app_config->get("non_manifold_edges") == "1"); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + +#ifdef _MSW_DARK_MODE + append_bool_option(m_optgroup_gui, "tabs_as_menu", + L("Set settings tabs as menu items (experimental)"), + L("If enabled, Settings Tabs will be placed as menu items. If disabled, old UI will be used."), + app_config->get("tabs_as_menu") == "1"); +#endif + + m_optgroup_gui->append_separator(); + + append_bool_option(m_optgroup_gui, "show_hints", + L("Show \"Tip of the day\" notification after start"), + L("If enabled, useful hints are displayed at startup."), + app_config->get("show_hints") == "1"); + + append_enum_option(m_optgroup_gui, "notify_release", + L("Notify about new releases"), + L("You will be notified about new release after startup acordingly: All = Regular release and alpha / beta releases. Release only = regular release."), + new ConfigOptionEnum(static_cast(s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")))), + &ConfigOptionEnum::get_enum_values(), + {"all", "release", "none"}, + {L("All"), L("Release only"), L("None")}); + + m_optgroup_gui->append_separator(); + + append_bool_option(m_optgroup_gui, "use_custom_toolbar_size", + L("Use custom size for toolbar icons"), + L("If enabled, you can change size of toolbar icons manually."), + app_config->get("use_custom_toolbar_size") == "1"); + } + + activate_options_tab(m_optgroup_gui); + + if (is_editor) { + // set Field for notify_release to its value to activate the object + boost::any val = s_keys_map_NotifyReleaseMode.at(app_config->get("notify_release")); + m_optgroup_gui->get_field("notify_release")->set_value(val, false); + + create_icon_size_slider(); + m_icon_size_sizer->ShowItems(app_config->get("use_custom_toolbar_size") == "1"); + + create_settings_mode_widget(); + create_settings_text_color_widget(); + +#if ENABLE_ENVIRONMENT_MAP + // Add "Render" tab + m_optgroup_render = create_options_tab(L("Render"), tabs); + m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } + m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; + }; + + append_bool_option(m_optgroup_render, "use_environment_map", + L("Use environment map"), + L("If enabled, renders object using the environment map."), + app_config->get("use_environment_map") == "1"); + + activate_options_tab(m_optgroup_render); +#endif // ENABLE_ENVIRONMENT_MAP + +#ifdef _WIN32 + // Add "Dark Mode" tab + m_optgroup_dark_mode = create_options_tab(_L("Dark mode (experimental)"), tabs); + m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + if (auto it = m_values.find(opt_key); it != m_values.end()) { + m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected + return; + } + m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; + }; + + append_bool_option(m_optgroup_dark_mode, "dark_color_mode", + L("Enable dark mode"), + L("If enabled, UI will use Dark mode colors. If disabled, old UI will be used."), + app_config->get("dark_color_mode") == "1"); + + if (wxPlatformInfo::Get().GetOSMajorVersion() >= 10) // Use system menu just for Window newer then Windows 10 + // Use menu with ownerdrawn items by default on systems older then Windows 10 + { + append_bool_option(m_optgroup_dark_mode, "sys_menu_enabled", + L("Use system menu for application"), + L("If enabled, application will use the standart Windows system menu,\n" + "but on some combination od display scales it can look ugly. If disabled, old UI will be used."), + app_config->get("sys_menu_enabled") == "1"); + } + + activate_options_tab(m_optgroup_dark_mode); +#endif //_WIN32 + } + + // update alignment of the controls for all tabs + update_ctrls_alignment(); + + auto sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(tabs, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); + + auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); + this->Bind(wxEVT_BUTTON, &PreferencesDialog::accept, this, wxID_OK); + this->Bind(wxEVT_BUTTON, &PreferencesDialog::revert, this, wxID_CANCEL); + + for (int id : {wxID_OK, wxID_CANCEL}) + wxGetApp().UpdateDarkUI(static_cast(FindWindowById(id, this))); + + sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM | wxTOP, 10); + + SetSizer(sizer); + sizer->SetSizeHints(this); + this->CenterOnParent(); +} + +std::vector PreferencesDialog::optgroups() +{ + std::vector out; + out.reserve(4); + for (ConfigOptionsGroup* opt : { m_optgroup_general.get(), m_optgroup_camera.get(), m_optgroup_gui.get() +#ifdef _WIN32 + , m_optgroup_dark_mode.get() +#endif // _WIN32 +#if ENABLE_ENVIRONMENT_MAP + , m_optgroup_render.get() +#endif // ENABLE_ENVIRONMENT_MAP + }) + if (opt) + out.emplace_back(opt); + return out; +} + +void PreferencesDialog::update_ctrls_alignment() +{ + int max_ctrl_width{ 0 }; + for (ConfigOptionsGroup* og : this->optgroups()) + if (int max = og->custom_ctrl->get_max_win_width(); + max_ctrl_width < max) + max_ctrl_width = max; + if (max_ctrl_width) + for (ConfigOptionsGroup* og : this->optgroups()) + og->custom_ctrl->set_max_win_width(max_ctrl_width); +} + +void PreferencesDialog::accept(wxEvent&) +{ + std::vector options_to_recreate_GUI = { "no_defaults", "tabs_as_menu", "sys_menu_enabled" }; + + for (const std::string& option : options_to_recreate_GUI) { + if (m_values.find(option) != m_values.end()) { + wxString title = wxGetApp().is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME); + title += " - " + _L("Changes for the critical options"); + MessageDialog dialog(nullptr, + _L("Changing some options will trigger application restart.\n" + "You will lose the content of the plater.") + "\n\n" + + _L("Do you want to proceed?"), + title, + wxICON_QUESTION | wxYES | wxNO); + if (dialog.ShowModal() == wxID_YES) { + m_recreate_GUI = true; + } + else { + for (const std::string& option : options_to_recreate_GUI) + m_values.erase(option); + } + break; + } + } + + auto app_config = get_app_config(); + + m_seq_top_layer_only_changed = false; + if (auto it = m_values.find("seq_top_layer_only"); it != m_values.end()) + m_seq_top_layer_only_changed = app_config->get("seq_top_layer_only") != it->second; + + m_settings_layout_changed = false; + for (const std::string& key : { "old_settings_layout_mode", + "new_settings_layout_mode", + "dlg_settings_layout_mode" }) + { + auto it = m_values.find(key); + if (it != m_values.end() && app_config->get(key) != it->second) { + m_settings_layout_changed = true; + break; + } + } + +#if 0 //#ifdef _WIN32 // #ysDarkMSW - Allow it when we deside to support the sustem colors for application + if (m_values.find("always_dark_color_mode") != m_values.end()) + wxGetApp().force_sys_colors_update(); +#endif + + for (std::map::iterator it = m_values.begin(); it != m_values.end(); ++it) + app_config->set(it->first, it->second); + + app_config->save(); + if (wxGetApp().is_editor()) { + wxGetApp().set_label_clr_sys(m_sys_colour->GetColour()); + wxGetApp().set_label_clr_modified(m_mod_colour->GetColour()); + } + + EndModal(wxID_OK); + +#ifdef _WIN32 + if (m_values.find("dark_color_mode") != m_values.end()) + wxGetApp().force_colors_update(); +#ifdef _MSW_DARK_MODE + if (m_values.find("sys_menu_enabled") != m_values.end()) + wxGetApp().force_menu_update(); +#endif //_MSW_DARK_MODE +#endif // _WIN32 + + wxGetApp().update_ui_from_settings(); + clear_cache(); +} + +void PreferencesDialog::revert(wxEvent&) +{ + auto app_config = get_app_config(); + + bool save_app_config = false; + if (m_custom_toolbar_size != atoi(app_config->get("custom_toolbar_size").c_str())) { + app_config->set("custom_toolbar_size", (boost::format("%d") % m_custom_toolbar_size).str()); + m_icon_size_slider->SetValue(m_custom_toolbar_size); + save_app_config |= true; + } + if (m_use_custom_toolbar_size != (get_app_config()->get("use_custom_toolbar_size") == "1")) { + app_config->set("use_custom_toolbar_size", m_use_custom_toolbar_size ? "1" : "0"); + save_app_config |= true; + + m_optgroup_gui->set_value("use_custom_toolbar_size", m_use_custom_toolbar_size); + m_icon_size_sizer->ShowItems(m_use_custom_toolbar_size); + refresh_og(m_optgroup_gui); + } + if (save_app_config) + app_config->save(); + + + for (auto value : m_values) { + bool reverted = false; + const std::string& key = value.first; + + if (key == "default_action_on_dirty_project") { + m_optgroup_general->set_value(key, app_config->get(key).empty()); + continue; + } + if (key == "default_action_on_close_application" || key == "default_action_on_select_preset" || key == "default_action_on_new_project") { + m_optgroup_general->set_value(key, app_config->get(key) == "none"); + continue; + } + if (key == "notify_release") { + m_optgroup_gui->set_value(key, s_keys_map_NotifyReleaseMode.at(app_config->get(key))); + continue; + } + if (key == "old_settings_layout_mode") { + m_rb_old_settings_layout_mode->SetValue(app_config->get(key) == "1"); + continue; + } + if (key == "new_settings_layout_mode") { + m_rb_new_settings_layout_mode->SetValue(app_config->get(key) == "1"); + continue; + } + if (key == "dlg_settings_layout_mode") { + m_rb_dlg_settings_layout_mode->SetValue(app_config->get(key) == "1"); + continue; + } + + for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui +#ifdef _WIN32 + , m_optgroup_dark_mode +#endif // _WIN32 +#if ENABLE_ENVIRONMENT_MAP + , m_optgroup_render +#endif // ENABLE_ENVIRONMENT_MAP + }) { + if (opt_group->set_value(key, app_config->get(key) == "1")) + break; + } + if (key == "tabs_as_menu") { + m_rb_new_settings_layout_mode->Show(app_config->get(key) != "1"); + refresh_og(m_optgroup_gui); + continue; + } + } + + clear_cache(); + EndModal(wxID_CANCEL); +} + +void PreferencesDialog::msw_rescale() +{ + for (ConfigOptionsGroup* og : this->optgroups()) + og->msw_rescale(); +#ifdef _WIN32 + m_optgroup_dark_mode->msw_rescale(); +#endif //_WIN32 +#if ENABLE_ENVIRONMENT_MAP + m_optgroup_render->msw_rescale(); +#endif // ENABLE_ENVIRONMENT_MAP + + msw_buttons_rescale(this, em_unit(), { wxID_OK, wxID_CANCEL }); + + layout(); +} + +void PreferencesDialog::on_sys_color_changed() +{ +#ifdef _WIN32 + wxGetApp().UpdateDlgDarkUI(this); +#endif +} + +void PreferencesDialog::layout() +{ + const int em = em_unit(); + + SetMinSize(wxSize(47 * em, 28 * em)); + Fit(); + + Refresh(); +} + +void PreferencesDialog::clear_cache() +{ + m_values.clear(); + m_custom_toolbar_size = -1; +} + +void PreferencesDialog::refresh_og(std::shared_ptr og) +{ + og->parent()->Layout(); + tabs->Layout(); + this->layout(); +} + +void PreferencesDialog::create_icon_size_slider() +{ + const auto app_config = get_app_config(); + + const int em = em_unit(); + + m_icon_size_sizer = new wxBoxSizer(wxHORIZONTAL); + + wxWindow* parent = m_optgroup_gui->parent(); + wxGetApp().UpdateDarkUI(parent); + + if (isOSX) + // For correct rendering of the slider and value label under OSX + // we should use system default background + parent->SetBackgroundStyle(wxBG_STYLE_ERASE); + + auto label = new wxStaticText(parent, wxID_ANY, _L("Icon size in a respect to the default size") + " (%) :"); + + m_icon_size_sizer->Add(label, 0, wxALIGN_CENTER_VERTICAL| wxRIGHT | (isOSX ? 0 : wxLEFT), em); + + const int def_val = atoi(app_config->get("custom_toolbar_size").c_str()); + + long style = wxSL_HORIZONTAL; + if (!isOSX) + style |= wxSL_LABELS | wxSL_AUTOTICKS; + + m_icon_size_slider = new wxSlider(parent, wxID_ANY, def_val, 30, 100, + wxDefaultPosition, wxDefaultSize, style); + + m_icon_size_slider->SetTickFreq(10); + m_icon_size_slider->SetPageSize(10); + m_icon_size_slider->SetToolTip(_L("Select toolbar icon size in respect to the default one.")); + + m_icon_size_sizer->Add(m_icon_size_slider, 1, wxEXPAND); + + wxStaticText* val_label{ nullptr }; + if (isOSX) { + val_label = new wxStaticText(parent, wxID_ANY, wxString::Format("%d", def_val)); + m_icon_size_sizer->Add(val_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, em); + } + + m_icon_size_slider->Bind(wxEVT_SLIDER, ([this, val_label, app_config](wxCommandEvent e) { + auto val = m_icon_size_slider->GetValue(); + + app_config->set("custom_toolbar_size", (boost::format("%d") % val).str()); + app_config->save(); + wxGetApp().plater()->get_current_canvas3D()->render(); + + if (val_label) + val_label->SetLabelText(wxString::Format("%d", val)); + }), m_icon_size_slider->GetId()); + + for (wxWindow* win : std::vector{ m_icon_size_slider, label, val_label }) { + if (!win) continue; + win->SetFont(wxGetApp().normal_font()); + + if (isOSX) continue; // under OSX we use wxBG_STYLE_ERASE + win->SetBackgroundStyle(wxBG_STYLE_PAINT); + } + + m_optgroup_gui->sizer->Add(m_icon_size_sizer, 0, wxEXPAND | wxALL, em); +} + +void PreferencesDialog::create_settings_mode_widget() +{ + wxWindow* parent = m_optgroup_gui->parent(); + wxGetApp().UpdateDarkUI(parent); + + wxString title = L("Layout Options"); + wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _(title)); + wxGetApp().UpdateDarkUI(stb); + if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); + stb->SetFont(wxGetApp().normal_font()); + + wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL); + + auto app_config = get_app_config(); + std::vector choices = { _L("Old regular layout with the tab bar"), + _L("New layout, access via settings button in the top menu"), + _L("Settings in non-modal window") }; + int id = -1; + auto add_radio = [this, parent, stb_sizer, choices](wxRadioButton** rb, int id, bool select) { + *rb = new wxRadioButton(parent, wxID_ANY, choices[id], wxDefaultPosition, wxDefaultSize, id == 0 ? wxRB_GROUP : 0); + stb_sizer->Add(*rb); + (*rb)->SetValue(select); + (*rb)->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) { + m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0"; + m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0"; + m_values["dlg_settings_layout_mode"] = (id == 2) ? "1" : "0"; + }); + }; + + add_radio(&m_rb_old_settings_layout_mode, ++id, app_config->get("old_settings_layout_mode") == "1"); + add_radio(&m_rb_new_settings_layout_mode, ++id, app_config->get("new_settings_layout_mode") == "1"); + add_radio(&m_rb_dlg_settings_layout_mode, ++id, app_config->get("dlg_settings_layout_mode") == "1"); + +#ifdef _MSW_DARK_MODE + if (app_config->get("tabs_as_menu") == "1") { + m_rb_new_settings_layout_mode->Hide(); + if (m_rb_new_settings_layout_mode->GetValue()) { + m_rb_new_settings_layout_mode->SetValue(false); + m_rb_old_settings_layout_mode->SetValue(true); + } + } +#endif + + std::string opt_key = "settings_layout_mode"; + m_blinkers[opt_key] = new BlinkingBitmap(parent); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2); + sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); + m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); + + append_preferences_option_to_searcer(m_optgroup_gui, opt_key, title); +} + +void PreferencesDialog::create_settings_text_color_widget() +{ + wxWindow* parent = m_optgroup_gui->parent(); + + wxString title = L("Text colors"); + wxStaticBox* stb = new wxStaticBox(parent, wxID_ANY, _(title)); + wxGetApp().UpdateDarkUI(stb); + if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); + + std::string opt_key = "text_colors"; + m_blinkers[opt_key] = new BlinkingBitmap(parent); + + wxSizer* stb_sizer = new wxStaticBoxSizer(stb, wxVERTICAL); + ButtonsDescription::FillSizerWithTextColorDescriptions(stb_sizer, parent, &m_sys_colour, &m_mod_colour); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_blinkers[opt_key], 0, wxRIGHT, 2); + sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); + + m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); + + append_preferences_option_to_searcer(m_optgroup_gui, opt_key, title); +} + +void PreferencesDialog::init_highlighter(const t_config_option_key& opt_key) +{ + if (m_blinkers.find(opt_key) != m_blinkers.end()) + if (BlinkingBitmap* blinker = m_blinkers.at(opt_key); blinker) { + m_highlighter.init(blinker); + return; + } + + for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui +#ifdef _WIN32 + , m_optgroup_dark_mode +#endif // _WIN32 +#if ENABLE_ENVIRONMENT_MAP + , m_optgroup_render +#endif // ENABLE_ENVIRONMENT_MAP + }) { + std::pair ctrl = opt_group->get_custom_ctrl_with_blinking_ptr(opt_key, -1); + if (ctrl.first && ctrl.second) { + m_highlighter.init(ctrl); + break; + } + } +} + +} // GUI +} // Slic3r diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 296c58622..6291d8734 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -1,449 +1,449 @@ -#ifdef HAS_WIN10SDK - -#ifndef NOMINMAX -# define NOMINMAX -#endif - -// Windows Runtime -#include -// for ComPtr -#include - -// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/ -#include -#include -#include - -#include "FixModelByWin10.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "libslic3r/Model.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/Format/3mf.hpp" -#include "../GUI/GUI.hpp" -#include "../GUI/I18N.hpp" -#include "../GUI/MsgDialog.hpp" - -#include -#include - -extern "C"{ - // from rapi.h - typedef HRESULT (__stdcall* FunctionRoInitialize)(int); - typedef HRESULT (__stdcall* FunctionRoUninitialize)(); - typedef HRESULT (__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance); - typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory); - // from winstring.h - typedef HRESULT (__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32 length, HSTRING *string); - typedef HRESULT (__stdcall* FunctionWindowsDelteString)(HSTRING string); -} - -namespace Slic3r { - -HMODULE s_hRuntimeObjectLibrary = nullptr; -FunctionRoInitialize s_RoInitialize = nullptr; -FunctionRoUninitialize s_RoUninitialize = nullptr; -FunctionRoActivateInstance s_RoActivateInstance = nullptr; -FunctionRoGetActivationFactory s_RoGetActivationFactory = nullptr; -FunctionWindowsCreateString s_WindowsCreateString = nullptr; -FunctionWindowsDelteString s_WindowsDeleteString = nullptr; - -bool winrt_load_runtime_object_library() -{ - if (s_hRuntimeObjectLibrary == nullptr) - s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll"); - if (s_hRuntimeObjectLibrary != nullptr) { - s_RoInitialize = (FunctionRoInitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize"); - s_RoUninitialize = (FunctionRoUninitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize"); - s_RoActivateInstance = (FunctionRoActivateInstance) GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance"); - s_RoGetActivationFactory = (FunctionRoGetActivationFactory) GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory"); - s_WindowsCreateString = (FunctionWindowsCreateString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString"); - s_WindowsDeleteString = (FunctionWindowsDelteString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString"); - } - return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString; -} - -static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst) -{ - HSTRING hClassName; - HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); - if (S_OK != hr) - return hr; - hr = (*s_RoActivateInstance)(hClassName, pinst); - (*s_WindowsDeleteString)(hClassName); - return hr; -} - -template -static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst) -{ - IInspectable *pinspectable = nullptr; - HRESULT hr = winrt_activate_instance(class_name, &pinspectable); - if (S_OK != hr) - return hr; - hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst); - pinspectable->Release(); - return hr; -} - -static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst) -{ - HSTRING hClassName; - HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); - if (S_OK != hr) - return hr; - hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst); - (*s_WindowsDeleteString)(hClassName); - return hr; -} - -template -static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst) -{ - return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast(pinst)); -} - -// To be called often to test whether to cancel the operation. -typedef std::function ThrowOnCancelFn; - -template -static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100) -{ - Microsoft::WRL::ComPtr asyncInfo; - asyncAction.As(&asyncInfo); - AsyncStatus status; - // Ugly blocking loop until the RepairAsync call finishes. -//FIXME replace with a callback. -// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage - for (;;) { - asyncInfo->get_Status(&status); - if (status != AsyncStatus::Started) - return status; - throw_on_cancel(); - ::Sleep(blocking_tick_ms); - } -} - -static HRESULT winrt_open_file_stream( - const std::wstring &path, - ABI::Windows::Storage::FileAccessMode mode, - ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream, - ThrowOnCancelFn throw_on_cancel) -{ - // Get the file factory. - Microsoft::WRL::ComPtr fileFactory; - HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf()); - if (FAILED(hr)) return hr; - - // Open the file asynchronously. - HSTRING hstr_path; - hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path); - if (FAILED(hr)) return hr; - Microsoft::WRL::ComPtr> fileOpenAsync; - hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf()); - if (FAILED(hr)) return hr; - (*s_WindowsDeleteString)(hstr_path); - - // Wait until the file gets open, get the actual file. - AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel); - Microsoft::WRL::ComPtr storageFile; - if (status == AsyncStatus::Completed) { - hr = fileOpenAsync->GetResults(storageFile.GetAddressOf()); - } else { - Microsoft::WRL::ComPtr asyncInfo; - hr = fileOpenAsync.As(&asyncInfo); - if (FAILED(hr)) return hr; - HRESULT err; - hr = asyncInfo->get_ErrorCode(&err); - return FAILED(hr) ? hr : err; - } - - Microsoft::WRL::ComPtr> fileStreamAsync; - hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf()); - if (FAILED(hr)) return hr; - - status = winrt_async_await(fileStreamAsync, throw_on_cancel); - if (status == AsyncStatus::Completed) { - hr = fileStreamAsync->GetResults(fileStream); - } else { - Microsoft::WRL::ComPtr asyncInfo; - hr = fileStreamAsync.As(&asyncInfo); - if (FAILED(hr)) return hr; - HRESULT err; - hr = asyncInfo->get_ErrorCode(&err); - if (!FAILED(hr)) - hr = err; - } - return hr; -} - -bool is_windows10() -{ - HKEY hKey; - LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey); - if (lRes == ERROR_SUCCESS) { - WCHAR szBuffer[512]; - DWORD dwBufferSize = sizeof(szBuffer); - lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize); - if (lRes == ERROR_SUCCESS) - return wcsncmp(szBuffer, L"Windows 10", 10) == 0; - RegCloseKey(hKey); - } - return false; -} - -// Progress function, to be called regularly to update the progress. -typedef std::function ProgressFn; - -void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel) -{ - if (! is_windows10()) - throw Slic3r::RuntimeError("fix_model_by_win10_sdk called on non Windows 10 system"); - - if (! winrt_load_runtime_object_library()) - throw Slic3r::RuntimeError("Failed to initialize the WinRT library."); - - HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED); - { - on_progress(L("Exporting source model"), 20); - - Microsoft::WRL::ComPtr fileStream; - hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel); - - Microsoft::WRL::ComPtr printing3d3mfpackage; - hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf()); - - Microsoft::WRL::ComPtr> modelAsync; - hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf()); - - AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel); - Microsoft::WRL::ComPtr model; - if (status == AsyncStatus::Completed) - hr = modelAsync->GetResults(model.GetAddressOf()); - else - throw Slic3r::RuntimeError(L("Failed loading the input model.")); - - Microsoft::WRL::ComPtr> meshes; - hr = model->get_Meshes(meshes.GetAddressOf()); - unsigned num_meshes = 0; - hr = meshes->get_Size(&num_meshes); - - on_progress(L("Repairing model by the Netfabb service"), 40); - - Microsoft::WRL::ComPtr repairAsync; - hr = model->RepairAsync(repairAsync.GetAddressOf()); - status = winrt_async_await(repairAsync, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(L("Mesh repair failed.")); - repairAsync->GetResults(); - - on_progress(L("Loading repaired model"), 60); - - // Verify the number of meshes returned after the repair action. - meshes.Reset(); - hr = model->get_Meshes(meshes.GetAddressOf()); - hr = meshes->get_Size(&num_meshes); - - // Save model to this class' Printing3D3MFPackage. - Microsoft::WRL::ComPtr saveToPackageAsync; - hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf()); - status = winrt_async_await(saveToPackageAsync, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); - hr = saveToPackageAsync->GetResults(); - - Microsoft::WRL::ComPtr> generatorStreamAsync; - hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf()); - status = winrt_async_await(generatorStreamAsync, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); - Microsoft::WRL::ComPtr generatorStream; - hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf()); - - // Go to the beginning of the stream. - generatorStream->Seek(0); - Microsoft::WRL::ComPtr inputStream; - hr = generatorStream.As(&inputStream); - - // Get the buffer factory. - Microsoft::WRL::ComPtr bufferFactory; - hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf()); - - // Open the destination file. - FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb"); - try { - Microsoft::WRL::ComPtr buffer; - byte *buffer_ptr; - bufferFactory->Create(65536 * 2048, buffer.GetAddressOf()); - { - Microsoft::WRL::ComPtr bufferByteAccess; - buffer.As(&bufferByteAccess); - hr = bufferByteAccess->Buffer(&buffer_ptr); - } - uint32_t length; - hr = buffer->get_Length(&length); - Microsoft::WRL::ComPtr> asyncRead; - for (;;) { - hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf()); - status = winrt_async_await(asyncRead, throw_on_cancel); - if (status != AsyncStatus::Completed) - throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); - hr = buffer->get_Length(&length); - if (length == 0) - break; - fwrite(buffer_ptr, length, 1, fout); - } - } catch (...) { - fclose(fout); - throw; - } - fclose(fout); - // Here all the COM objects will be released through the ComPtr destructors. - } - (*s_RoUninitialize)(); -} - -class RepairCanceledException : public std::exception { -public: - const char* what() const throw() { return "Model repair has been canceled"; } -}; - -// returt FALSE, if fixing was canceled -// fix_result is empty, if fixing finished successfully -// fix_result containes a message if fixing failed -bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxProgressDialog& progress_dialog, const wxString& msg_header, std::string& fix_result) -{ - std::mutex mutex; - std::condition_variable condition; - std::unique_lock lock(mutex); - struct Progress { - std::string message; - int percent = 0; - bool updated = false; - } progress; - std::atomic canceled = false; - std::atomic finished = false; - - std::vector volumes; - if (volume_idx == -1) - volumes = model_object.volumes; - else - volumes.emplace_back(model_object.volumes[volume_idx]); - - // Executing the calculation in a background thread, so that the COM context could be created with its own threading model. - // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). - bool success = false; - size_t ivolume = 0; - auto on_progress = [&mutex, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) { - std::lock_guard lk(mutex); - progress.message = msg; - progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size())); - progress.updated = true; - condition.notify_all(); - }; - auto worker_thread = boost::thread([&model_object, &volumes, &ivolume, on_progress, &success, &canceled, &finished]() { - try { - std::vector meshes_repaired; - meshes_repaired.reserve(volumes.size()); - for (; ivolume < volumes.size(); ++ ivolume) { - on_progress(L("Exporting source model"), 0); - boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); - path_src += ".3mf"; - Model model; - ModelObject *mo = model.add_object(); - mo->add_volume(*volumes[ivolume]); - - // We are about to save a 3mf, fix it by netfabb and load the fixed 3mf back. - // store_3mf currently bakes the volume transformation into the mesh itself. - // If we then loaded the repaired 3mf and pushed the mesh into the original ModelVolume - // (which remembers the matrix the whole time), the transformation would be used twice. - // We will therefore set the volume transform on the dummy ModelVolume to identity. - mo->volumes.back()->set_transformation(Geometry::Transformation()); - - mo->add_instance(); - if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false, nullptr, false)) { - boost::filesystem::remove(path_src); - throw Slic3r::RuntimeError(L("Export of a temporary 3mf file failed")); - } - model.clear_objects(); - model.clear_materials(); - boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); - path_dst += ".3mf"; - fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, - [&canceled]() { if (canceled) throw RepairCanceledException(); }); - boost::filesystem::remove(path_src); - // PresetBundle bundle; - on_progress(L("Loading repaired model"), 80); - DynamicPrintConfig config; - ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent }; - bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false); - boost::filesystem::remove(path_dst); - if (! loaded) - throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed")); - if (model.objects.size() == 0) - throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any object")); - if (model.objects.size() > 1) - throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one object")); - if (model.objects.front()->volumes.size() == 0) - throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any volume")); - if (model.objects.front()->volumes.size() > 1) - throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one volume")); - meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh())); - } - for (size_t i = 0; i < volumes.size(); ++ i) { - volumes[i]->set_mesh(std::move(meshes_repaired[i])); - volumes[i]->calculate_convex_hull(); - volumes[i]->set_new_unique_id(); - } - model_object.invalidate_bounding_box(); - -- ivolume; - on_progress(L("Model repair finished"), 100); - success = true; - finished = true; - } catch (RepairCanceledException & /* ex */) { - canceled = true; - finished = true; - on_progress(L("Model repair canceled"), 100); - } catch (std::exception &ex) { - success = false; - finished = true; - on_progress(ex.what(), 100); - } - }); - while (! finished) { - condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; }); - // decrease progress.percent value to avoid closing of the progress dialog - if (!progress_dialog.Update(progress.percent-1, msg_header + _(progress.message))) - canceled = true; - else - progress_dialog.Fit(); - progress.updated = false; - } - - if (canceled) { - // Nothing to show. - } else if (success) { - fix_result = ""; - } else { - fix_result = progress.message; - } - worker_thread.join(); - return !canceled; -} - -} // namespace Slic3r - -#endif /* HAS_WIN10SDK */ +#ifdef HAS_WIN10SDK + +#ifndef NOMINMAX +# define NOMINMAX +#endif + +// Windows Runtime +#include +// for ComPtr +#include + +// from C:/Program Files (x86)/Windows Kits/10/Include/10.0.17134.0/ +#include +#include +#include + +#include "FixModelByWin10.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "libslic3r/Model.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Format/3mf.hpp" +#include "../GUI/GUI.hpp" +#include "../GUI/I18N.hpp" +#include "../GUI/MsgDialog.hpp" + +#include +#include + +extern "C"{ + // from rapi.h + typedef HRESULT (__stdcall* FunctionRoInitialize)(int); + typedef HRESULT (__stdcall* FunctionRoUninitialize)(); + typedef HRESULT (__stdcall* FunctionRoActivateInstance)(HSTRING activatableClassId, IInspectable **instance); + typedef HRESULT (__stdcall* FunctionRoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory); + // from winstring.h + typedef HRESULT (__stdcall* FunctionWindowsCreateString)(LPCWSTR sourceString, UINT32 length, HSTRING *string); + typedef HRESULT (__stdcall* FunctionWindowsDelteString)(HSTRING string); +} + +namespace Slic3r { + +HMODULE s_hRuntimeObjectLibrary = nullptr; +FunctionRoInitialize s_RoInitialize = nullptr; +FunctionRoUninitialize s_RoUninitialize = nullptr; +FunctionRoActivateInstance s_RoActivateInstance = nullptr; +FunctionRoGetActivationFactory s_RoGetActivationFactory = nullptr; +FunctionWindowsCreateString s_WindowsCreateString = nullptr; +FunctionWindowsDelteString s_WindowsDeleteString = nullptr; + +bool winrt_load_runtime_object_library() +{ + if (s_hRuntimeObjectLibrary == nullptr) + s_hRuntimeObjectLibrary = LoadLibrary(L"ComBase.dll"); + if (s_hRuntimeObjectLibrary != nullptr) { + s_RoInitialize = (FunctionRoInitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoInitialize"); + s_RoUninitialize = (FunctionRoUninitialize) GetProcAddress(s_hRuntimeObjectLibrary, "RoUninitialize"); + s_RoActivateInstance = (FunctionRoActivateInstance) GetProcAddress(s_hRuntimeObjectLibrary, "RoActivateInstance"); + s_RoGetActivationFactory = (FunctionRoGetActivationFactory) GetProcAddress(s_hRuntimeObjectLibrary, "RoGetActivationFactory"); + s_WindowsCreateString = (FunctionWindowsCreateString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsCreateString"); + s_WindowsDeleteString = (FunctionWindowsDelteString) GetProcAddress(s_hRuntimeObjectLibrary, "WindowsDeleteString"); + } + return s_RoInitialize && s_RoUninitialize && s_RoActivateInstance && s_WindowsCreateString && s_WindowsDeleteString; +} + +static HRESULT winrt_activate_instance(const std::wstring &class_name, IInspectable **pinst) +{ + HSTRING hClassName; + HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); + if (S_OK != hr) + return hr; + hr = (*s_RoActivateInstance)(hClassName, pinst); + (*s_WindowsDeleteString)(hClassName); + return hr; +} + +template +static HRESULT winrt_activate_instance(const std::wstring &class_name, TYPE **pinst) +{ + IInspectable *pinspectable = nullptr; + HRESULT hr = winrt_activate_instance(class_name, &pinspectable); + if (S_OK != hr) + return hr; + hr = pinspectable->QueryInterface(__uuidof(TYPE), (void**)pinst); + pinspectable->Release(); + return hr; +} + +static HRESULT winrt_get_activation_factory(const std::wstring &class_name, REFIID iid, void **pinst) +{ + HSTRING hClassName; + HRESULT hr = (*s_WindowsCreateString)(class_name.c_str(), class_name.size(), &hClassName); + if (S_OK != hr) + return hr; + hr = (*s_RoGetActivationFactory)(hClassName, iid, pinst); + (*s_WindowsDeleteString)(hClassName); + return hr; +} + +template +static HRESULT winrt_get_activation_factory(const std::wstring &class_name, TYPE **pinst) +{ + return winrt_get_activation_factory(class_name, __uuidof(TYPE), reinterpret_cast(pinst)); +} + +// To be called often to test whether to cancel the operation. +typedef std::function ThrowOnCancelFn; + +template +static AsyncStatus winrt_async_await(const Microsoft::WRL::ComPtr &asyncAction, ThrowOnCancelFn throw_on_cancel, int blocking_tick_ms = 100) +{ + Microsoft::WRL::ComPtr asyncInfo; + asyncAction.As(&asyncInfo); + AsyncStatus status; + // Ugly blocking loop until the RepairAsync call finishes. +//FIXME replace with a callback. +// https://social.msdn.microsoft.com/Forums/en-US/a5038fb4-b7b7-4504-969d-c102faa389fb/trying-to-block-an-async-operation-and-wait-for-a-particular-time?forum=vclanguage + for (;;) { + asyncInfo->get_Status(&status); + if (status != AsyncStatus::Started) + return status; + throw_on_cancel(); + ::Sleep(blocking_tick_ms); + } +} + +static HRESULT winrt_open_file_stream( + const std::wstring &path, + ABI::Windows::Storage::FileAccessMode mode, + ABI::Windows::Storage::Streams::IRandomAccessStream **fileStream, + ThrowOnCancelFn throw_on_cancel) +{ + // Get the file factory. + Microsoft::WRL::ComPtr fileFactory; + HRESULT hr = winrt_get_activation_factory(L"Windows.Storage.StorageFile", fileFactory.GetAddressOf()); + if (FAILED(hr)) return hr; + + // Open the file asynchronously. + HSTRING hstr_path; + hr = (*s_WindowsCreateString)(path.c_str(), path.size(), &hstr_path); + if (FAILED(hr)) return hr; + Microsoft::WRL::ComPtr> fileOpenAsync; + hr = fileFactory->GetFileFromPathAsync(hstr_path, fileOpenAsync.GetAddressOf()); + if (FAILED(hr)) return hr; + (*s_WindowsDeleteString)(hstr_path); + + // Wait until the file gets open, get the actual file. + AsyncStatus status = winrt_async_await(fileOpenAsync, throw_on_cancel); + Microsoft::WRL::ComPtr storageFile; + if (status == AsyncStatus::Completed) { + hr = fileOpenAsync->GetResults(storageFile.GetAddressOf()); + } else { + Microsoft::WRL::ComPtr asyncInfo; + hr = fileOpenAsync.As(&asyncInfo); + if (FAILED(hr)) return hr; + HRESULT err; + hr = asyncInfo->get_ErrorCode(&err); + return FAILED(hr) ? hr : err; + } + + Microsoft::WRL::ComPtr> fileStreamAsync; + hr = storageFile->OpenAsync(mode, fileStreamAsync.GetAddressOf()); + if (FAILED(hr)) return hr; + + status = winrt_async_await(fileStreamAsync, throw_on_cancel); + if (status == AsyncStatus::Completed) { + hr = fileStreamAsync->GetResults(fileStream); + } else { + Microsoft::WRL::ComPtr asyncInfo; + hr = fileStreamAsync.As(&asyncInfo); + if (FAILED(hr)) return hr; + HRESULT err; + hr = asyncInfo->get_ErrorCode(&err); + if (!FAILED(hr)) + hr = err; + } + return hr; +} + +bool is_windows10() +{ + HKEY hKey; + LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey); + if (lRes == ERROR_SUCCESS) { + WCHAR szBuffer[512]; + DWORD dwBufferSize = sizeof(szBuffer); + lRes = RegQueryValueExW(hKey, L"ProductName", 0, nullptr, (LPBYTE)szBuffer, &dwBufferSize); + if (lRes == ERROR_SUCCESS) + return wcsncmp(szBuffer, L"Windows 10", 10) == 0; + RegCloseKey(hKey); + } + return false; +} + +// Progress function, to be called regularly to update the progress. +typedef std::function ProgressFn; + +void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path_dst, ProgressFn on_progress, ThrowOnCancelFn throw_on_cancel) +{ + if (! is_windows10()) + throw Slic3r::RuntimeError("fix_model_by_win10_sdk called on non Windows 10 system"); + + if (! winrt_load_runtime_object_library()) + throw Slic3r::RuntimeError("Failed to initialize the WinRT library."); + + HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED); + { + on_progress(L("Exporting source model"), 20); + + Microsoft::WRL::ComPtr fileStream; + hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel); + + Microsoft::WRL::ComPtr printing3d3mfpackage; + hr = winrt_activate_instance(L"Windows.Graphics.Printing3D.Printing3D3MFPackage", printing3d3mfpackage.GetAddressOf()); + + Microsoft::WRL::ComPtr> modelAsync; + hr = printing3d3mfpackage->LoadModelFromPackageAsync(fileStream.Get(), modelAsync.GetAddressOf()); + + AsyncStatus status = winrt_async_await(modelAsync, throw_on_cancel); + Microsoft::WRL::ComPtr model; + if (status == AsyncStatus::Completed) + hr = modelAsync->GetResults(model.GetAddressOf()); + else + throw Slic3r::RuntimeError(L("Failed loading the input model.")); + + Microsoft::WRL::ComPtr> meshes; + hr = model->get_Meshes(meshes.GetAddressOf()); + unsigned num_meshes = 0; + hr = meshes->get_Size(&num_meshes); + + on_progress(L("Repairing model by the Netfabb service"), 40); + + Microsoft::WRL::ComPtr repairAsync; + hr = model->RepairAsync(repairAsync.GetAddressOf()); + status = winrt_async_await(repairAsync, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw Slic3r::RuntimeError(L("Mesh repair failed.")); + repairAsync->GetResults(); + + on_progress(L("Loading repaired model"), 60); + + // Verify the number of meshes returned after the repair action. + meshes.Reset(); + hr = model->get_Meshes(meshes.GetAddressOf()); + hr = meshes->get_Size(&num_meshes); + + // Save model to this class' Printing3D3MFPackage. + Microsoft::WRL::ComPtr saveToPackageAsync; + hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf()); + status = winrt_async_await(saveToPackageAsync, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); + hr = saveToPackageAsync->GetResults(); + + Microsoft::WRL::ComPtr> generatorStreamAsync; + hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf()); + status = winrt_async_await(generatorStreamAsync, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); + Microsoft::WRL::ComPtr generatorStream; + hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf()); + + // Go to the beginning of the stream. + generatorStream->Seek(0); + Microsoft::WRL::ComPtr inputStream; + hr = generatorStream.As(&inputStream); + + // Get the buffer factory. + Microsoft::WRL::ComPtr bufferFactory; + hr = winrt_get_activation_factory(L"Windows.Storage.Streams.Buffer", bufferFactory.GetAddressOf()); + + // Open the destination file. + FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb"); + try { + Microsoft::WRL::ComPtr buffer; + byte *buffer_ptr; + bufferFactory->Create(65536 * 2048, buffer.GetAddressOf()); + { + Microsoft::WRL::ComPtr bufferByteAccess; + buffer.As(&bufferByteAccess); + hr = bufferByteAccess->Buffer(&buffer_ptr); + } + uint32_t length; + hr = buffer->get_Length(&length); + Microsoft::WRL::ComPtr> asyncRead; + for (;;) { + hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf()); + status = winrt_async_await(asyncRead, throw_on_cancel); + if (status != AsyncStatus::Completed) + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); + hr = buffer->get_Length(&length); + if (length == 0) + break; + fwrite(buffer_ptr, length, 1, fout); + } + } catch (...) { + fclose(fout); + throw; + } + fclose(fout); + // Here all the COM objects will be released through the ComPtr destructors. + } + (*s_RoUninitialize)(); +} + +class RepairCanceledException : public std::exception { +public: + const char* what() const throw() { return "Model repair has been canceled"; } +}; + +// returt FALSE, if fixing was canceled +// fix_result is empty, if fixing finished successfully +// fix_result containes a message if fixing failed +bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxProgressDialog& progress_dialog, const wxString& msg_header, std::string& fix_result) +{ + std::mutex mutex; + std::condition_variable condition; + std::unique_lock lock(mutex); + struct Progress { + std::string message; + int percent = 0; + bool updated = false; + } progress; + std::atomic canceled = false; + std::atomic finished = false; + + std::vector volumes; + if (volume_idx == -1) + volumes = model_object.volumes; + else + volumes.emplace_back(model_object.volumes[volume_idx]); + + // Executing the calculation in a background thread, so that the COM context could be created with its own threading model. + // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). + bool success = false; + size_t ivolume = 0; + auto on_progress = [&mutex, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) { + std::lock_guard lk(mutex); + progress.message = msg; + progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size())); + progress.updated = true; + condition.notify_all(); + }; + auto worker_thread = boost::thread([&model_object, &volumes, &ivolume, on_progress, &success, &canceled, &finished]() { + try { + std::vector meshes_repaired; + meshes_repaired.reserve(volumes.size()); + for (; ivolume < volumes.size(); ++ ivolume) { + on_progress(L("Exporting source model"), 0); + boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + path_src += ".3mf"; + Model model; + ModelObject *mo = model.add_object(); + mo->add_volume(*volumes[ivolume]); + + // We are about to save a 3mf, fix it by netfabb and load the fixed 3mf back. + // store_3mf currently bakes the volume transformation into the mesh itself. + // If we then loaded the repaired 3mf and pushed the mesh into the original ModelVolume + // (which remembers the matrix the whole time), the transformation would be used twice. + // We will therefore set the volume transform on the dummy ModelVolume to identity. + mo->volumes.back()->set_transformation(Geometry::Transformation()); + + mo->add_instance(); + if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false, nullptr, false)) { + boost::filesystem::remove(path_src); + throw Slic3r::RuntimeError(L("Export of a temporary 3mf file failed")); + } + model.clear_objects(); + model.clear_materials(); + boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); + path_dst += ".3mf"; + fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress, + [&canceled]() { if (canceled) throw RepairCanceledException(); }); + boost::filesystem::remove(path_src); + // PresetBundle bundle; + on_progress(L("Loading repaired model"), 80); + DynamicPrintConfig config; + ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent }; + bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false); + boost::filesystem::remove(path_dst); + if (! loaded) + throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed")); + if (model.objects.size() == 0) + throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any object")); + if (model.objects.size() > 1) + throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one object")); + if (model.objects.front()->volumes.size() == 0) + throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any volume")); + if (model.objects.front()->volumes.size() > 1) + throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one volume")); + meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh())); + } + for (size_t i = 0; i < volumes.size(); ++ i) { + volumes[i]->set_mesh(std::move(meshes_repaired[i])); + volumes[i]->calculate_convex_hull(); + volumes[i]->set_new_unique_id(); + } + model_object.invalidate_bounding_box(); + -- ivolume; + on_progress(L("Model repair finished"), 100); + success = true; + finished = true; + } catch (RepairCanceledException & /* ex */) { + canceled = true; + finished = true; + on_progress(L("Model repair canceled"), 100); + } catch (std::exception &ex) { + success = false; + finished = true; + on_progress(ex.what(), 100); + } + }); + while (! finished) { + condition.wait_for(lock, std::chrono::milliseconds(250), [&progress]{ return progress.updated; }); + // decrease progress.percent value to avoid closing of the progress dialog + if (!progress_dialog.Update(progress.percent-1, msg_header + _(progress.message))) + canceled = true; + else + progress_dialog.Fit(); + progress.updated = false; + } + + if (canceled) { + // Nothing to show. + } else if (success) { + fix_result = ""; + } else { + fix_result = progress.message; + } + worker_thread.join(); + return !canceled; +} + +} // namespace Slic3r + +#endif /* HAS_WIN10SDK */ From c0ab4dc2f547a7427620d068f352419c89bdaceb Mon Sep 17 00:00:00 2001 From: rk0n Date: Tue, 17 May 2022 19:41:54 +0200 Subject: [PATCH 022/169] Add profile for Creality Ender 3 S1 Pro --- resources/profiles/Creality.idx | 2 ++ resources/profiles/Creality.ini | 28 ++++++++++++++++++ .../Creality/ENDER3S1PRO_thumbnail.png | Bin 0 -> 43801 bytes 3 files changed, 30 insertions(+) create mode 100644 resources/profiles/Creality/ENDER3S1PRO_thumbnail.png diff --git a/resources/profiles/Creality.idx b/resources/profiles/Creality.idx index 1ff148aad..8f7dd0e8c 100644 --- a/resources/profiles/Creality.idx +++ b/resources/profiles/Creality.idx @@ -1,3 +1,5 @@ +min_slic3r_version = 2.5.0-alpha0 +0.1.5 Added Ender-3 S1 Pro min_slic3r_version = 2.4.1 0.1.4 Added Ender-3 Pro. Added M25 support for some printers. min_slic3r_version = 2.4.0-rc diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 0fa14d8a8..80ca6313e 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -59,6 +59,15 @@ bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +[printer_model:ENDER3S1PRO] +name = Creality Ender-3 S1 Pro +variants = 0.4 +technology = FFF +family = ENDER +bed_model = ender3v2_bed.stl +bed_texture = ender3v2.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY + [printer_model:ENDER3MAX] name = Creality Ender-3 Max variants = 0.4 @@ -842,6 +851,18 @@ filament_cost = 27.44 filament_density = 1.29 filament_colour = #C7F935 +[filament:Verbatim PLA @CREALITY] +inherits = *PLA* +filament_vendor = Verbatim +temperature = 205 +bed_temperature = 60 +first_layer_temperature = 210 +first_layer_bed_temperature = 60 +filament_cost = 22.99 +filament_density = 1.24 +filament_colour = #001ca8 +filament_spool_weight = 500 + # Common printer preset [printer:*common*] printer_technology = FFF @@ -992,6 +1013,13 @@ max_print_height = 270 printer_model = ENDER3S1 printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_ENDER3S1 +[printer:Creality Ender-3 S1 Pro] +inherits = *common*; *pauseprint*; *spriteextruder* +bed_shape = 5x0,215x0,215x220,5x220 +max_print_height = 270 +printer_model = ENDER3S1PRO +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_CREALITY\nPRINTER_MODEL_ENDER3S1PRO + [printer:Creality Ender-3 Max] inherits = *common*; *pauseprint* retract_length = 6 diff --git a/resources/profiles/Creality/ENDER3S1PRO_thumbnail.png b/resources/profiles/Creality/ENDER3S1PRO_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..199f2edf3194306253a6f6203cd4ecf3286084ab GIT binary patch literal 43801 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 K?K(|V#Qy-IPD2j> literal 0 HcmV?d00001 From adaed9fdece421dc65a4f2b958c6e31f4ed98e08 Mon Sep 17 00:00:00 2001 From: rk0n Date: Wed, 18 May 2022 00:46:40 +0200 Subject: [PATCH 023/169] Change Alpha to Beta Badge --- .../Creality/ENDER3S1PRO_thumbnail.png | Bin 43801 -> 43931 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/profiles/Creality/ENDER3S1PRO_thumbnail.png b/resources/profiles/Creality/ENDER3S1PRO_thumbnail.png index 199f2edf3194306253a6f6203cd4ecf3286084ab..d11396b05784e53915028604ffad6d6afe8e422f 100644 GIT binary patch literal 43931 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 NfomGvtx|s){y&GM@8kdg literal 43801 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 K?K(|V#Qy-IPD2j> From f6661f4217121342c6743eb99c5fb9274bcd905a Mon Sep 17 00:00:00 2001 From: rk0n Date: Wed, 18 May 2022 00:51:02 +0200 Subject: [PATCH 024/169] Add Verbatim PLA to all Creality printers --- resources/profiles/Creality.ini | 70 ++++++++++++++++----------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 80ca6313e..b537976d1 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -21,7 +21,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER3BLTOUCH] name = Creality Ender-3 BLTouch @@ -30,7 +30,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER3PRO] name = Creality Ender-3 Pro @@ -39,7 +39,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER3V2] name = Creality Ender-3 V2 @@ -48,7 +48,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER3S1] name = Creality Ender-3 S1 @@ -57,7 +57,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER3S1PRO] name = Creality Ender-3 S1 Pro @@ -75,7 +75,7 @@ technology = FFF family = ENDER bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER4] name = Creality Ender-4 @@ -84,7 +84,7 @@ technology = FFF family = ENDER bed_model = ender3v2_bed.stl bed_texture = ender3v2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER5] name = Creality Ender-5 @@ -93,7 +93,7 @@ technology = FFF family = ENDER bed_model = ender3_bed.stl bed_texture = ender3.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER5PLUS] name = Creality Ender-5 Plus @@ -102,7 +102,7 @@ technology = FFF family = ENDER bed_model = ender5plus_bed.stl bed_texture = ender5plus.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER6] name = Creality Ender-6 @@ -111,7 +111,7 @@ technology = FFF family = ENDER bed_model = ender6_bed.stl bed_texture = ender6.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER7] name = Creality Ender-7 @@ -120,7 +120,7 @@ technology = FFF family = ENDER bed_model = ender7_bed.stl bed_texture = ender7.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER2] name = Creality Ender-2 @@ -129,7 +129,7 @@ technology = FFF family = ENDER bed_model = ender2_bed.stl bed_texture = ender2.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:ENDER2PRO] name = Creality Ender-2 Pro @@ -138,7 +138,7 @@ technology = FFF family = ENDER bed_model = ender2pro_bed.stl bed_texture = ender2pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR5PRO] name = Creality CR-5 Pro @@ -147,7 +147,7 @@ technology = FFF family = CR bed_model = cr5pro_bed.stl bed_texture = cr5pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR5PROH] name = Creality CR-5 Pro H @@ -156,7 +156,7 @@ technology = FFF family = CR bed_model = cr5pro_bed.stl bed_texture = cr5pro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR6SE] name = Creality CR-6 SE @@ -165,7 +165,7 @@ technology = FFF family = CR bed_model = cr6se_bed.stl bed_texture = cr6se.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR6MAX] name = Creality CR-6 Max @@ -174,7 +174,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10SMART] name = Creality CR-10 SMART @@ -183,7 +183,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10MINI] name = Creality CR-10 Mini @@ -192,7 +192,7 @@ technology = FFF family = CR bed_model = cr10mini_bed.stl bed_texture = cr10mini.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10MAX] name = Creality CR-10 Max @@ -201,7 +201,7 @@ technology = FFF family = CR bed_model = cr10max_bed.stl bed_texture = cr10max.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10] name = Creality CR-10 @@ -210,7 +210,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10V2] name = Creality CR-10 V2 @@ -219,7 +219,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10V3] name = Creality CR-10 V3 @@ -228,7 +228,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10S] name = Creality CR-10 S @@ -237,7 +237,7 @@ technology = FFF family = CR bed_model = cr10_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10SPRO] name = Creality CR-10 S Pro @@ -246,7 +246,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10spro.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10SPROV2] name = Creality CR-10 S Pro V2 @@ -255,7 +255,7 @@ technology = FFF family = CR bed_model = cr10v2_bed.stl bed_texture = cr10.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10S4] name = Creality CR-10 S4 @@ -264,7 +264,7 @@ technology = FFF family = CR bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR10S5] name = Creality CR-10 S5 @@ -273,7 +273,7 @@ technology = FFF family = CR bed_model = cr10s5_bed.stl bed_texture = cr10s5.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR20] name = Creality CR-20 @@ -282,7 +282,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR20PRO] name = Creality CR-20 Pro @@ -291,7 +291,7 @@ technology = FFF family = CR bed_model = ender3_bed.stl bed_texture = cr20.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR200B] name = Creality CR-200B @@ -300,7 +300,7 @@ technology = FFF family = CR bed_model = cr200b_bed.stl bed_texture = cr200b.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:CR8] name = Creality CR-8 @@ -309,7 +309,7 @@ technology = FFF family = CR bed_model = cr8_bed.stl bed_texture = cr8.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY #[printer_model:CRX] #name = Creality CR-X @@ -318,7 +318,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY #[printer_model:CRXPRO] #name = Creality CR-X Pro @@ -327,7 +327,7 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #family = CR-X #bed_model = cr10v2_bed.stl #bed_texture = cr10spro.svg -#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +#default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY [printer_model:SERMOOND1] name = Creality Sermoon-D1 @@ -336,7 +336,7 @@ technology = FFF family = SERMOON bed_model = sermoond1_bed.stl bed_texture = sermoond1.svg -default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY; Verbatim PLA @CREALITY # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. From 1cf75100fc34177db09a8c674e73b334f425e946 Mon Sep 17 00:00:00 2001 From: rk0n Date: Wed, 18 May 2022 00:51:19 +0200 Subject: [PATCH 025/169] Remove filament spool weight from Verbatim PLA --- resources/profiles/Creality.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index b537976d1..66a793415 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -861,7 +861,6 @@ first_layer_bed_temperature = 60 filament_cost = 22.99 filament_density = 1.24 filament_colour = #001ca8 -filament_spool_weight = 500 # Common printer preset [printer:*common*] From 910db38ae85d911bbca09ab06b15d7bf588c71b8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 19 May 2022 12:34:43 +0200 Subject: [PATCH 026/169] Fixed rotation, using gizmo, for instances whose offset does not match with bounding box center --- src/slic3r/GUI/Selection.cpp | 5533 +++++++++++++++++----------------- 1 file changed, 2768 insertions(+), 2765 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 7ccc6fc25..e7c4e1763 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1,2765 +1,2768 @@ -#include "libslic3r/libslic3r.h" -#include "Selection.hpp" - -#include "3DScene.hpp" -#include "GLCanvas3D.hpp" -#include "GUI_App.hpp" -#include "GUI.hpp" -#include "GUI_ObjectManipulation.hpp" -#include "GUI_ObjectList.hpp" -#include "Gizmos/GLGizmoBase.hpp" -#include "Camera.hpp" -#include "Plater.hpp" -#include "slic3r/Utils/UndoRedo.hpp" - -#include "libslic3r/LocalesUtils.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/BuildVolume.hpp" - -#include - -#include -#include - -static const Slic3r::ColorRGBA UNIFORM_SCALE_COLOR = Slic3r::ColorRGBA::ORANGE(); -static const Slic3r::ColorRGBA SOLID_PLANE_COLOR = Slic3r::ColorRGBA::ORANGE(); -static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5f }; - -namespace Slic3r { -namespace GUI { - -Selection::VolumeCache::TransformCache::TransformCache() - : position(Vec3d::Zero()) - , rotation(Vec3d::Zero()) - , scaling_factor(Vec3d::Ones()) - , mirror(Vec3d::Ones()) - , rotation_matrix(Transform3d::Identity()) - , scale_matrix(Transform3d::Identity()) - , mirror_matrix(Transform3d::Identity()) - , full_matrix(Transform3d::Identity()) -{ -} - -Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) - : position(transform.get_offset()) - , rotation(transform.get_rotation()) - , scaling_factor(transform.get_scaling_factor()) - , mirror(transform.get_mirror()) - , full_matrix(transform.get_matrix()) -{ - rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); - scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); - mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); -} - -Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) - : m_volume(volume_transform) - , m_instance(instance_transform) -{ -} - -bool Selection::Clipboard::is_sla_compliant() const -{ - if (m_mode == Selection::Volume) - return false; - - for (const ModelObject* o : m_model->objects) { - if (o->is_multiparts()) - return false; - - for (const ModelVolume* v : o->volumes) { - if (v->is_modifier()) - return false; - } - } - - return true; -} - -Selection::Clipboard::Clipboard() -{ - m_model.reset(new Model); -} - -void Selection::Clipboard::reset() -{ - m_model->clear_objects(); -} - -bool Selection::Clipboard::is_empty() const -{ - return m_model->objects.empty(); -} - -ModelObject* Selection::Clipboard::add_object() -{ - return m_model->add_object(); -} - -ModelObject* Selection::Clipboard::get_object(unsigned int id) -{ - return (id < (unsigned int)m_model->objects.size()) ? m_model->objects[id] : nullptr; -} - -const ModelObjectPtrs& Selection::Clipboard::get_objects() const -{ - return m_model->objects; -} - -Selection::Selection() - : m_volumes(nullptr) - , m_model(nullptr) - , m_enabled(false) - , m_mode(Instance) - , m_type(Empty) - , m_valid(false) - , m_scale_factor(1.0f) -{ - this->set_bounding_boxes_dirty(); -} - - -void Selection::set_volumes(GLVolumePtrs* volumes) -{ - m_volumes = volumes; - update_valid(); -} - -// Init shall be called from the OpenGL render function, so that the OpenGL context is initialized! -bool Selection::init() -{ - m_arrow.init_from(straight_arrow(10.0f, 5.0f, 5.0f, 10.0f, 1.0f)); - m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f)); -#if ENABLE_RENDER_SELECTION_CENTER - m_vbo_sphere.init_from(its_make_sphere(0.75, PI / 12.0)); -#endif // ENABLE_RENDER_SELECTION_CENTER - - return true; -} - -void Selection::set_model(Model* model) -{ - m_model = model; - update_valid(); -} - -void Selection::add(unsigned int volume_idx, bool as_single_selection, bool check_for_already_contained) -{ - if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) - return; - - const GLVolume* volume = (*m_volumes)[volume_idx]; - // wipe tower is already selected - if (is_wipe_tower() && volume->is_wipe_tower) - return; - - bool keep_instance_mode = (m_mode == Instance) && !as_single_selection; - bool already_contained = check_for_already_contained && contains_volume(volume_idx); - - // resets the current list if needed - bool needs_reset = as_single_selection && !already_contained; - needs_reset |= volume->is_wipe_tower; - needs_reset |= is_wipe_tower() && !volume->is_wipe_tower; - needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier; - needs_reset |= is_any_modifier() && !volume->is_modifier; - - if (!already_contained || needs_reset) { - wxGetApp().plater()->take_snapshot(_L("Selection-Add"), UndoRedo::SnapshotType::Selection); - - if (needs_reset) - clear(); - - if (!keep_instance_mode) - m_mode = volume->is_modifier ? Volume : Instance; - } - else - // keep current mode - return; - - switch (m_mode) - { - case Volume: - { - if (volume->volume_idx() >= 0 && (is_empty() || volume->instance_idx() == get_instance_idx())) - do_add_volume(volume_idx); - - break; - } - case Instance: - { - Plater::SuppressSnapshots suppress(wxGetApp().plater()); - add_instance(volume->object_idx(), volume->instance_idx(), as_single_selection); - break; - } - } - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::remove(unsigned int volume_idx) -{ - if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) - return; - - if (!contains_volume(volume_idx)) - return; - - wxGetApp().plater()->take_snapshot(_L("Selection-Remove"), UndoRedo::SnapshotType::Selection); - - GLVolume* volume = (*m_volumes)[volume_idx]; - - switch (m_mode) - { - case Volume: - { - do_remove_volume(volume_idx); - break; - } - case Instance: - { - do_remove_instance(volume->object_idx(), volume->instance_idx()); - break; - } - } - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::add_object(unsigned int object_idx, bool as_single_selection) -{ - if (!m_valid) - return; - - std::vector volume_idxs = get_volume_idxs_from_object(object_idx); - if ((!as_single_selection && contains_all_volumes(volume_idxs)) || - (as_single_selection && matches(volume_idxs))) - return; - - wxGetApp().plater()->take_snapshot(_L("Selection-Add Object"), UndoRedo::SnapshotType::Selection); - - // resets the current list if needed - if (as_single_selection) - clear(); - - m_mode = Instance; - - do_add_volumes(volume_idxs); - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::remove_object(unsigned int object_idx) -{ - if (!m_valid) - return; - - wxGetApp().plater()->take_snapshot(_L("Selection-Remove Object"), UndoRedo::SnapshotType::Selection); - - do_remove_object(object_idx); - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, bool as_single_selection) -{ - if (!m_valid) - return; - - const std::vector volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx); - if ((!as_single_selection && contains_all_volumes(volume_idxs)) || - (as_single_selection && matches(volume_idxs))) - return; - - wxGetApp().plater()->take_snapshot(_L("Selection-Add Instance"), UndoRedo::SnapshotType::Selection); - - // resets the current list if needed - if (as_single_selection) - clear(); - - m_mode = Instance; - - do_add_volumes(volume_idxs); - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::remove_instance(unsigned int object_idx, unsigned int instance_idx) -{ - if (!m_valid) - return; - - wxGetApp().plater()->take_snapshot(_L("Selection-Remove Instance"), UndoRedo::SnapshotType::Selection); - - do_remove_instance(object_idx, instance_idx); - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection) -{ - if (!m_valid) - return; - - std::vector volume_idxs = get_volume_idxs_from_volume(object_idx, instance_idx, volume_idx); - if ((!as_single_selection && contains_all_volumes(volume_idxs)) || - (as_single_selection && matches(volume_idxs))) - return; - - // resets the current list if needed - if (as_single_selection) - clear(); - - m_mode = Volume; - - do_add_volumes(volume_idxs); - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) -{ - if (!m_valid) - return; - - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - GLVolume* v = (*m_volumes)[i]; - if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) - do_remove_volume(i); - } - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::add_volumes(EMode mode, const std::vector& volume_idxs, bool as_single_selection) -{ - if (!m_valid) - return; - - if ((!as_single_selection && contains_all_volumes(volume_idxs)) || - (as_single_selection && matches(volume_idxs))) - return; - - // resets the current list if needed - if (as_single_selection) - clear(); - - m_mode = mode; - for (unsigned int i : volume_idxs) { - if (i < (unsigned int)m_volumes->size()) - do_add_volume(i); - } - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::remove_volumes(EMode mode, const std::vector& volume_idxs) -{ - if (!m_valid) - return; - - m_mode = mode; - for (unsigned int i : volume_idxs) { - if (i < (unsigned int)m_volumes->size()) - do_remove_volume(i); - } - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::add_all() -{ - if (!m_valid) - return; - - unsigned int count = 0; - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - if (!(*m_volumes)[i]->is_wipe_tower) - ++count; - } - - if ((unsigned int)m_list.size() == count) - return; - - wxGetApp().plater()->take_snapshot(_(L("Selection-Add All")), UndoRedo::SnapshotType::Selection); - - m_mode = Instance; - clear(); - - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - if (!(*m_volumes)[i]->is_wipe_tower) - do_add_volume(i); - } - - update_type(); - this->set_bounding_boxes_dirty(); -} - -void Selection::remove_all() -{ - if (!m_valid) - return; - - if (is_empty()) - return; - -// Not taking the snapshot with non-empty Redo stack will likely be more confusing than losing the Redo stack. -// Let's wait for user feedback. -// if (!wxGetApp().plater()->can_redo()) - wxGetApp().plater()->take_snapshot(_L("Selection-Remove All"), UndoRedo::SnapshotType::Selection); - - m_mode = Instance; - clear(); -} - -void Selection::set_deserialized(EMode mode, const std::vector> &volumes_and_instances) -{ - if (! m_valid) - return; - - m_mode = mode; - for (unsigned int i : m_list) - (*m_volumes)[i]->selected = false; - m_list.clear(); - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) - if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), (*m_volumes)[i]->geometry_id)) - do_add_volume(i); - update_type(); - set_bounding_boxes_dirty(); -} - -void Selection::clear() -{ - if (!m_valid) - return; - - if (m_list.empty()) - return; - - // ensure that the volumes get the proper color before next call to render (expecially needed for transparent volumes) - for (unsigned int i : m_list) { - GLVolume& volume = *(*m_volumes)[i]; - volume.selected = false; - volume.set_render_color(volume.color.is_transparent()); - } - - m_list.clear(); - - update_type(); - set_bounding_boxes_dirty(); - - // this happens while the application is closing - if (wxGetApp().obj_manipul() == nullptr) - return; - - // resets the cache in the sidebar - wxGetApp().obj_manipul()->reset_cache(); - - // #et_FIXME fake KillFocus from sidebar - wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); -} - -// Update the selection based on the new instance IDs. -void Selection::instances_changed(const std::vector &instance_ids_selected) -{ - assert(m_valid); - assert(m_mode == Instance); - m_list.clear(); - for (unsigned int volume_idx = 0; volume_idx < (unsigned int)m_volumes->size(); ++ volume_idx) { - const GLVolume *volume = (*m_volumes)[volume_idx]; - auto it = std::lower_bound(instance_ids_selected.begin(), instance_ids_selected.end(), volume->geometry_id.second); - if (it != instance_ids_selected.end() && *it == volume->geometry_id.second) - this->do_add_volume(volume_idx); - } - update_type(); - this->set_bounding_boxes_dirty(); -} - -// Update the selection based on the map from old indices to new indices after m_volumes changed. -// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances. -void Selection::volumes_changed(const std::vector &map_volume_old_to_new) -{ - assert(m_valid); - assert(m_mode == Volume); - IndicesList list_new; - for (unsigned int idx : m_list) - if (map_volume_old_to_new[idx] != size_t(-1)) { - unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx]; - (*m_volumes)[new_idx]->selected = true; - list_new.insert(new_idx); - } - m_list = std::move(list_new); - update_type(); - this->set_bounding_boxes_dirty(); -} - -bool Selection::is_single_full_instance() const -{ - if (m_type == SingleFullInstance) - return true; - - if (m_type == SingleFullObject) - return get_instance_idx() != -1; - - if (m_list.empty() || m_volumes->empty()) - return false; - - int object_idx = m_valid ? get_object_idx() : -1; - if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) - return false; - - int instance_idx = (*m_volumes)[*m_list.begin()]->instance_idx(); - - std::set volumes_idxs; - for (unsigned int i : m_list) { - const GLVolume* v = (*m_volumes)[i]; - if (object_idx != v->object_idx() || instance_idx != v->instance_idx()) - return false; - - int volume_idx = v->volume_idx(); - if (volume_idx >= 0) - volumes_idxs.insert(volume_idx); - } - - return m_model->objects[object_idx]->volumes.size() == volumes_idxs.size(); -} - -bool Selection::is_from_single_object() const -{ - const int idx = get_object_idx(); -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - return 0 <= idx && idx < int(m_model->objects.size()); -#else - return 0 <= idx && idx < 1000; -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL -} - -bool Selection::is_sla_compliant() const -{ - if (m_mode == Volume) - return false; - - for (unsigned int i : m_list) { - if ((*m_volumes)[i]->is_modifier) - return false; - } - - return true; -} - -bool Selection::contains_all_volumes(const std::vector& volume_idxs) const -{ - for (unsigned int i : volume_idxs) { - if (m_list.find(i) == m_list.end()) - return false; - } - - return true; -} - -bool Selection::contains_any_volume(const std::vector& volume_idxs) const -{ - for (unsigned int i : volume_idxs) { - if (m_list.find(i) != m_list.end()) - return true; - } - - return false; -} - -bool Selection::matches(const std::vector& volume_idxs) const -{ - unsigned int count = 0; - - for (unsigned int i : volume_idxs) { - if (m_list.find(i) != m_list.end()) - ++count; - else - return false; - } - - return count == (unsigned int)m_list.size(); -} - -bool Selection::requires_uniform_scale() const -{ - if (is_single_full_instance() || is_single_modifier() || is_single_volume()) - return false; - - return true; -} - -int Selection::get_object_idx() const -{ - return (m_cache.content.size() == 1) ? m_cache.content.begin()->first : -1; -} - -int Selection::get_instance_idx() const -{ - if (m_cache.content.size() == 1) { - const InstanceIdxsList& idxs = m_cache.content.begin()->second; - if (idxs.size() == 1) - return *idxs.begin(); - } - - return -1; -} - -const Selection::InstanceIdxsList& Selection::get_instance_idxs() const -{ - assert(m_cache.content.size() == 1); - return m_cache.content.begin()->second; -} - -const GLVolume* Selection::get_volume(unsigned int volume_idx) const -{ - return (m_valid && (volume_idx < (unsigned int)m_volumes->size())) ? (*m_volumes)[volume_idx] : nullptr; -} - -const BoundingBoxf3& Selection::get_bounding_box() const -{ - if (!m_bounding_box.has_value()) { - std::optional* bbox = const_cast*>(&m_bounding_box); - *bbox = BoundingBoxf3(); - if (m_valid) { - for (unsigned int i : m_list) { - (*bbox)->merge((*m_volumes)[i]->transformed_convex_hull_bounding_box()); - } - } - } - return *m_bounding_box; -} - -const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const -{ - if (!m_unscaled_instance_bounding_box.has_value()) { - std::optional* bbox = const_cast*>(&m_unscaled_instance_bounding_box); - *bbox = BoundingBoxf3(); - if (m_valid) { - for (unsigned int i : m_list) { - const GLVolume& volume = *(*m_volumes)[i]; - if (volume.is_modifier) - continue; - Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); - trafo.translation().z() += volume.get_sla_shift_z(); - (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); - } - } - } - return *m_unscaled_instance_bounding_box; -} - -const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const -{ - if (!m_scaled_instance_bounding_box.has_value()) { - std::optional* bbox = const_cast*>(&m_scaled_instance_bounding_box); - *bbox = BoundingBoxf3(); - if (m_valid) { - for (unsigned int i : m_list) { - const GLVolume& volume = *(*m_volumes)[i]; - if (volume.is_modifier) - continue; - Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix(); - trafo.translation().z() += volume.get_sla_shift_z(); - (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); - } - } - } - return *m_scaled_instance_bounding_box; -} - -void Selection::setup_cache() -{ - if (!m_valid) - return; - - set_caches(); -} - -void Selection::translate(const Vec3d& displacement, bool local) -{ - if (!m_valid) - return; - - EMode translation_type = m_mode; - - for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - if (m_mode == Volume || v.is_wipe_tower) { - if (local) - v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); - else { - const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; - v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); - } - } - else if (m_mode == Instance) { - if (is_from_fully_selected_instance(i)) - v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); - else { - const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; - v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); - translation_type = Volume; - } - } - } - -#if !DISABLE_INSTANCES_SYNCH - if (translation_type == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); - else if (translation_type == Volume) - synchronize_unselected_volumes(); -#endif // !DISABLE_INSTANCES_SYNCH - - ensure_not_below_bed(); - set_bounding_boxes_dirty(); - wxGetApp().plater()->canvas3D()->requires_check_outside_state(); -} - -// Rotate an object around one of the axes. Only one rotation component is expected to be changing. -void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) -{ - if (!m_valid) - return; - - // Only relative rotation values are allowed in the world coordinate system. - assert(!transformation_type.world() || transformation_type.relative()); - - if (!is_wipe_tower()) { - int rot_axis_max = 0; - if (rotation.isApprox(Vec3d::Zero())) { - for (unsigned int i : m_list) { - GLVolume &v = *(*m_volumes)[i]; - if (m_mode == Instance) { - v.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation()); - v.set_instance_offset(m_cache.volumes_data[i].get_instance_position()); - } - else if (m_mode == Volume) { - v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation()); - v.set_volume_offset(m_cache.volumes_data[i].get_volume_position()); - } - } - } - else { // this is not the wipe tower - //FIXME this does not work for absolute rotations (transformation_type.absolute() is true) - rotation.cwiseAbs().maxCoeff(&rot_axis_max); - -// if ( single instance or single volume ) - // Rotate around center , if only a single object or volume -// transformation_type.set_independent(); - - // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. - std::vector object_instance_first(m_model->objects.size(), -1); - auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { - const int first_volume_idx = object_instance_first[volume.object_idx()]; - if (rot_axis_max != 2 && first_volume_idx != -1) { - // Generic rotation, but no rotation around the Z axis. - // Always do a local rotation (do not consider the selection to be a rigid body). - assert(is_approx(rotation.z(), 0.0)); - const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; - const Vec3d &rotation = first_volume.get_instance_rotation(); - const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); - volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); - } - else { - // extracts rotations from the composed transformation - Vec3d new_rotation = transformation_type.world() ? - Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : - transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); - if (rot_axis_max == 2 && transformation_type.joint()) { - // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. - const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); - volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - } - volume.set_instance_rotation(new_rotation); - object_instance_first[volume.object_idx()] = i; - } - }; - - for (unsigned int i : m_list) { - GLVolume &v = *(*m_volumes)[i]; - if (is_single_full_instance()) - rotate_instance(v, i); - else if (is_single_volume() || is_single_modifier()) { - if (transformation_type.independent()) - v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); - else { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - v.set_volume_rotation(new_rotation); - } - } - else - { - if (m_mode == Instance) - rotate_instance(v, i); - else if (m_mode == Volume) { - // extracts rotations from the composed transformation - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - if (transformation_type.joint()) { - const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; - const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); - v.set_volume_offset(local_pivot + offset); - } - v.set_volume_rotation(new_rotation); - } - } - } - } - - #if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) - synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); - else if (m_mode == Volume) - synchronize_unselected_volumes(); - #endif // !DISABLE_INSTANCES_SYNCH - } - else { // it's the wipe tower that's selected and being rotated - GLVolume& volume = *((*m_volumes)[*m_list.begin()]); // the wipe tower is always alone in the selection - - // make sure the wipe tower rotates around its center, not origin - // we can assume that only Z rotation changes - const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); - const Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0.0, 0.0, 1.0)) * center_local; - volume.set_volume_rotation(rotation); - volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); - } - - set_bounding_boxes_dirty(); - wxGetApp().plater()->canvas3D()->requires_check_outside_state(); -} - -void Selection::flattening_rotate(const Vec3d& normal) -{ - // We get the normal in untransformed coordinates. We must transform it using the instance matrix, find out - // how to rotate the instance so it faces downwards and do the rotation. All that for all selected instances. - // The function assumes that is_from_single_object() holds. - assert(Slic3r::is_approx(normal.norm(), 1.)); - - if (!m_valid) - return; - - for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - // Normal transformed from the object coordinate space to the world coordinate space. - const auto &voldata = m_cache.volumes_data[i]; - Vec3d tnormal = (Geometry::assemble_transform( - Vec3d::Zero(), voldata.get_instance_rotation(), - voldata.get_instance_scaling_factor().cwiseInverse(), voldata.get_instance_mirror()) * normal).normalized(); - // Additional rotation to align tnormal with the down vector in the world coordinate space. - auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, - Vec3d::UnitZ()); - v.set_instance_rotation(Geometry::extract_euler_angles(extra_rotation.toRotationMatrix() * m_cache.volumes_data[i].get_instance_rotation_matrix())); - } - -#if !DISABLE_INSTANCES_SYNCH - // Apply the same transformation also to other instances, - // but respect their possibly diffrent z-rotation. - if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_GENERAL); -#endif // !DISABLE_INSTANCES_SYNCH - - this->set_bounding_boxes_dirty(); -} - -void Selection::scale(const Vec3d& scale, TransformationType transformation_type) -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) { - GLVolume &v = *(*m_volumes)[i]; - if (is_single_full_instance()) { - if (transformation_type.relative()) { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); - // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (transformation_type.joint()) - v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - - v.set_instance_scaling_factor(new_scale); - } - else { - if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { - // Non-uniform scaling. Transform the scaling factors into the local coordinate system. - // This is only possible, if the instance rotation is mulitples of ninety degrees. - assert(Geometry::is_rotation_ninety_degrees(v.get_instance_rotation())); - v.set_instance_scaling_factor((v.get_instance_transformation().get_matrix(true, false, true, true).matrix().block<3, 3>(0, 0).transpose() * scale).cwiseAbs()); - } - else - v.set_instance_scaling_factor(scale); - } - } - else if (is_single_volume() || is_single_modifier()) - v.set_volume_scaling_factor(scale); - else { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); - if (m_mode == Instance) { - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); - // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (transformation_type.joint()) - v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - - v.set_instance_scaling_factor(new_scale); - } - else if (m_mode == Volume) { - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); - // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (transformation_type.joint()) { - Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); - v.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); - } - v.set_volume_scaling_factor(new_scale); - } - } - } - -#if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); - else if (m_mode == Volume) - synchronize_unselected_volumes(); -#endif // !DISABLE_INSTANCES_SYNCH - - ensure_on_bed(); - set_bounding_boxes_dirty(); - wxGetApp().plater()->canvas3D()->requires_check_outside_state(); -} - -void Selection::scale_to_fit_print_volume(const BuildVolume& volume) -{ - auto fit = [this](double s, Vec3d offset) { - if (s <= 0.0 || s == 1.0) - return; - - wxGetApp().plater()->take_snapshot(_L("Scale To Fit")); - - TransformationType type; - type.set_world(); - type.set_relative(); - type.set_joint(); - - // apply scale - setup_cache(); - scale(s * Vec3d::Ones(), type); - wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot - - // center selection on print bed - setup_cache(); - offset.z() = -get_bounding_box().min.z(); - translate(offset); - wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot - - wxGetApp().obj_manipul()->set_dirty(); - }; - - auto fit_rectangle = [this, fit](const BuildVolume& volume) { - const BoundingBoxf3 print_volume = volume.bounding_volume(); - const Vec3d print_volume_size = print_volume.size(); - - // adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings - const Vec3d box_size = get_bounding_box().size() + 0.02 * Vec3d::Ones(); - - const double sx = (box_size.x() != 0.0) ? print_volume_size.x() / box_size.x() : 0.0; - const double sy = (box_size.y() != 0.0) ? print_volume_size.y() / box_size.y() : 0.0; - const double sz = (box_size.z() != 0.0) ? print_volume_size.z() / box_size.z() : 0.0; - - if (sx != 0.0 && sy != 0.0 && sz != 0.0) - fit(std::min(sx, std::min(sy, sz)), print_volume.center() - get_bounding_box().center()); - }; - - auto fit_circle = [this, fit](const BuildVolume& volume) { - const Geometry::Circled& print_circle = volume.circle(); - double print_circle_radius = unscale(print_circle.radius); - - if (print_circle_radius == 0.0) - return; - - Points points; - double max_z = 0.0; - for (unsigned int i : m_list) { - const GLVolume& v = *(*m_volumes)[i]; - TriangleMesh hull_3d = *v.convex_hull(); - hull_3d.transform(v.world_matrix()); - max_z = std::max(max_z, hull_3d.bounding_box().size().z()); - const Polygon hull_2d = hull_3d.convex_hull(); - points.insert(points.end(), hull_2d.begin(), hull_2d.end()); - } - - if (points.empty()) - return; - - const Geometry::Circled circle = Geometry::smallest_enclosing_circle_welzl(points); - // adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings - const double circle_radius = unscale(circle.radius) + 0.01; - - if (circle_radius == 0.0 || max_z == 0.0) - return; - - const double s = std::min(print_circle_radius / circle_radius, volume.max_print_height() / max_z); - const Vec3d sel_center = get_bounding_box().center(); - const Vec3d offset = s * (Vec3d(unscale(circle.center.x()), unscale(circle.center.y()), 0.5 * max_z) - sel_center); - const Vec3d print_center = { unscale(print_circle.center.x()), unscale(print_circle.center.y()), 0.5 * volume.max_print_height() }; - fit(s, print_center - (sel_center + offset)); - }; - - if (is_empty() || m_mode == Volume) - return; - - switch (volume.type()) - { - case BuildVolume::Type::Rectangle: { fit_rectangle(volume); break; } - case BuildVolume::Type::Circle: { fit_circle(volume); break; } - default: { break; } - } -} - -void Selection::mirror(Axis axis) -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - if (is_single_full_instance()) - v.set_instance_mirror(axis, -v.get_instance_mirror(axis)); - else if (m_mode == Volume) - v.set_volume_mirror(axis, -v.get_volume_mirror(axis)); - } - -#if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); - else if (m_mode == Volume) - synchronize_unselected_volumes(); -#endif // !DISABLE_INSTANCES_SYNCH - - set_bounding_boxes_dirty(); -} - -void Selection::translate(unsigned int object_idx, const Vec3d& displacement) -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - if (v.object_idx() == (int)object_idx) - v.set_instance_offset(v.get_instance_offset() + displacement); - } - - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) { - if (done.size() == m_volumes->size()) - break; - -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if ((*m_volumes)[i]->is_wipe_tower) - continue; - - int object_idx = (*m_volumes)[i]->object_idx(); -#else - int object_idx = (*m_volumes)[i]->object_idx(); - if (object_idx >= 1000) - continue; -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - - // Process unselected volumes of the object. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume& v = *(*m_volumes)[j]; - if (v.object_idx() != object_idx) - continue; - - v.set_instance_offset(v.get_instance_offset() + displacement); - done.insert(j); - } - } - - this->set_bounding_boxes_dirty(); -} - -void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) -{ - if (!m_valid) - return; - - for (unsigned int i : m_list) { - GLVolume& v = *(*m_volumes)[i]; - if (v.object_idx() == (int)object_idx && v.instance_idx() == (int)instance_idx) - v.set_instance_offset(v.get_instance_offset() + displacement); - } - - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) { - if (done.size() == m_volumes->size()) - break; - -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if ((*m_volumes)[i]->is_wipe_tower) - continue; - - int object_idx = (*m_volumes)[i]->object_idx(); -#else - int object_idx = (*m_volumes)[i]->object_idx(); - if (object_idx >= 1000) - continue; -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - - // Process unselected volumes of the object. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume& v = *(*m_volumes)[j]; - if (v.object_idx() != object_idx || v.instance_idx() != (int)instance_idx) - continue; - - v.set_instance_offset(v.get_instance_offset() + displacement); - done.insert(j); - } - } - - this->set_bounding_boxes_dirty(); -} - -void Selection::erase() -{ - if (!m_valid) - return; - - if (is_single_full_object()) - wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itObject, get_object_idx(), 0); - else if (is_multiple_full_object()) { - std::vector items; - items.reserve(m_cache.content.size()); - for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) { - items.emplace_back(ItemType::itObject, it->first, 0); - } - wxGetApp().obj_list()->delete_from_model_and_list(items); - } - else if (is_multiple_full_instance()) { - std::set> instances_idxs; - for (ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.begin(); obj_it != m_cache.content.end(); ++obj_it) { - for (InstanceIdxsList::reverse_iterator inst_it = obj_it->second.rbegin(); inst_it != obj_it->second.rend(); ++inst_it) { - instances_idxs.insert(std::make_pair(obj_it->first, *inst_it)); - } - } - - std::vector items; - items.reserve(instances_idxs.size()); - for (const std::pair& i : instances_idxs) { - items.emplace_back(ItemType::itInstance, i.first, i.second); - } - wxGetApp().obj_list()->delete_from_model_and_list(items); - } - else if (is_single_full_instance()) - wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itInstance, get_object_idx(), get_instance_idx()); - else if (is_mixed()) { - std::set items_set; - std::map volumes_in_obj; - - for (auto i : m_list) { - const auto gl_vol = (*m_volumes)[i]; - const auto glv_obj_idx = gl_vol->object_idx(); - const auto model_object = m_model->objects[glv_obj_idx]; - - if (model_object->instances.size() == 1) { - if (model_object->volumes.size() == 1) - items_set.insert(ItemForDelete(ItemType::itObject, glv_obj_idx, -1)); - else { - items_set.insert(ItemForDelete(ItemType::itVolume, glv_obj_idx, gl_vol->volume_idx())); - int idx = (volumes_in_obj.find(glv_obj_idx) == volumes_in_obj.end()) ? 0 : volumes_in_obj.at(glv_obj_idx); - volumes_in_obj[glv_obj_idx] = ++idx; - } - continue; - } - - const auto glv_ins_idx = gl_vol->instance_idx(); - - for (auto obj_ins : m_cache.content) { - if (obj_ins.first == glv_obj_idx) { - if (obj_ins.second.find(glv_ins_idx) != obj_ins.second.end()) { - if (obj_ins.second.size() == model_object->instances.size()) - items_set.insert(ItemForDelete(ItemType::itObject, glv_obj_idx, -1)); - else - items_set.insert(ItemForDelete(ItemType::itInstance, glv_obj_idx, glv_ins_idx)); - - break; - } - } - } - } - - std::vector items; - items.reserve(items_set.size()); - for (const ItemForDelete& i : items_set) { - if (i.type == ItemType::itVolume) { - const int vol_in_obj_cnt = volumes_in_obj.find(i.obj_idx) == volumes_in_obj.end() ? 0 : volumes_in_obj.at(i.obj_idx); - if (vol_in_obj_cnt == (int)m_model->objects[i.obj_idx]->volumes.size()) { - if (i.sub_obj_idx == vol_in_obj_cnt - 1) - items.emplace_back(ItemType::itObject, i.obj_idx, 0); - continue; - } - } - items.emplace_back(i.type, i.obj_idx, i.sub_obj_idx); - } - - wxGetApp().obj_list()->delete_from_model_and_list(items); - } - else { - std::set> volumes_idxs; - for (unsigned int i : m_list) { - const GLVolume* v = (*m_volumes)[i]; - // Only remove volumes associated with ModelVolumes from the object list. - // Temporary meshes (SLA supports or pads) are not managed by the object list. - if (v->volume_idx() >= 0) - volumes_idxs.insert(std::make_pair(v->object_idx(), v->volume_idx())); - } - - std::vector items; - items.reserve(volumes_idxs.size()); - for (const std::pair& v : volumes_idxs) { - items.emplace_back(ItemType::itVolume, v.first, v.second); - } - - wxGetApp().obj_list()->delete_from_model_and_list(items); - ensure_not_below_bed(); - } -} - -void Selection::render(float scale_factor) -{ - if (!m_valid || is_empty()) - return; - - m_scale_factor = scale_factor; - // render cumulative bounding box of selected volumes -#if ENABLE_LEGACY_OPENGL_REMOVAL - render_bounding_box(get_bounding_box(), ColorRGB::WHITE()); -#else - render_selected_volumes(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - render_synchronized_volumes(); -} - -#if ENABLE_RENDER_SELECTION_CENTER -void Selection::render_center(bool gizmo_is_dragging) -{ - if (!m_valid || is_empty()) - return; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; - - shader->start_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - const Vec3d center = gizmo_is_dragging ? m_cache.dragging_center : get_bounding_box().center(); - - glsafe(::glDisable(GL_DEPTH_TEST)); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform(center); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslated(center.x(), center.y(), center.z())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_vbo_sphere.set_color(ColorRGBA::WHITE()); -#else - m_vbo_sphere.set_color(-1, ColorRGBA::WHITE()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - m_vbo_sphere.render(); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} -#endif // ENABLE_RENDER_SELECTION_CENTER - -void Selection::render_sidebar_hints(const std::string& sidebar_field) -{ - if (sidebar_field.empty()) - return; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat" : "gouraud_light"); - if (shader == nullptr) - return; - - shader->start_using(); -#else - GLShaderProgram* shader = nullptr; - - if (!boost::starts_with(sidebar_field, "layer")) { - shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - shader->start_using(); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glEnable(GL_DEPTH_TEST)); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d base_matrix = Geometry::assemble_transform(get_bounding_box().center()); - Transform3d orient_matrix = Transform3d::Identity(); -#else - glsafe(::glPushMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (!boost::starts_with(sidebar_field, "layer")) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - shader->set_uniform("emission_factor", 0.05f); -#else - const Vec3d& center = get_bounding_box().center(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glTranslated(center.x(), center.y(), center.z())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - if (!boost::starts_with(sidebar_field, "position")) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES - Transform3d orient_matrix = Transform3d::Identity(); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - if (boost::starts_with(sidebar_field, "scale")) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::starts_with(sidebar_field, "rotation")) { - if (boost::ends_with(sidebar_field, "x")) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else if (boost::ends_with(sidebar_field, "y")) { - const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); - if (rotation.x() == 0.0) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - else - orient_matrix.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ())); - } - } -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glMultMatrixd(orient_matrix.data())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - } - else if (is_single_volume() || is_single_modifier()) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); -#else - glsafe(::glTranslated(center.x(), center.y(), center.z())); - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (!boost::starts_with(sidebar_field, "position")) - orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glMultMatrixd(orient_matrix.data())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - else { -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (requires_local_axes()) - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); -#else - glsafe(::glTranslated(center.x(), center.y(), center.z())); - if (requires_local_axes()) { - const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); - glsafe(::glMultMatrixd(orient_matrix.data())); - } -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!boost::starts_with(sidebar_field, "layer")) - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (boost::starts_with(sidebar_field, "position")) - render_sidebar_position_hints(sidebar_field, *shader, base_matrix * orient_matrix); - else if (boost::starts_with(sidebar_field, "rotation")) - render_sidebar_rotation_hints(sidebar_field, *shader, base_matrix * orient_matrix); - else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) - render_sidebar_scale_hints(sidebar_field, *shader, base_matrix * orient_matrix); - else if (boost::starts_with(sidebar_field, "layer")) - render_sidebar_layers_hints(sidebar_field, *shader); -#else - if (boost::starts_with(sidebar_field, "position")) - render_sidebar_position_hints(sidebar_field); - else if (boost::starts_with(sidebar_field, "rotation")) - render_sidebar_rotation_hints(sidebar_field); - else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) - render_sidebar_scale_hints(sidebar_field); - else if (boost::starts_with(sidebar_field, "layer")) - render_sidebar_layers_hints(sidebar_field); - - glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - if (!boost::starts_with(sidebar_field, "layer")) -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); -} - -bool Selection::requires_local_axes() const -{ - return m_mode == Volume && is_from_single_instance(); -} - -void Selection::copy_to_clipboard() -{ - if (!m_valid) - return; - - m_clipboard.reset(); - - for (const ObjectIdxsToInstanceIdxsMap::value_type& object : m_cache.content) { - ModelObject* src_object = m_model->objects[object.first]; - ModelObject* dst_object = m_clipboard.add_object(); - dst_object->name = src_object->name; - dst_object->input_file = src_object->input_file; - dst_object->config.assign_config(src_object->config); - dst_object->sla_support_points = src_object->sla_support_points; - dst_object->sla_points_status = src_object->sla_points_status; - dst_object->sla_drain_holes = src_object->sla_drain_holes; - dst_object->layer_config_ranges = src_object->layer_config_ranges; // #ys_FIXME_experiment - dst_object->layer_height_profile.assign(src_object->layer_height_profile); - dst_object->origin_translation = src_object->origin_translation; - - for (int i : object.second) { - dst_object->add_instance(*src_object->instances[i]); - } - - for (unsigned int i : m_list) { - // Copy the ModelVolumes only for the selected GLVolumes of the 1st selected instance. - const GLVolume* volume = (*m_volumes)[i]; - if (volume->object_idx() == object.first && volume->instance_idx() == *object.second.begin()) { - int volume_idx = volume->volume_idx(); - if (0 <= volume_idx && volume_idx < (int)src_object->volumes.size()) { - ModelVolume* src_volume = src_object->volumes[volume_idx]; - ModelVolume* dst_volume = dst_object->add_volume(*src_volume); - dst_volume->set_new_unique_id(); - } - else - assert(false); - } - } - } - - m_clipboard.set_mode(m_mode); -} - -void Selection::paste_from_clipboard() -{ - if (!m_valid || m_clipboard.is_empty()) - return; - - switch (m_clipboard.get_mode()) - { - case Volume: - { - if (is_from_single_instance()) - paste_volumes_from_clipboard(); - - break; - } - case Instance: - { - if (m_mode == Instance) - paste_objects_from_clipboard(); - - break; - } - } -} - -std::vector Selection::get_volume_idxs_from_object(unsigned int object_idx) const -{ - std::vector idxs; - - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - if ((*m_volumes)[i]->object_idx() == (int)object_idx) - idxs.push_back(i); - } - - return idxs; -} - -std::vector Selection::get_volume_idxs_from_instance(unsigned int object_idx, unsigned int instance_idx) const -{ - std::vector idxs; - - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - const GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == (int)object_idx) && (v->instance_idx() == (int)instance_idx)) - idxs.push_back(i); - } - - return idxs; -} - -std::vector Selection::get_volume_idxs_from_volume(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx) const -{ - std::vector idxs; - - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { - const GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == (int)object_idx) && (v->volume_idx() == (int)volume_idx)) - { - if (((int)instance_idx != -1) && (v->instance_idx() == (int)instance_idx)) - idxs.push_back(i); - } - } - - return idxs; -} - -std::vector Selection::get_missing_volume_idxs_from(const std::vector& volume_idxs) const -{ - std::vector idxs; - - for (unsigned int i : m_list) - { - std::vector::const_iterator it = std::find(volume_idxs.begin(), volume_idxs.end(), i); - if (it == volume_idxs.end()) - idxs.push_back(i); - } - - return idxs; -} - -std::vector Selection::get_unselected_volume_idxs_from(const std::vector& volume_idxs) const -{ - std::vector idxs; - - for (unsigned int i : volume_idxs) - { - if (m_list.find(i) == m_list.end()) - idxs.push_back(i); - } - - return idxs; -} - -void Selection::update_valid() -{ - m_valid = (m_volumes != nullptr) && (m_model != nullptr); -} - -void Selection::update_type() -{ - m_cache.content.clear(); - m_type = Mixed; - - for (unsigned int i : m_list) - { - const GLVolume* volume = (*m_volumes)[i]; - int obj_idx = volume->object_idx(); - int inst_idx = volume->instance_idx(); - ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.find(obj_idx); - if (obj_it == m_cache.content.end()) - obj_it = m_cache.content.insert(ObjectIdxsToInstanceIdxsMap::value_type(obj_idx, InstanceIdxsList())).first; - - obj_it->second.insert(inst_idx); - } - - bool requires_disable = false; - - if (!m_valid) - m_type = Invalid; - else - { - if (m_list.empty()) - m_type = Empty; - else if (m_list.size() == 1) - { - const GLVolume* first = (*m_volumes)[*m_list.begin()]; - if (first->is_wipe_tower) - m_type = WipeTower; - else if (first->is_modifier) - { - m_type = SingleModifier; - requires_disable = true; - } - else - { - const ModelObject* model_object = m_model->objects[first->object_idx()]; - unsigned int volumes_count = (unsigned int)model_object->volumes.size(); - unsigned int instances_count = (unsigned int)model_object->instances.size(); - if (volumes_count * instances_count == 1) - { - m_type = SingleFullObject; - // ensures the correct mode is selected - m_mode = Instance; - } - else if (volumes_count == 1) // instances_count > 1 - { - m_type = SingleFullInstance; - // ensures the correct mode is selected - m_mode = Instance; - } - else - { - m_type = SingleVolume; - requires_disable = true; - } - } - } - else - { - unsigned int sla_volumes_count = 0; - // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is - for (unsigned int i : m_list) { - if ((*m_volumes)[i]->volume_idx() < 0) - ++sla_volumes_count; - } - - if (m_cache.content.size() == 1) // single object - { - const ModelObject* model_object = m_model->objects[m_cache.content.begin()->first]; - unsigned int model_volumes_count = (unsigned int)model_object->volumes.size(); - - unsigned int instances_count = (unsigned int)model_object->instances.size(); - unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); - if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) - { - m_type = SingleFullObject; - // ensures the correct mode is selected - m_mode = Instance; - } - else if (selected_instances_count == 1) - { - if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) - { - m_type = SingleFullInstance; - // ensures the correct mode is selected - m_mode = Instance; - } - else - { - unsigned int modifiers_count = 0; - for (unsigned int i : m_list) - { - if ((*m_volumes)[i]->is_modifier) - ++modifiers_count; - } - - if (modifiers_count == 0) - m_type = MultipleVolume; - else if (modifiers_count == (unsigned int)m_list.size()) - m_type = MultipleModifier; - - requires_disable = true; - } - } - else if ((selected_instances_count > 1) && (selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size())) - { - m_type = MultipleFullInstance; - // ensures the correct mode is selected - m_mode = Instance; - } - } - else - { - unsigned int sels_cntr = 0; - for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) - { - const ModelObject* model_object = m_model->objects[it->first]; - unsigned int volumes_count = (unsigned int)model_object->volumes.size(); - unsigned int instances_count = (unsigned int)model_object->instances.size(); - sels_cntr += volumes_count * instances_count; - } - if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) - { - m_type = MultipleFullObject; - // ensures the correct mode is selected - m_mode = Instance; - } - } - } - } - - int object_idx = get_object_idx(); - int instance_idx = get_instance_idx(); - for (GLVolume* v : *m_volumes) - { - v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false; - } - -#if ENABLE_SELECTION_DEBUG_OUTPUT - std::cout << "Selection: "; - std::cout << "mode: "; - switch (m_mode) - { - case Volume: - { - std::cout << "Volume"; - break; - } - case Instance: - { - std::cout << "Instance"; - break; - } - } - - std::cout << " - type: "; - - switch (m_type) - { - case Invalid: - { - std::cout << "Invalid" << std::endl; - break; - } - case Empty: - { - std::cout << "Empty" << std::endl; - break; - } - case WipeTower: - { - std::cout << "WipeTower" << std::endl; - break; - } - case SingleModifier: - { - std::cout << "SingleModifier" << std::endl; - break; - } - case MultipleModifier: - { - std::cout << "MultipleModifier" << std::endl; - break; - } - case SingleVolume: - { - std::cout << "SingleVolume" << std::endl; - break; - } - case MultipleVolume: - { - std::cout << "MultipleVolume" << std::endl; - break; - } - case SingleFullObject: - { - std::cout << "SingleFullObject" << std::endl; - break; - } - case MultipleFullObject: - { - std::cout << "MultipleFullObject" << std::endl; - break; - } - case SingleFullInstance: - { - std::cout << "SingleFullInstance" << std::endl; - break; - } - case MultipleFullInstance: - { - std::cout << "MultipleFullInstance" << std::endl; - break; - } - case Mixed: - { - std::cout << "Mixed" << std::endl; - break; - } - } -#endif // ENABLE_SELECTION_DEBUG_OUTPUT -} - -void Selection::set_caches() -{ - m_cache.volumes_data.clear(); - m_cache.sinking_volumes.clear(); - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - const GLVolume& v = *(*m_volumes)[i]; - m_cache.volumes_data.emplace(i, VolumeCache(v.get_volume_transformation(), v.get_instance_transformation())); - if (v.is_sinking()) - m_cache.sinking_volumes.push_back(i); - } - m_cache.dragging_center = get_bounding_box().center(); -} - -void Selection::do_add_volume(unsigned int volume_idx) -{ - m_list.insert(volume_idx); - GLVolume* v = (*m_volumes)[volume_idx]; - v->selected = true; - if (v->hover == GLVolume::HS_Select || v->hover == GLVolume::HS_Deselect) - v->hover = GLVolume::HS_Hover; -} - -void Selection::do_add_volumes(const std::vector& volume_idxs) -{ - for (unsigned int i : volume_idxs) - { - if (i < (unsigned int)m_volumes->size()) - do_add_volume(i); - } -} - -void Selection::do_remove_volume(unsigned int volume_idx) -{ - IndicesList::iterator v_it = m_list.find(volume_idx); - if (v_it == m_list.end()) - return; - - m_list.erase(v_it); - - (*m_volumes)[volume_idx]->selected = false; -} - -void Selection::do_remove_instance(unsigned int object_idx, unsigned int instance_idx) -{ - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - GLVolume* v = (*m_volumes)[i]; - if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx) - do_remove_volume(i); - } -} - -void Selection::do_remove_object(unsigned int object_idx) -{ - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { - GLVolume* v = (*m_volumes)[i]; - if (v->object_idx() == (int)object_idx) - do_remove_volume(i); - } -} - -#if !ENABLE_LEGACY_OPENGL_REMOVAL -void Selection::render_selected_volumes() const -{ - float color[3] = { 1.0f, 1.0f, 1.0f }; - render_bounding_box(get_bounding_box(), color); -} -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -void Selection::render_synchronized_volumes() -{ - if (m_mode == Instance) - return; - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - float color[3] = { 1.0f, 1.0f, 0.0f }; -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - for (unsigned int i : m_list) { - const GLVolume& volume = *(*m_volumes)[i]; - int object_idx = volume.object_idx(); - int volume_idx = volume.volume_idx(); - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (i == j) - continue; - - const GLVolume& v = *(*m_volumes)[j]; - if (v.object_idx() != object_idx || v.volume_idx() != volume_idx) - continue; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - render_bounding_box(v.transformed_convex_hull_bounding_box(), ColorRGB::YELLOW()); -#else - render_bounding_box(v.transformed_convex_hull_bounding_box(), color); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } -} - -#if ENABLE_LEGACY_OPENGL_REMOVAL -void Selection::render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color) -{ -#else -void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) const -{ - if (color == nullptr) - return; - - const Vec3f b_min = box.min.cast(); - const Vec3f b_max = box.max.cast(); - const Vec3f size = 0.2f * box.size().cast(); - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glColor3fv(color)); - glsafe(::glLineWidth(2.0f * m_scale_factor)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const BoundingBoxf3& curr_box = m_box.get_bounding_box(); - if (!m_box.is_initialized() || !is_approx(box.min, curr_box.min) || !is_approx(box.max, curr_box.max)) { - m_box.reset(); - - const Vec3f b_min = box.min.cast(); - const Vec3f b_max = box.max.cast(); - const Vec3f size = 0.2f * box.size().cast(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.reserve_vertices(48); - init_data.reserve_indices(48); - - // vertices - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z() + size.z())); - - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z() + size.z())); - - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z() + size.z())); - - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z() + size.z())); - - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z() - size.z())); - - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z() - size.z())); - - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z() - size.z())); - - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); - init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z() - size.z())); - - // indices - for (unsigned int i = 0; i < 48; ++i) { - init_data.add_index(i); - } - - m_box.init_from(std::move(init_data)); - } - - glsafe(::glEnable(GL_DEPTH_TEST)); - - glsafe(::glLineWidth(2.0f * m_scale_factor)); - - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; - - shader->start_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - 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()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_box.set_color(to_rgba(color)); - m_box.render(); - shader->stop_using(); -#else - ::glBegin(GL_LINES); - - ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_min(2)); - ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_min(2)); - ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1), b_min(2) + size(2)); - - ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_min(2)); - ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_min(2)); - ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1), b_min(2) + size(2)); - - ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_min(2)); - ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_min(2)); - ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1), b_min(2) + size(2)); - - ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_min(2)); - ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_min(2)); - ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1), b_min(2) + size(2)); - - ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_max(2)); - ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_max(2)); - ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1), b_max(2) - size(2)); - - ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_max(2)); - ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_max(2)); - ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1), b_max(2) - size(2)); - - ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_max(2)); - ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_max(2)); - ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1), b_max(2) - size(2)); - - ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_max(2)); - ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_max(2)); - ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1), b_max(2) - size(2)); - - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -static ColorRGBA get_color(Axis axis) -{ - return AXES_COLOR[axis]; -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void Selection::render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) -#else -void Selection::render_sidebar_position_hints(const std::string& sidebar_field) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_matrix = camera.get_view_matrix() * matrix; - shader.set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (boost::ends_with(sidebar_field, "x")) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()); - shader.set_uniform("view_model_matrix", view_model_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_arrow.set_color(get_color(X)); - m_arrow.render(); - } - else if (boost::ends_with(sidebar_field, "y")) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - shader.set_uniform("view_model_matrix", view_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_arrow.set_color(get_color(Y)); - m_arrow.render(); - } - else if (boost::ends_with(sidebar_field, "z")) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitX()); - shader.set_uniform("view_model_matrix", view_model_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_arrow.set_color(get_color(Z)); - m_arrow.render(); - } -#else - if (boost::ends_with(sidebar_field, "x")) { - glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); - m_arrow.set_color(-1, get_color(X)); - m_arrow.render(); - } - else if (boost::ends_with(sidebar_field, "y")) { - m_arrow.set_color(-1, get_color(Y)); - m_arrow.render(); - } - else if (boost::ends_with(sidebar_field, "z")) { - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); - m_arrow.set_color(-1, get_color(Z)); - m_arrow.render(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) -#else -void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - auto render_sidebar_rotation_hint = [this](GLShaderProgram& shader, const Transform3d& matrix) { - Transform3d view_model_matrix = matrix; - shader.set_uniform("view_model_matrix", view_model_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_curved_arrow.render(); - view_model_matrix = matrix * Geometry::assemble_transform(Vec3d::Zero(), PI * Vec3d::UnitZ()); - shader.set_uniform("view_model_matrix", view_model_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); - m_curved_arrow.render(); - }; - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_matrix = camera.get_view_matrix() * matrix; - shader.set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - auto render_sidebar_rotation_hint = [this]() { - m_curved_arrow.render(); - glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); - m_curved_arrow.render(); - }; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (boost::ends_with(sidebar_field, "x")) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - m_curved_arrow.set_color(get_color(X)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_sidebar_rotation_hint(shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY())); -#else - render_sidebar_rotation_hint(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - else if (boost::ends_with(sidebar_field, "y")) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - m_curved_arrow.set_color(get_color(Y)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_sidebar_rotation_hint(shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitX())); -#else - render_sidebar_rotation_hint(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - else if (boost::ends_with(sidebar_field, "z")) { - m_curved_arrow.set_color(get_color(Z)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_sidebar_rotation_hint(shader, view_matrix); -#else - render_sidebar_rotation_hint(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } -#else - auto render_sidebar_rotation_hint = [this]() { - m_curved_arrow.render(); - glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); - m_curved_arrow.render(); - }; - - if (boost::ends_with(sidebar_field, "x")) { - glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); - m_curved_arrow.set_color(-1, get_color(X)); - render_sidebar_rotation_hint(); - } - else if (boost::ends_with(sidebar_field, "y")) { - glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); - m_curved_arrow.set_color(-1, get_color(Y)); - render_sidebar_rotation_hint(); - } - else if (boost::ends_with(sidebar_field, "z")) { - m_curved_arrow.set_color(-1, get_color(Z)); - render_sidebar_rotation_hint(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) -#else -void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ - const bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis, GLShaderProgram& shader, const Transform3d& matrix) { -#else - auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis) { -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_arrow.set_color(uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); -#else - m_arrow.set_color(-1, uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_GL_SHADERS_ATTRIBUTES - Transform3d view_model_matrix = matrix * Geometry::assemble_transform(5.0 * Vec3d::UnitY()); - shader.set_uniform("view_model_matrix", view_model_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - GLShaderProgram* shader = wxGetApp().get_current_shader(); - if (shader != nullptr) - shader->set_uniform("emission_factor", 0.0f); - - glsafe(::glTranslated(0.0, 5.0, 0.0)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_arrow.render(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - view_model_matrix = matrix * Geometry::assemble_transform(-5.0 * Vec3d::UnitY(), PI * Vec3d::UnitZ()); - shader.set_uniform("view_model_matrix", view_model_matrix); - shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glTranslated(0.0, -10.0, 0.0)); - glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_arrow.render(); - }; - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_matrix = camera.get_view_matrix() * matrix; - shader.set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (boost::ends_with(sidebar_field, "x") || uniform_scale) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_sidebar_scale_hint(X, shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ())); -#else - glsafe(::glPushMatrix()); - glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); - render_sidebar_scale_hint(X); - glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - - if (boost::ends_with(sidebar_field, "y") || uniform_scale) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_sidebar_scale_hint(Y, shader, view_matrix); -#else - glsafe(::glPushMatrix()); - render_sidebar_scale_hint(Y); - glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } - - if (boost::ends_with(sidebar_field, "z") || uniform_scale) { -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_sidebar_scale_hint(Z, shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitX())); -#else - glsafe(::glPushMatrix()); - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); - render_sidebar_scale_hint(Z); - glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - } -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void Selection::render_sidebar_layers_hints(const std::string& sidebar_field, GLShaderProgram& shader) -#else -void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ - static const float Margin = 10.0f; - - std::string field = sidebar_field; - - // extract max_z - std::string::size_type pos = field.rfind("_"); - if (pos == std::string::npos) - return; - - const float max_z = float(string_to_double_decimal_point(field.substr(pos + 1))); - - // extract min_z - field = field.substr(0, pos); - pos = field.rfind("_"); - if (pos == std::string::npos) - return; - - const float min_z = float(string_to_double_decimal_point(field.substr(pos + 1))); - - // extract type - field = field.substr(0, pos); - pos = field.rfind("_"); - if (pos == std::string::npos) - return; - - const int type = std::stoi(field.substr(pos + 1)); - - const BoundingBoxf3& box = get_bounding_box(); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - const float min_x = float(box.min.x()) - Margin; - const float max_x = float(box.max.x()) + Margin; - const float min_y = float(box.min.y()) - Margin; - const float max_y = float(box.max.y()) + Margin; -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - // view dependend order of rendering to keep correct transparency - const bool camera_on_top = wxGetApp().plater()->get_camera().is_looking_downward(); - const float z1 = camera_on_top ? min_z : max_z; - const float z2 = camera_on_top ? max_z : min_z; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - const Vec3f p1 = { float(box.min.x()) - Margin, float(box.min.y()) - Margin, z1 }; - const Vec3f p2 = { float(box.max.x()) + Margin, float(box.max.y()) + Margin, z2 }; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!m_planes.models[0].is_initialized() || !is_approx(m_planes.check_points[0], p1)) { - m_planes.check_points[0] = p1; - m_planes.models[0].reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - - // vertices - init_data.add_vertex(Vec3f(p1.x(), p1.y(), z1)); - init_data.add_vertex(Vec3f(p2.x(), p1.y(), z1)); - init_data.add_vertex(Vec3f(p2.x(), p2.y(), z1)); - init_data.add_vertex(Vec3f(p1.x(), p2.y(), z1)); - - // indices - init_data.add_triangle(0, 1, 2); - init_data.add_triangle(2, 3, 0); - - m_planes.models[0].init_from(std::move(init_data)); - } - - if (!m_planes.models[1].is_initialized() || !is_approx(m_planes.check_points[1], p2)) { - m_planes.check_points[1] = p2; - m_planes.models[1].reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - - // vertices - init_data.add_vertex(Vec3f(p1.x(), p1.y(), z2)); - init_data.add_vertex(Vec3f(p2.x(), p1.y(), z2)); - init_data.add_vertex(Vec3f(p2.x(), p2.y(), z2)); - init_data.add_vertex(Vec3f(p1.x(), p2.y(), z2)); - - // indices - init_data.add_triangle(0, 1, 2); - init_data.add_triangle(2, 3, 0); - - m_planes.models[1].init_from(std::move(init_data)); - } - -#if ENABLE_GL_SHADERS_ATTRIBUTES - 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()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - m_planes.models[0].set_color((camera_on_top && type == 1) || (!camera_on_top && type == 2) ? SOLID_PLANE_COLOR : TRANSPARENT_PLANE_COLOR); - m_planes.models[0].render(); - m_planes.models[1].set_color((camera_on_top && type == 2) || (!camera_on_top && type == 1) ? SOLID_PLANE_COLOR : TRANSPARENT_PLANE_COLOR); - m_planes.models[1].render(); -#else - ::glBegin(GL_QUADS); - ::glColor4fv((camera_on_top && type == 1) || (!camera_on_top && type == 2) ? SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data()); - ::glVertex3f(min_x, min_y, z1); - ::glVertex3f(max_x, min_y, z1); - ::glVertex3f(max_x, max_y, z1); - ::glVertex3f(min_x, max_y, z1); - glsafe(::glEnd()); - - ::glBegin(GL_QUADS); - ::glColor4fv((camera_on_top && type == 2) || (!camera_on_top && type == 1) ? SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data()); - ::glVertex3f(min_x, min_y, z2); - ::glVertex3f(max_x, min_y, z2); - ::glVertex3f(max_x, max_y, z2); - ::glVertex3f(min_x, max_y, z2); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glDisable(GL_BLEND)); -} - -#ifndef NDEBUG -static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) -{ - const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); - const Vec3d axis = angle_axis.axis(); - const double angle = angle_axis.angle(); - if (std::abs(angle) < 1e-8) - return true; - assert(std::abs(axis.x()) < 1e-8); - assert(std::abs(axis.y()) < 1e-8); - assert(std::abs(std::abs(axis.z()) - 1.) < 1e-8); - return std::abs(axis.x()) < 1e-8 && std::abs(axis.y()) < 1e-8 && std::abs(std::abs(axis.z()) - 1.) < 1e-8; -} - -static void verify_instances_rotation_synchronized(const Model &model, const GLVolumePtrs &volumes) -{ - for (int idx_object = 0; idx_object < int(model.objects.size()); ++idx_object) { - int idx_volume_first = -1; - for (int i = 0; i < (int)volumes.size(); ++i) { - if (volumes[i]->object_idx() == idx_object) { - idx_volume_first = i; - break; - } - } - assert(idx_volume_first != -1); // object without instances? - if (idx_volume_first == -1) - continue; - const Vec3d &rotation0 = volumes[idx_volume_first]->get_instance_rotation(); - for (int i = idx_volume_first + 1; i < (int)volumes.size(); ++i) - if (volumes[i]->object_idx() == idx_object) { - const Vec3d &rotation = volumes[i]->get_instance_rotation(); - assert(is_rotation_xy_synchronized(rotation, rotation0)); - } - } -} -#endif /* NDEBUG */ - -void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) -{ - std::set done; // prevent processing volumes twice - done.insert(m_list.begin(), m_list.end()); - - for (unsigned int i : m_list) { - if (done.size() == m_volumes->size()) - break; - - const GLVolume* volume = (*m_volumes)[i]; -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if (volume->is_wipe_tower) - continue; - - const int object_idx = volume->object_idx(); -#else - const int object_idx = volume->object_idx(); - if (object_idx >= 1000) - continue; -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - - const int instance_idx = volume->instance_idx(); - const Vec3d& rotation = volume->get_instance_rotation(); - const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); - const Vec3d& mirror = volume->get_instance_mirror(); - - // Process unselected instances. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (done.size() == m_volumes->size()) - break; - - if (done.find(j) != done.end()) - continue; - - GLVolume* v = (*m_volumes)[j]; - if (v->object_idx() != object_idx || v->instance_idx() == instance_idx) - continue; - - assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); - switch (sync_rotation_type) { - case SYNC_ROTATION_NONE: { - // z only rotation -> synch instance z - // The X,Y rotations should be synchronized from start to end of the rotation. - assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) - v->set_instance_offset(Z, volume->get_instance_offset().z()); - break; - } - case SYNC_ROTATION_GENERAL: - // generic rotation -> update instance z with the delta of the rotation. - const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); - v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); - break; - } - - v->set_instance_scaling_factor(scaling_factor); - v->set_instance_mirror(mirror); - - done.insert(j); - } - } - -#ifndef NDEBUG - verify_instances_rotation_synchronized(*m_model, *m_volumes); -#endif /* NDEBUG */ -} - -void Selection::synchronize_unselected_volumes() -{ - for (unsigned int i : m_list) { - const GLVolume* volume = (*m_volumes)[i]; -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if (volume->is_wipe_tower) - continue; - - const int object_idx = volume->object_idx(); -#else - const int object_idx = volume->object_idx(); - if (object_idx >= 1000) - continue; -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - - const int volume_idx = volume->volume_idx(); - const Vec3d& offset = volume->get_volume_offset(); - const Vec3d& rotation = volume->get_volume_rotation(); - const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); - const Vec3d& mirror = volume->get_volume_mirror(); - - // Process unselected volumes. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { - if (j == i) - continue; - - GLVolume* v = (*m_volumes)[j]; - if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) - continue; - - v->set_volume_offset(offset); - v->set_volume_rotation(rotation); - v->set_volume_scaling_factor(scaling_factor); - v->set_volume_mirror(mirror); - } - } -} - -void Selection::ensure_on_bed() -{ - typedef std::map, double> InstancesToZMap; - InstancesToZMap instances_min_z; - - for (size_t i = 0; i < m_volumes->size(); ++i) { - GLVolume* volume = (*m_volumes)[i]; - if (!volume->is_wipe_tower && !volume->is_modifier && - std::find(m_cache.sinking_volumes.begin(), m_cache.sinking_volumes.end(), i) == m_cache.sinking_volumes.end()) { - const double min_z = volume->transformed_convex_hull_bounding_box().min.z(); - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_min_z.find(instance); - if (it == instances_min_z.end()) - it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; - - it->second = std::min(it->second, min_z); - } - } - - for (GLVolume* volume : *m_volumes) { - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_min_z.find(instance); - if (it != instances_min_z.end()) - volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second); - } -} - -void Selection::ensure_not_below_bed() -{ - typedef std::map, double> InstancesToZMap; - InstancesToZMap instances_max_z; - - for (size_t i = 0; i < m_volumes->size(); ++i) { - GLVolume* volume = (*m_volumes)[i]; - if (!volume->is_wipe_tower && !volume->is_modifier) { - const double max_z = volume->transformed_convex_hull_bounding_box().max.z(); - const std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_max_z.find(instance); - if (it == instances_max_z.end()) - it = instances_max_z.insert({ instance, -DBL_MAX }).first; - - it->second = std::max(it->second, max_z); - } - } - - if (is_any_volume()) { - for (unsigned int i : m_list) { - GLVolume& volume = *(*m_volumes)[i]; - const std::pair instance = std::make_pair(volume.object_idx(), volume.instance_idx()); - InstancesToZMap::const_iterator it = instances_max_z.find(instance); - const double z_shift = SINKING_MIN_Z_THRESHOLD - it->second; - if (it != instances_max_z.end() && z_shift > 0.0) - volume.set_volume_offset(Z, volume.get_volume_offset(Z) + z_shift); - } - } - else { - for (GLVolume* volume : *m_volumes) { - const std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::const_iterator it = instances_max_z.find(instance); - if (it != instances_max_z.end() && it->second < SINKING_MIN_Z_THRESHOLD) - volume->set_instance_offset(Z, volume->get_instance_offset(Z) + SINKING_MIN_Z_THRESHOLD - it->second); - } - } -} - -bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const -{ - struct SameInstance - { - int obj_idx; - int inst_idx; - GLVolumePtrs& volumes; - - SameInstance(int obj_idx, int inst_idx, GLVolumePtrs& volumes) : obj_idx(obj_idx), inst_idx(inst_idx), volumes(volumes) {} - bool operator () (unsigned int i) { return (volumes[i]->volume_idx() >= 0) && (volumes[i]->object_idx() == obj_idx) && (volumes[i]->instance_idx() == inst_idx); } - }; - - if ((unsigned int)m_volumes->size() <= volume_idx) - return false; - - GLVolume* volume = (*m_volumes)[volume_idx]; - int object_idx = volume->object_idx(); - if ((int)m_model->objects.size() <= object_idx) - return false; - - unsigned int count = (unsigned int)std::count_if(m_list.begin(), m_list.end(), SameInstance(object_idx, volume->instance_idx(), *m_volumes)); - return count == (unsigned int)m_model->objects[object_idx]->volumes.size(); -} - -void Selection::paste_volumes_from_clipboard() -{ -#ifdef _DEBUG - check_model_ids_validity(*m_model); -#endif /* _DEBUG */ - - int dst_obj_idx = get_object_idx(); - if ((dst_obj_idx < 0) || ((int)m_model->objects.size() <= dst_obj_idx)) - return; - - ModelObject* dst_object = m_model->objects[dst_obj_idx]; - - int dst_inst_idx = get_instance_idx(); - if ((dst_inst_idx < 0) || ((int)dst_object->instances.size() <= dst_inst_idx)) - return; - - ModelObject* src_object = m_clipboard.get_object(0); - if (src_object != nullptr) - { - ModelInstance* dst_instance = dst_object->instances[dst_inst_idx]; - BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx); - Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true); - Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true); - bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix); - - // used to keep relative position of multivolume selections when pasting from another object - BoundingBoxf3 total_bb; - - ModelVolumePtrs volumes; - for (ModelVolume* src_volume : src_object->volumes) - { - ModelVolume* dst_volume = dst_object->add_volume(*src_volume); - dst_volume->set_new_unique_id(); - if (from_same_object) - { -// // if the volume comes from the same object, apply the offset in world system -// double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); -// dst_volume->translate(dst_matrix.inverse() * Vec3d(offset, offset, 0.0)); - } - else - { - // if the volume comes from another object, apply the offset as done when adding modifiers - // see ObjectList::load_generic_subobject() - total_bb.merge(dst_volume->mesh().bounding_box().transformed(src_volume->get_matrix())); - } - - volumes.push_back(dst_volume); -#ifdef _DEBUG - check_model_ids_validity(*m_model); -#endif /* _DEBUG */ - } - - // keeps relative position of multivolume selections - if (!from_same_object) - { - for (ModelVolume* v : volumes) - { - v->set_offset((v->get_offset() - total_bb.center()) + dst_matrix.inverse() * (Vec3d(dst_instance_bb.max(0), dst_instance_bb.min(1), dst_instance_bb.min(2)) + 0.5 * total_bb.size() - dst_instance->get_transformation().get_offset())); - } - } - - wxGetApp().obj_list()->paste_volumes_into_list(dst_obj_idx, volumes); - } - -#ifdef _DEBUG - check_model_ids_validity(*m_model); -#endif /* _DEBUG */ -} - -void Selection::paste_objects_from_clipboard() -{ -#ifdef _DEBUG - check_model_ids_validity(*m_model); -#endif /* _DEBUG */ - - std::vector object_idxs; - const ModelObjectPtrs& src_objects = m_clipboard.get_objects(); - for (const ModelObject* src_object : src_objects) - { - ModelObject* dst_object = m_model->add_object(*src_object); - double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); - Vec3d displacement(offset, offset, 0.0); - for (ModelInstance* inst : dst_object->instances) - { - inst->set_offset(inst->get_offset() + displacement); - } - - object_idxs.push_back(m_model->objects.size() - 1); -#ifdef _DEBUG - check_model_ids_validity(*m_model); -#endif /* _DEBUG */ - } - - wxGetApp().obj_list()->paste_objects_into_list(object_idxs); - -#ifdef _DEBUG - check_model_ids_validity(*m_model); -#endif /* _DEBUG */ -} - -} // namespace GUI -} // namespace Slic3r +#include "libslic3r/libslic3r.h" +#include "Selection.hpp" + +#include "3DScene.hpp" +#include "GLCanvas3D.hpp" +#include "GUI_App.hpp" +#include "GUI.hpp" +#include "GUI_ObjectManipulation.hpp" +#include "GUI_ObjectList.hpp" +#include "Gizmos/GLGizmoBase.hpp" +#include "Camera.hpp" +#include "Plater.hpp" +#include "slic3r/Utils/UndoRedo.hpp" + +#include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/BuildVolume.hpp" + +#include + +#include +#include + +static const Slic3r::ColorRGBA UNIFORM_SCALE_COLOR = Slic3r::ColorRGBA::ORANGE(); +static const Slic3r::ColorRGBA SOLID_PLANE_COLOR = Slic3r::ColorRGBA::ORANGE(); +static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5f }; + +namespace Slic3r { +namespace GUI { + +Selection::VolumeCache::TransformCache::TransformCache() + : position(Vec3d::Zero()) + , rotation(Vec3d::Zero()) + , scaling_factor(Vec3d::Ones()) + , mirror(Vec3d::Ones()) + , rotation_matrix(Transform3d::Identity()) + , scale_matrix(Transform3d::Identity()) + , mirror_matrix(Transform3d::Identity()) + , full_matrix(Transform3d::Identity()) +{ +} + +Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) + : position(transform.get_offset()) + , rotation(transform.get_rotation()) + , scaling_factor(transform.get_scaling_factor()) + , mirror(transform.get_mirror()) + , full_matrix(transform.get_matrix()) +{ + rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); + scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); + mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); +} + +Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) + : m_volume(volume_transform) + , m_instance(instance_transform) +{ +} + +bool Selection::Clipboard::is_sla_compliant() const +{ + if (m_mode == Selection::Volume) + return false; + + for (const ModelObject* o : m_model->objects) { + if (o->is_multiparts()) + return false; + + for (const ModelVolume* v : o->volumes) { + if (v->is_modifier()) + return false; + } + } + + return true; +} + +Selection::Clipboard::Clipboard() +{ + m_model.reset(new Model); +} + +void Selection::Clipboard::reset() +{ + m_model->clear_objects(); +} + +bool Selection::Clipboard::is_empty() const +{ + return m_model->objects.empty(); +} + +ModelObject* Selection::Clipboard::add_object() +{ + return m_model->add_object(); +} + +ModelObject* Selection::Clipboard::get_object(unsigned int id) +{ + return (id < (unsigned int)m_model->objects.size()) ? m_model->objects[id] : nullptr; +} + +const ModelObjectPtrs& Selection::Clipboard::get_objects() const +{ + return m_model->objects; +} + +Selection::Selection() + : m_volumes(nullptr) + , m_model(nullptr) + , m_enabled(false) + , m_mode(Instance) + , m_type(Empty) + , m_valid(false) + , m_scale_factor(1.0f) +{ + this->set_bounding_boxes_dirty(); +} + + +void Selection::set_volumes(GLVolumePtrs* volumes) +{ + m_volumes = volumes; + update_valid(); +} + +// Init shall be called from the OpenGL render function, so that the OpenGL context is initialized! +bool Selection::init() +{ + m_arrow.init_from(straight_arrow(10.0f, 5.0f, 5.0f, 10.0f, 1.0f)); + m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f)); +#if ENABLE_RENDER_SELECTION_CENTER + m_vbo_sphere.init_from(its_make_sphere(0.75, PI / 12.0)); +#endif // ENABLE_RENDER_SELECTION_CENTER + + return true; +} + +void Selection::set_model(Model* model) +{ + m_model = model; + update_valid(); +} + +void Selection::add(unsigned int volume_idx, bool as_single_selection, bool check_for_already_contained) +{ + if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) + return; + + const GLVolume* volume = (*m_volumes)[volume_idx]; + // wipe tower is already selected + if (is_wipe_tower() && volume->is_wipe_tower) + return; + + bool keep_instance_mode = (m_mode == Instance) && !as_single_selection; + bool already_contained = check_for_already_contained && contains_volume(volume_idx); + + // resets the current list if needed + bool needs_reset = as_single_selection && !already_contained; + needs_reset |= volume->is_wipe_tower; + needs_reset |= is_wipe_tower() && !volume->is_wipe_tower; + needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier; + needs_reset |= is_any_modifier() && !volume->is_modifier; + + if (!already_contained || needs_reset) { + wxGetApp().plater()->take_snapshot(_L("Selection-Add"), UndoRedo::SnapshotType::Selection); + + if (needs_reset) + clear(); + + if (!keep_instance_mode) + m_mode = volume->is_modifier ? Volume : Instance; + } + else + // keep current mode + return; + + switch (m_mode) + { + case Volume: + { + if (volume->volume_idx() >= 0 && (is_empty() || volume->instance_idx() == get_instance_idx())) + do_add_volume(volume_idx); + + break; + } + case Instance: + { + Plater::SuppressSnapshots suppress(wxGetApp().plater()); + add_instance(volume->object_idx(), volume->instance_idx(), as_single_selection); + break; + } + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove(unsigned int volume_idx) +{ + if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) + return; + + if (!contains_volume(volume_idx)) + return; + + wxGetApp().plater()->take_snapshot(_L("Selection-Remove"), UndoRedo::SnapshotType::Selection); + + GLVolume* volume = (*m_volumes)[volume_idx]; + + switch (m_mode) + { + case Volume: + { + do_remove_volume(volume_idx); + break; + } + case Instance: + { + do_remove_instance(volume->object_idx(), volume->instance_idx()); + break; + } + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::add_object(unsigned int object_idx, bool as_single_selection) +{ + if (!m_valid) + return; + + std::vector volume_idxs = get_volume_idxs_from_object(object_idx); + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + wxGetApp().plater()->take_snapshot(_L("Selection-Add Object"), UndoRedo::SnapshotType::Selection); + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = Instance; + + do_add_volumes(volume_idxs); + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove_object(unsigned int object_idx) +{ + if (!m_valid) + return; + + wxGetApp().plater()->take_snapshot(_L("Selection-Remove Object"), UndoRedo::SnapshotType::Selection); + + do_remove_object(object_idx); + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, bool as_single_selection) +{ + if (!m_valid) + return; + + const std::vector volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx); + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + wxGetApp().plater()->take_snapshot(_L("Selection-Add Instance"), UndoRedo::SnapshotType::Selection); + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = Instance; + + do_add_volumes(volume_idxs); + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove_instance(unsigned int object_idx, unsigned int instance_idx) +{ + if (!m_valid) + return; + + wxGetApp().plater()->take_snapshot(_L("Selection-Remove Instance"), UndoRedo::SnapshotType::Selection); + + do_remove_instance(object_idx, instance_idx); + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection) +{ + if (!m_valid) + return; + + std::vector volume_idxs = get_volume_idxs_from_volume(object_idx, instance_idx, volume_idx); + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = Volume; + + do_add_volumes(volume_idxs); + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) +{ + if (!m_valid) + return; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) + do_remove_volume(i); + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::add_volumes(EMode mode, const std::vector& volume_idxs, bool as_single_selection) +{ + if (!m_valid) + return; + + if ((!as_single_selection && contains_all_volumes(volume_idxs)) || + (as_single_selection && matches(volume_idxs))) + return; + + // resets the current list if needed + if (as_single_selection) + clear(); + + m_mode = mode; + for (unsigned int i : volume_idxs) { + if (i < (unsigned int)m_volumes->size()) + do_add_volume(i); + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove_volumes(EMode mode, const std::vector& volume_idxs) +{ + if (!m_valid) + return; + + m_mode = mode; + for (unsigned int i : volume_idxs) { + if (i < (unsigned int)m_volumes->size()) + do_remove_volume(i); + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::add_all() +{ + if (!m_valid) + return; + + unsigned int count = 0; + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + if (!(*m_volumes)[i]->is_wipe_tower) + ++count; + } + + if ((unsigned int)m_list.size() == count) + return; + + wxGetApp().plater()->take_snapshot(_(L("Selection-Add All")), UndoRedo::SnapshotType::Selection); + + m_mode = Instance; + clear(); + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + if (!(*m_volumes)[i]->is_wipe_tower) + do_add_volume(i); + } + + update_type(); + this->set_bounding_boxes_dirty(); +} + +void Selection::remove_all() +{ + if (!m_valid) + return; + + if (is_empty()) + return; + +// Not taking the snapshot with non-empty Redo stack will likely be more confusing than losing the Redo stack. +// Let's wait for user feedback. +// if (!wxGetApp().plater()->can_redo()) + wxGetApp().plater()->take_snapshot(_L("Selection-Remove All"), UndoRedo::SnapshotType::Selection); + + m_mode = Instance; + clear(); +} + +void Selection::set_deserialized(EMode mode, const std::vector> &volumes_and_instances) +{ + if (! m_valid) + return; + + m_mode = mode; + for (unsigned int i : m_list) + (*m_volumes)[i]->selected = false; + m_list.clear(); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) + if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), (*m_volumes)[i]->geometry_id)) + do_add_volume(i); + update_type(); + set_bounding_boxes_dirty(); +} + +void Selection::clear() +{ + if (!m_valid) + return; + + if (m_list.empty()) + return; + + // ensure that the volumes get the proper color before next call to render (expecially needed for transparent volumes) + for (unsigned int i : m_list) { + GLVolume& volume = *(*m_volumes)[i]; + volume.selected = false; + volume.set_render_color(volume.color.is_transparent()); + } + + m_list.clear(); + + update_type(); + set_bounding_boxes_dirty(); + + // this happens while the application is closing + if (wxGetApp().obj_manipul() == nullptr) + return; + + // resets the cache in the sidebar + wxGetApp().obj_manipul()->reset_cache(); + + // #et_FIXME fake KillFocus from sidebar + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); +} + +// Update the selection based on the new instance IDs. +void Selection::instances_changed(const std::vector &instance_ids_selected) +{ + assert(m_valid); + assert(m_mode == Instance); + m_list.clear(); + for (unsigned int volume_idx = 0; volume_idx < (unsigned int)m_volumes->size(); ++ volume_idx) { + const GLVolume *volume = (*m_volumes)[volume_idx]; + auto it = std::lower_bound(instance_ids_selected.begin(), instance_ids_selected.end(), volume->geometry_id.second); + if (it != instance_ids_selected.end() && *it == volume->geometry_id.second) + this->do_add_volume(volume_idx); + } + update_type(); + this->set_bounding_boxes_dirty(); +} + +// Update the selection based on the map from old indices to new indices after m_volumes changed. +// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances. +void Selection::volumes_changed(const std::vector &map_volume_old_to_new) +{ + assert(m_valid); + assert(m_mode == Volume); + IndicesList list_new; + for (unsigned int idx : m_list) + if (map_volume_old_to_new[idx] != size_t(-1)) { + unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx]; + (*m_volumes)[new_idx]->selected = true; + list_new.insert(new_idx); + } + m_list = std::move(list_new); + update_type(); + this->set_bounding_boxes_dirty(); +} + +bool Selection::is_single_full_instance() const +{ + if (m_type == SingleFullInstance) + return true; + + if (m_type == SingleFullObject) + return get_instance_idx() != -1; + + if (m_list.empty() || m_volumes->empty()) + return false; + + int object_idx = m_valid ? get_object_idx() : -1; + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) + return false; + + int instance_idx = (*m_volumes)[*m_list.begin()]->instance_idx(); + + std::set volumes_idxs; + for (unsigned int i : m_list) { + const GLVolume* v = (*m_volumes)[i]; + if (object_idx != v->object_idx() || instance_idx != v->instance_idx()) + return false; + + int volume_idx = v->volume_idx(); + if (volume_idx >= 0) + volumes_idxs.insert(volume_idx); + } + + return m_model->objects[object_idx]->volumes.size() == volumes_idxs.size(); +} + +bool Selection::is_from_single_object() const +{ + const int idx = get_object_idx(); +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + return 0 <= idx && idx < int(m_model->objects.size()); +#else + return 0 <= idx && idx < 1000; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +} + +bool Selection::is_sla_compliant() const +{ + if (m_mode == Volume) + return false; + + for (unsigned int i : m_list) { + if ((*m_volumes)[i]->is_modifier) + return false; + } + + return true; +} + +bool Selection::contains_all_volumes(const std::vector& volume_idxs) const +{ + for (unsigned int i : volume_idxs) { + if (m_list.find(i) == m_list.end()) + return false; + } + + return true; +} + +bool Selection::contains_any_volume(const std::vector& volume_idxs) const +{ + for (unsigned int i : volume_idxs) { + if (m_list.find(i) != m_list.end()) + return true; + } + + return false; +} + +bool Selection::matches(const std::vector& volume_idxs) const +{ + unsigned int count = 0; + + for (unsigned int i : volume_idxs) { + if (m_list.find(i) != m_list.end()) + ++count; + else + return false; + } + + return count == (unsigned int)m_list.size(); +} + +bool Selection::requires_uniform_scale() const +{ + if (is_single_full_instance() || is_single_modifier() || is_single_volume()) + return false; + + return true; +} + +int Selection::get_object_idx() const +{ + return (m_cache.content.size() == 1) ? m_cache.content.begin()->first : -1; +} + +int Selection::get_instance_idx() const +{ + if (m_cache.content.size() == 1) { + const InstanceIdxsList& idxs = m_cache.content.begin()->second; + if (idxs.size() == 1) + return *idxs.begin(); + } + + return -1; +} + +const Selection::InstanceIdxsList& Selection::get_instance_idxs() const +{ + assert(m_cache.content.size() == 1); + return m_cache.content.begin()->second; +} + +const GLVolume* Selection::get_volume(unsigned int volume_idx) const +{ + return (m_valid && (volume_idx < (unsigned int)m_volumes->size())) ? (*m_volumes)[volume_idx] : nullptr; +} + +const BoundingBoxf3& Selection::get_bounding_box() const +{ + if (!m_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + (*bbox)->merge((*m_volumes)[i]->transformed_convex_hull_bounding_box()); + } + } + } + return *m_bounding_box; +} + +const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const +{ + if (!m_unscaled_instance_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_unscaled_instance_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + if (volume.is_modifier) + continue; + Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_unscaled_instance_bounding_box; +} + +const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const +{ + if (!m_scaled_instance_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_scaled_instance_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + if (volume.is_modifier) + continue; + Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_scaled_instance_bounding_box; +} + +void Selection::setup_cache() +{ + if (!m_valid) + return; + + set_caches(); +} + +void Selection::translate(const Vec3d& displacement, bool local) +{ + if (!m_valid) + return; + + EMode translation_type = m_mode; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + if (m_mode == Volume || v.is_wipe_tower) { + if (local) + v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); + else { + const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); + } + } + else if (m_mode == Instance) { + if (is_from_fully_selected_instance(i)) + v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); + else { + const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); + translation_type = Volume; + } + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (translation_type == Instance) + synchronize_unselected_instances(SYNC_ROTATION_NONE); + else if (translation_type == Volume) + synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + ensure_not_below_bed(); + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} + +// Rotate an object around one of the axes. Only one rotation component is expected to be changing. +void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) +{ + if (!m_valid) + return; + + // Only relative rotation values are allowed in the world coordinate system. + assert(!transformation_type.world() || transformation_type.relative()); + + if (!is_wipe_tower()) { + int rot_axis_max = 0; + if (rotation.isApprox(Vec3d::Zero())) { + for (unsigned int i : m_list) { + GLVolume &v = *(*m_volumes)[i]; + if (m_mode == Instance) { + v.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation()); + v.set_instance_offset(m_cache.volumes_data[i].get_instance_position()); + } + else if (m_mode == Volume) { + v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation()); + v.set_volume_offset(m_cache.volumes_data[i].get_volume_position()); + } + } + } + else { // this is not the wipe tower + //FIXME this does not work for absolute rotations (transformation_type.absolute() is true) + rotation.cwiseAbs().maxCoeff(&rot_axis_max); + +// if ( single instance or single volume ) + // Rotate around center , if only a single object or volume +// transformation_type.set_independent(); + + // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. + std::vector object_instance_first(m_model->objects.size(), -1); + auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { + const int first_volume_idx = object_instance_first[volume.object_idx()]; + if (rot_axis_max != 2 && first_volume_idx != -1) { + // Generic rotation, but no rotation around the Z axis. + // Always do a local rotation (do not consider the selection to be a rigid body). + assert(is_approx(rotation.z(), 0.0)); + const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; + const Vec3d &rotation = first_volume.get_instance_rotation(); + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); + volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + } + else { + // extracts rotations from the composed transformation + Vec3d new_rotation = transformation_type.world() ? + Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : + transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); + if (rot_axis_max == 2 && transformation_type.joint()) { + // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); + volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + } + else if (!(m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center).isApprox(Vec3d::Zero())) + volume.set_instance_offset(m_cache.dragging_center + Geometry::assemble_transform(Vec3d::Zero(), new_rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix().inverse() * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + + volume.set_instance_rotation(new_rotation); + object_instance_first[volume.object_idx()] = i; + } + }; + + for (unsigned int i : m_list) { + GLVolume &v = *(*m_volumes)[i]; + if (is_single_full_instance()) + rotate_instance(v, i); + else if (is_single_volume() || is_single_modifier()) { + if (transformation_type.independent()) + v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); + else { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + v.set_volume_rotation(new_rotation); + } + } + else + { + if (m_mode == Instance) + rotate_instance(v, i); + else if (m_mode == Volume) { + // extracts rotations from the composed transformation + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + if (transformation_type.joint()) { + const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; + const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); + v.set_volume_offset(local_pivot + offset); + } + v.set_volume_rotation(new_rotation); + } + } + } + } + + #if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); + else if (m_mode == Volume) + synchronize_unselected_volumes(); + #endif // !DISABLE_INSTANCES_SYNCH + } + else { // it's the wipe tower that's selected and being rotated + GLVolume& volume = *((*m_volumes)[*m_list.begin()]); // the wipe tower is always alone in the selection + + // make sure the wipe tower rotates around its center, not origin + // we can assume that only Z rotation changes + const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); + const Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0.0, 0.0, 1.0)) * center_local; + volume.set_volume_rotation(rotation); + volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); + } + + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} + +void Selection::flattening_rotate(const Vec3d& normal) +{ + // We get the normal in untransformed coordinates. We must transform it using the instance matrix, find out + // how to rotate the instance so it faces downwards and do the rotation. All that for all selected instances. + // The function assumes that is_from_single_object() holds. + assert(Slic3r::is_approx(normal.norm(), 1.)); + + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + // Normal transformed from the object coordinate space to the world coordinate space. + const auto &voldata = m_cache.volumes_data[i]; + Vec3d tnormal = (Geometry::assemble_transform( + Vec3d::Zero(), voldata.get_instance_rotation(), + voldata.get_instance_scaling_factor().cwiseInverse(), voldata.get_instance_mirror()) * normal).normalized(); + // Additional rotation to align tnormal with the down vector in the world coordinate space. + auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, - Vec3d::UnitZ()); + v.set_instance_rotation(Geometry::extract_euler_angles(extra_rotation.toRotationMatrix() * m_cache.volumes_data[i].get_instance_rotation_matrix())); + } + +#if !DISABLE_INSTANCES_SYNCH + // Apply the same transformation also to other instances, + // but respect their possibly diffrent z-rotation. + if (m_mode == Instance) + synchronize_unselected_instances(SYNC_ROTATION_GENERAL); +#endif // !DISABLE_INSTANCES_SYNCH + + this->set_bounding_boxes_dirty(); +} + +void Selection::scale(const Vec3d& scale, TransformationType transformation_type) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume &v = *(*m_volumes)[i]; + if (is_single_full_instance()) { + if (transformation_type.relative()) { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); + Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); + // extracts scaling factors from the composed transformation + Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + if (transformation_type.joint()) + v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + + v.set_instance_scaling_factor(new_scale); + } + else { + if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { + // Non-uniform scaling. Transform the scaling factors into the local coordinate system. + // This is only possible, if the instance rotation is mulitples of ninety degrees. + assert(Geometry::is_rotation_ninety_degrees(v.get_instance_rotation())); + v.set_instance_scaling_factor((v.get_instance_transformation().get_matrix(true, false, true, true).matrix().block<3, 3>(0, 0).transpose() * scale).cwiseAbs()); + } + else + v.set_instance_scaling_factor(scale); + } + } + else if (is_single_volume() || is_single_modifier()) + v.set_volume_scaling_factor(scale); + else { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); + if (m_mode == Instance) { + Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); + // extracts scaling factors from the composed transformation + Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + if (transformation_type.joint()) + v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + + v.set_instance_scaling_factor(new_scale); + } + else if (m_mode == Volume) { + Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); + // extracts scaling factors from the composed transformation + Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + if (transformation_type.joint()) { + Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); + v.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); + } + v.set_volume_scaling_factor(new_scale); + } + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + synchronize_unselected_instances(SYNC_ROTATION_NONE); + else if (m_mode == Volume) + synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + ensure_on_bed(); + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} + +void Selection::scale_to_fit_print_volume(const BuildVolume& volume) +{ + auto fit = [this](double s, Vec3d offset) { + if (s <= 0.0 || s == 1.0) + return; + + wxGetApp().plater()->take_snapshot(_L("Scale To Fit")); + + TransformationType type; + type.set_world(); + type.set_relative(); + type.set_joint(); + + // apply scale + setup_cache(); + scale(s * Vec3d::Ones(), type); + wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot + + // center selection on print bed + setup_cache(); + offset.z() = -get_bounding_box().min.z(); + translate(offset); + wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot + + wxGetApp().obj_manipul()->set_dirty(); + }; + + auto fit_rectangle = [this, fit](const BuildVolume& volume) { + const BoundingBoxf3 print_volume = volume.bounding_volume(); + const Vec3d print_volume_size = print_volume.size(); + + // adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings + const Vec3d box_size = get_bounding_box().size() + 0.02 * Vec3d::Ones(); + + const double sx = (box_size.x() != 0.0) ? print_volume_size.x() / box_size.x() : 0.0; + const double sy = (box_size.y() != 0.0) ? print_volume_size.y() / box_size.y() : 0.0; + const double sz = (box_size.z() != 0.0) ? print_volume_size.z() / box_size.z() : 0.0; + + if (sx != 0.0 && sy != 0.0 && sz != 0.0) + fit(std::min(sx, std::min(sy, sz)), print_volume.center() - get_bounding_box().center()); + }; + + auto fit_circle = [this, fit](const BuildVolume& volume) { + const Geometry::Circled& print_circle = volume.circle(); + double print_circle_radius = unscale(print_circle.radius); + + if (print_circle_radius == 0.0) + return; + + Points points; + double max_z = 0.0; + for (unsigned int i : m_list) { + const GLVolume& v = *(*m_volumes)[i]; + TriangleMesh hull_3d = *v.convex_hull(); + hull_3d.transform(v.world_matrix()); + max_z = std::max(max_z, hull_3d.bounding_box().size().z()); + const Polygon hull_2d = hull_3d.convex_hull(); + points.insert(points.end(), hull_2d.begin(), hull_2d.end()); + } + + if (points.empty()) + return; + + const Geometry::Circled circle = Geometry::smallest_enclosing_circle_welzl(points); + // adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings + const double circle_radius = unscale(circle.radius) + 0.01; + + if (circle_radius == 0.0 || max_z == 0.0) + return; + + const double s = std::min(print_circle_radius / circle_radius, volume.max_print_height() / max_z); + const Vec3d sel_center = get_bounding_box().center(); + const Vec3d offset = s * (Vec3d(unscale(circle.center.x()), unscale(circle.center.y()), 0.5 * max_z) - sel_center); + const Vec3d print_center = { unscale(print_circle.center.x()), unscale(print_circle.center.y()), 0.5 * volume.max_print_height() }; + fit(s, print_center - (sel_center + offset)); + }; + + if (is_empty() || m_mode == Volume) + return; + + switch (volume.type()) + { + case BuildVolume::Type::Rectangle: { fit_rectangle(volume); break; } + case BuildVolume::Type::Circle: { fit_circle(volume); break; } + default: { break; } + } +} + +void Selection::mirror(Axis axis) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + if (is_single_full_instance()) + v.set_instance_mirror(axis, -v.get_instance_mirror(axis)); + else if (m_mode == Volume) + v.set_volume_mirror(axis, -v.get_volume_mirror(axis)); + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + synchronize_unselected_instances(SYNC_ROTATION_NONE); + else if (m_mode == Volume) + synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + set_bounding_boxes_dirty(); +} + +void Selection::translate(unsigned int object_idx, const Vec3d& displacement) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + if (v.object_idx() == (int)object_idx) + v.set_instance_offset(v.get_instance_offset() + displacement); + } + + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) { + if (done.size() == m_volumes->size()) + break; + +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if ((*m_volumes)[i]->is_wipe_tower) + continue; + + int object_idx = (*m_volumes)[i]->object_idx(); +#else + int object_idx = (*m_volumes)[i]->object_idx(); + if (object_idx >= 1000) + continue; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + + // Process unselected volumes of the object. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume& v = *(*m_volumes)[j]; + if (v.object_idx() != object_idx) + continue; + + v.set_instance_offset(v.get_instance_offset() + displacement); + done.insert(j); + } + } + + this->set_bounding_boxes_dirty(); +} + +void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + if (v.object_idx() == (int)object_idx && v.instance_idx() == (int)instance_idx) + v.set_instance_offset(v.get_instance_offset() + displacement); + } + + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) { + if (done.size() == m_volumes->size()) + break; + +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if ((*m_volumes)[i]->is_wipe_tower) + continue; + + int object_idx = (*m_volumes)[i]->object_idx(); +#else + int object_idx = (*m_volumes)[i]->object_idx(); + if (object_idx >= 1000) + continue; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + + // Process unselected volumes of the object. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume& v = *(*m_volumes)[j]; + if (v.object_idx() != object_idx || v.instance_idx() != (int)instance_idx) + continue; + + v.set_instance_offset(v.get_instance_offset() + displacement); + done.insert(j); + } + } + + this->set_bounding_boxes_dirty(); +} + +void Selection::erase() +{ + if (!m_valid) + return; + + if (is_single_full_object()) + wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itObject, get_object_idx(), 0); + else if (is_multiple_full_object()) { + std::vector items; + items.reserve(m_cache.content.size()); + for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) { + items.emplace_back(ItemType::itObject, it->first, 0); + } + wxGetApp().obj_list()->delete_from_model_and_list(items); + } + else if (is_multiple_full_instance()) { + std::set> instances_idxs; + for (ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.begin(); obj_it != m_cache.content.end(); ++obj_it) { + for (InstanceIdxsList::reverse_iterator inst_it = obj_it->second.rbegin(); inst_it != obj_it->second.rend(); ++inst_it) { + instances_idxs.insert(std::make_pair(obj_it->first, *inst_it)); + } + } + + std::vector items; + items.reserve(instances_idxs.size()); + for (const std::pair& i : instances_idxs) { + items.emplace_back(ItemType::itInstance, i.first, i.second); + } + wxGetApp().obj_list()->delete_from_model_and_list(items); + } + else if (is_single_full_instance()) + wxGetApp().obj_list()->delete_from_model_and_list(ItemType::itInstance, get_object_idx(), get_instance_idx()); + else if (is_mixed()) { + std::set items_set; + std::map volumes_in_obj; + + for (auto i : m_list) { + const auto gl_vol = (*m_volumes)[i]; + const auto glv_obj_idx = gl_vol->object_idx(); + const auto model_object = m_model->objects[glv_obj_idx]; + + if (model_object->instances.size() == 1) { + if (model_object->volumes.size() == 1) + items_set.insert(ItemForDelete(ItemType::itObject, glv_obj_idx, -1)); + else { + items_set.insert(ItemForDelete(ItemType::itVolume, glv_obj_idx, gl_vol->volume_idx())); + int idx = (volumes_in_obj.find(glv_obj_idx) == volumes_in_obj.end()) ? 0 : volumes_in_obj.at(glv_obj_idx); + volumes_in_obj[glv_obj_idx] = ++idx; + } + continue; + } + + const auto glv_ins_idx = gl_vol->instance_idx(); + + for (auto obj_ins : m_cache.content) { + if (obj_ins.first == glv_obj_idx) { + if (obj_ins.second.find(glv_ins_idx) != obj_ins.second.end()) { + if (obj_ins.second.size() == model_object->instances.size()) + items_set.insert(ItemForDelete(ItemType::itObject, glv_obj_idx, -1)); + else + items_set.insert(ItemForDelete(ItemType::itInstance, glv_obj_idx, glv_ins_idx)); + + break; + } + } + } + } + + std::vector items; + items.reserve(items_set.size()); + for (const ItemForDelete& i : items_set) { + if (i.type == ItemType::itVolume) { + const int vol_in_obj_cnt = volumes_in_obj.find(i.obj_idx) == volumes_in_obj.end() ? 0 : volumes_in_obj.at(i.obj_idx); + if (vol_in_obj_cnt == (int)m_model->objects[i.obj_idx]->volumes.size()) { + if (i.sub_obj_idx == vol_in_obj_cnt - 1) + items.emplace_back(ItemType::itObject, i.obj_idx, 0); + continue; + } + } + items.emplace_back(i.type, i.obj_idx, i.sub_obj_idx); + } + + wxGetApp().obj_list()->delete_from_model_and_list(items); + } + else { + std::set> volumes_idxs; + for (unsigned int i : m_list) { + const GLVolume* v = (*m_volumes)[i]; + // Only remove volumes associated with ModelVolumes from the object list. + // Temporary meshes (SLA supports or pads) are not managed by the object list. + if (v->volume_idx() >= 0) + volumes_idxs.insert(std::make_pair(v->object_idx(), v->volume_idx())); + } + + std::vector items; + items.reserve(volumes_idxs.size()); + for (const std::pair& v : volumes_idxs) { + items.emplace_back(ItemType::itVolume, v.first, v.second); + } + + wxGetApp().obj_list()->delete_from_model_and_list(items); + ensure_not_below_bed(); + } +} + +void Selection::render(float scale_factor) +{ + if (!m_valid || is_empty()) + return; + + m_scale_factor = scale_factor; + // render cumulative bounding box of selected volumes +#if ENABLE_LEGACY_OPENGL_REMOVAL + render_bounding_box(get_bounding_box(), ColorRGB::WHITE()); +#else + render_selected_volumes(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + render_synchronized_volumes(); +} + +#if ENABLE_RENDER_SELECTION_CENTER +void Selection::render_center(bool gizmo_is_dragging) +{ + if (!m_valid || is_empty()) + return; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + const Vec3d center = gizmo_is_dragging ? m_cache.dragging_center : get_bounding_box().center(); + + glsafe(::glDisable(GL_DEPTH_TEST)); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform(center); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslated(center.x(), center.y(), center.z())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_vbo_sphere.set_color(ColorRGBA::WHITE()); +#else + m_vbo_sphere.set_color(-1, ColorRGBA::WHITE()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + m_vbo_sphere.render(); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} +#endif // ENABLE_RENDER_SELECTION_CENTER + +void Selection::render_sidebar_hints(const std::string& sidebar_field) +{ + if (sidebar_field.empty()) + return; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat" : "gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); +#else + GLShaderProgram* shader = nullptr; + + if (!boost::starts_with(sidebar_field, "layer")) { + shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glEnable(GL_DEPTH_TEST)); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d base_matrix = Geometry::assemble_transform(get_bounding_box().center()); + Transform3d orient_matrix = Transform3d::Identity(); +#else + glsafe(::glPushMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (!boost::starts_with(sidebar_field, "layer")) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + shader->set_uniform("emission_factor", 0.05f); +#else + const Vec3d& center = get_bounding_box().center(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glTranslated(center.x(), center.y(), center.z())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + if (!boost::starts_with(sidebar_field, "position")) { +#if !ENABLE_GL_SHADERS_ATTRIBUTES + Transform3d orient_matrix = Transform3d::Identity(); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + if (boost::starts_with(sidebar_field, "scale")) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + else if (boost::starts_with(sidebar_field, "rotation")) { + if (boost::ends_with(sidebar_field, "x")) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + else if (boost::ends_with(sidebar_field, "y")) { + const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); + if (rotation.x() == 0.0) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + else + orient_matrix.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ())); + } + } +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glMultMatrixd(orient_matrix.data())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + } + else if (is_single_volume() || is_single_modifier()) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#else + glsafe(::glTranslated(center.x(), center.y(), center.z())); + Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + if (!boost::starts_with(sidebar_field, "position")) + orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glMultMatrixd(orient_matrix.data())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + else { +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (requires_local_axes()) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#else + glsafe(::glTranslated(center.x(), center.y(), center.z())); + if (requires_local_axes()) { + const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + glsafe(::glMultMatrixd(orient_matrix.data())); + } +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (!boost::starts_with(sidebar_field, "layer")) + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (boost::starts_with(sidebar_field, "position")) + render_sidebar_position_hints(sidebar_field, *shader, base_matrix * orient_matrix); + else if (boost::starts_with(sidebar_field, "rotation")) + render_sidebar_rotation_hints(sidebar_field, *shader, base_matrix * orient_matrix); + else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) + render_sidebar_scale_hints(sidebar_field, *shader, base_matrix * orient_matrix); + else if (boost::starts_with(sidebar_field, "layer")) + render_sidebar_layers_hints(sidebar_field, *shader); +#else + if (boost::starts_with(sidebar_field, "position")) + render_sidebar_position_hints(sidebar_field); + else if (boost::starts_with(sidebar_field, "rotation")) + render_sidebar_rotation_hints(sidebar_field); + else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size")) + render_sidebar_scale_hints(sidebar_field); + else if (boost::starts_with(sidebar_field, "layer")) + render_sidebar_layers_hints(sidebar_field); + + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + if (!boost::starts_with(sidebar_field, "layer")) +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); +} + +bool Selection::requires_local_axes() const +{ + return m_mode == Volume && is_from_single_instance(); +} + +void Selection::copy_to_clipboard() +{ + if (!m_valid) + return; + + m_clipboard.reset(); + + for (const ObjectIdxsToInstanceIdxsMap::value_type& object : m_cache.content) { + ModelObject* src_object = m_model->objects[object.first]; + ModelObject* dst_object = m_clipboard.add_object(); + dst_object->name = src_object->name; + dst_object->input_file = src_object->input_file; + dst_object->config.assign_config(src_object->config); + dst_object->sla_support_points = src_object->sla_support_points; + dst_object->sla_points_status = src_object->sla_points_status; + dst_object->sla_drain_holes = src_object->sla_drain_holes; + dst_object->layer_config_ranges = src_object->layer_config_ranges; // #ys_FIXME_experiment + dst_object->layer_height_profile.assign(src_object->layer_height_profile); + dst_object->origin_translation = src_object->origin_translation; + + for (int i : object.second) { + dst_object->add_instance(*src_object->instances[i]); + } + + for (unsigned int i : m_list) { + // Copy the ModelVolumes only for the selected GLVolumes of the 1st selected instance. + const GLVolume* volume = (*m_volumes)[i]; + if (volume->object_idx() == object.first && volume->instance_idx() == *object.second.begin()) { + int volume_idx = volume->volume_idx(); + if (0 <= volume_idx && volume_idx < (int)src_object->volumes.size()) { + ModelVolume* src_volume = src_object->volumes[volume_idx]; + ModelVolume* dst_volume = dst_object->add_volume(*src_volume); + dst_volume->set_new_unique_id(); + } + else + assert(false); + } + } + } + + m_clipboard.set_mode(m_mode); +} + +void Selection::paste_from_clipboard() +{ + if (!m_valid || m_clipboard.is_empty()) + return; + + switch (m_clipboard.get_mode()) + { + case Volume: + { + if (is_from_single_instance()) + paste_volumes_from_clipboard(); + + break; + } + case Instance: + { + if (m_mode == Instance) + paste_objects_from_clipboard(); + + break; + } + } +} + +std::vector Selection::get_volume_idxs_from_object(unsigned int object_idx) const +{ + std::vector idxs; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + if ((*m_volumes)[i]->object_idx() == (int)object_idx) + idxs.push_back(i); + } + + return idxs; +} + +std::vector Selection::get_volume_idxs_from_instance(unsigned int object_idx, unsigned int instance_idx) const +{ + std::vector idxs; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + const GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == (int)object_idx) && (v->instance_idx() == (int)instance_idx)) + idxs.push_back(i); + } + + return idxs; +} + +std::vector Selection::get_volume_idxs_from_volume(unsigned int object_idx, unsigned int instance_idx, unsigned int volume_idx) const +{ + std::vector idxs; + + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) + { + const GLVolume* v = (*m_volumes)[i]; + if ((v->object_idx() == (int)object_idx) && (v->volume_idx() == (int)volume_idx)) + { + if (((int)instance_idx != -1) && (v->instance_idx() == (int)instance_idx)) + idxs.push_back(i); + } + } + + return idxs; +} + +std::vector Selection::get_missing_volume_idxs_from(const std::vector& volume_idxs) const +{ + std::vector idxs; + + for (unsigned int i : m_list) + { + std::vector::const_iterator it = std::find(volume_idxs.begin(), volume_idxs.end(), i); + if (it == volume_idxs.end()) + idxs.push_back(i); + } + + return idxs; +} + +std::vector Selection::get_unselected_volume_idxs_from(const std::vector& volume_idxs) const +{ + std::vector idxs; + + for (unsigned int i : volume_idxs) + { + if (m_list.find(i) == m_list.end()) + idxs.push_back(i); + } + + return idxs; +} + +void Selection::update_valid() +{ + m_valid = (m_volumes != nullptr) && (m_model != nullptr); +} + +void Selection::update_type() +{ + m_cache.content.clear(); + m_type = Mixed; + + for (unsigned int i : m_list) + { + const GLVolume* volume = (*m_volumes)[i]; + int obj_idx = volume->object_idx(); + int inst_idx = volume->instance_idx(); + ObjectIdxsToInstanceIdxsMap::iterator obj_it = m_cache.content.find(obj_idx); + if (obj_it == m_cache.content.end()) + obj_it = m_cache.content.insert(ObjectIdxsToInstanceIdxsMap::value_type(obj_idx, InstanceIdxsList())).first; + + obj_it->second.insert(inst_idx); + } + + bool requires_disable = false; + + if (!m_valid) + m_type = Invalid; + else + { + if (m_list.empty()) + m_type = Empty; + else if (m_list.size() == 1) + { + const GLVolume* first = (*m_volumes)[*m_list.begin()]; + if (first->is_wipe_tower) + m_type = WipeTower; + else if (first->is_modifier) + { + m_type = SingleModifier; + requires_disable = true; + } + else + { + const ModelObject* model_object = m_model->objects[first->object_idx()]; + unsigned int volumes_count = (unsigned int)model_object->volumes.size(); + unsigned int instances_count = (unsigned int)model_object->instances.size(); + if (volumes_count * instances_count == 1) + { + m_type = SingleFullObject; + // ensures the correct mode is selected + m_mode = Instance; + } + else if (volumes_count == 1) // instances_count > 1 + { + m_type = SingleFullInstance; + // ensures the correct mode is selected + m_mode = Instance; + } + else + { + m_type = SingleVolume; + requires_disable = true; + } + } + } + else + { + unsigned int sla_volumes_count = 0; + // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is + for (unsigned int i : m_list) { + if ((*m_volumes)[i]->volume_idx() < 0) + ++sla_volumes_count; + } + + if (m_cache.content.size() == 1) // single object + { + const ModelObject* model_object = m_model->objects[m_cache.content.begin()->first]; + unsigned int model_volumes_count = (unsigned int)model_object->volumes.size(); + + unsigned int instances_count = (unsigned int)model_object->instances.size(); + unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); + if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) + { + m_type = SingleFullObject; + // ensures the correct mode is selected + m_mode = Instance; + } + else if (selected_instances_count == 1) + { + if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) + { + m_type = SingleFullInstance; + // ensures the correct mode is selected + m_mode = Instance; + } + else + { + unsigned int modifiers_count = 0; + for (unsigned int i : m_list) + { + if ((*m_volumes)[i]->is_modifier) + ++modifiers_count; + } + + if (modifiers_count == 0) + m_type = MultipleVolume; + else if (modifiers_count == (unsigned int)m_list.size()) + m_type = MultipleModifier; + + requires_disable = true; + } + } + else if ((selected_instances_count > 1) && (selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size())) + { + m_type = MultipleFullInstance; + // ensures the correct mode is selected + m_mode = Instance; + } + } + else + { + unsigned int sels_cntr = 0; + for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) + { + const ModelObject* model_object = m_model->objects[it->first]; + unsigned int volumes_count = (unsigned int)model_object->volumes.size(); + unsigned int instances_count = (unsigned int)model_object->instances.size(); + sels_cntr += volumes_count * instances_count; + } + if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) + { + m_type = MultipleFullObject; + // ensures the correct mode is selected + m_mode = Instance; + } + } + } + } + + int object_idx = get_object_idx(); + int instance_idx = get_instance_idx(); + for (GLVolume* v : *m_volumes) + { + v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false; + } + +#if ENABLE_SELECTION_DEBUG_OUTPUT + std::cout << "Selection: "; + std::cout << "mode: "; + switch (m_mode) + { + case Volume: + { + std::cout << "Volume"; + break; + } + case Instance: + { + std::cout << "Instance"; + break; + } + } + + std::cout << " - type: "; + + switch (m_type) + { + case Invalid: + { + std::cout << "Invalid" << std::endl; + break; + } + case Empty: + { + std::cout << "Empty" << std::endl; + break; + } + case WipeTower: + { + std::cout << "WipeTower" << std::endl; + break; + } + case SingleModifier: + { + std::cout << "SingleModifier" << std::endl; + break; + } + case MultipleModifier: + { + std::cout << "MultipleModifier" << std::endl; + break; + } + case SingleVolume: + { + std::cout << "SingleVolume" << std::endl; + break; + } + case MultipleVolume: + { + std::cout << "MultipleVolume" << std::endl; + break; + } + case SingleFullObject: + { + std::cout << "SingleFullObject" << std::endl; + break; + } + case MultipleFullObject: + { + std::cout << "MultipleFullObject" << std::endl; + break; + } + case SingleFullInstance: + { + std::cout << "SingleFullInstance" << std::endl; + break; + } + case MultipleFullInstance: + { + std::cout << "MultipleFullInstance" << std::endl; + break; + } + case Mixed: + { + std::cout << "Mixed" << std::endl; + break; + } + } +#endif // ENABLE_SELECTION_DEBUG_OUTPUT +} + +void Selection::set_caches() +{ + m_cache.volumes_data.clear(); + m_cache.sinking_volumes.clear(); + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + const GLVolume& v = *(*m_volumes)[i]; + m_cache.volumes_data.emplace(i, VolumeCache(v.get_volume_transformation(), v.get_instance_transformation())); + if (v.is_sinking()) + m_cache.sinking_volumes.push_back(i); + } + m_cache.dragging_center = get_bounding_box().center(); +} + +void Selection::do_add_volume(unsigned int volume_idx) +{ + m_list.insert(volume_idx); + GLVolume* v = (*m_volumes)[volume_idx]; + v->selected = true; + if (v->hover == GLVolume::HS_Select || v->hover == GLVolume::HS_Deselect) + v->hover = GLVolume::HS_Hover; +} + +void Selection::do_add_volumes(const std::vector& volume_idxs) +{ + for (unsigned int i : volume_idxs) + { + if (i < (unsigned int)m_volumes->size()) + do_add_volume(i); + } +} + +void Selection::do_remove_volume(unsigned int volume_idx) +{ + IndicesList::iterator v_it = m_list.find(volume_idx); + if (v_it == m_list.end()) + return; + + m_list.erase(v_it); + + (*m_volumes)[volume_idx]->selected = false; +} + +void Selection::do_remove_instance(unsigned int object_idx, unsigned int instance_idx) +{ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx) + do_remove_volume(i); + } +} + +void Selection::do_remove_object(unsigned int object_idx) +{ + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { + GLVolume* v = (*m_volumes)[i]; + if (v->object_idx() == (int)object_idx) + do_remove_volume(i); + } +} + +#if !ENABLE_LEGACY_OPENGL_REMOVAL +void Selection::render_selected_volumes() const +{ + float color[3] = { 1.0f, 1.0f, 1.0f }; + render_bounding_box(get_bounding_box(), color); +} +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +void Selection::render_synchronized_volumes() +{ + if (m_mode == Instance) + return; + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + float color[3] = { 1.0f, 1.0f, 0.0f }; +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + int object_idx = volume.object_idx(); + int volume_idx = volume.volume_idx(); + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (i == j) + continue; + + const GLVolume& v = *(*m_volumes)[j]; + if (v.object_idx() != object_idx || v.volume_idx() != volume_idx) + continue; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + render_bounding_box(v.transformed_convex_hull_bounding_box(), ColorRGB::YELLOW()); +#else + render_bounding_box(v.transformed_convex_hull_bounding_box(), color); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } +} + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void Selection::render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color) +{ +#else +void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) const +{ + if (color == nullptr) + return; + + const Vec3f b_min = box.min.cast(); + const Vec3f b_max = box.max.cast(); + const Vec3f size = 0.2f * box.size().cast(); + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glColor3fv(color)); + glsafe(::glLineWidth(2.0f * m_scale_factor)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const BoundingBoxf3& curr_box = m_box.get_bounding_box(); + if (!m_box.is_initialized() || !is_approx(box.min, curr_box.min) || !is_approx(box.max, curr_box.max)) { + m_box.reset(); + + const Vec3f b_min = box.min.cast(); + const Vec3f b_max = box.max.cast(); + const Vec3f size = 0.2f * box.size().cast(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(48); + init_data.reserve_indices(48); + + // vertices + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_min.z() + size.z())); + + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y() + size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_min.y(), b_max.z() - size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y() + size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_min.y(), b_max.z() - size.z())); + + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x() - size.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y() - size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_max.x(), b_max.y(), b_max.z() - size.z())); + + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x() + size.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y() - size.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z())); + init_data.add_vertex(Vec3f(b_min.x(), b_max.y(), b_max.z() - size.z())); + + // indices + for (unsigned int i = 0; i < 48; ++i) { + init_data.add_index(i); + } + + m_box.init_from(std::move(init_data)); + } + + glsafe(::glEnable(GL_DEPTH_TEST)); + + glsafe(::glLineWidth(2.0f * m_scale_factor)); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + 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()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_box.set_color(to_rgba(color)); + m_box.render(); + shader->stop_using(); +#else + ::glBegin(GL_LINES); + + ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_min(2)); + ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_min(2)); + ::glVertex3f(b_min(0), b_min(1), b_min(2)); ::glVertex3f(b_min(0), b_min(1), b_min(2) + size(2)); + + ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_min(2)); + ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_min(2)); + ::glVertex3f(b_max(0), b_min(1), b_min(2)); ::glVertex3f(b_max(0), b_min(1), b_min(2) + size(2)); + + ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_min(2)); + ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_min(2)); + ::glVertex3f(b_max(0), b_max(1), b_min(2)); ::glVertex3f(b_max(0), b_max(1), b_min(2) + size(2)); + + ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_min(2)); + ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_min(2)); + ::glVertex3f(b_min(0), b_max(1), b_min(2)); ::glVertex3f(b_min(0), b_max(1), b_min(2) + size(2)); + + ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_min(1), b_max(2)); + ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1) + size(1), b_max(2)); + ::glVertex3f(b_min(0), b_min(1), b_max(2)); ::glVertex3f(b_min(0), b_min(1), b_max(2) - size(2)); + + ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_min(1), b_max(2)); + ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1) + size(1), b_max(2)); + ::glVertex3f(b_max(0), b_min(1), b_max(2)); ::glVertex3f(b_max(0), b_min(1), b_max(2) - size(2)); + + ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0) - size(0), b_max(1), b_max(2)); + ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1) - size(1), b_max(2)); + ::glVertex3f(b_max(0), b_max(1), b_max(2)); ::glVertex3f(b_max(0), b_max(1), b_max(2) - size(2)); + + ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0) + size(0), b_max(1), b_max(2)); + ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1) - size(1), b_max(2)); + ::glVertex3f(b_min(0), b_max(1), b_max(2)); ::glVertex3f(b_min(0), b_max(1), b_max(2) - size(2)); + + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +static ColorRGBA get_color(Axis axis) +{ + return AXES_COLOR[axis]; +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void Selection::render_sidebar_position_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) +#else +void Selection::render_sidebar_position_hints(const std::string& sidebar_field) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_matrix = camera.get_view_matrix() * matrix; + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (boost::ends_with(sidebar_field, "x")) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()); + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_arrow.set_color(get_color(X)); + m_arrow.render(); + } + else if (boost::ends_with(sidebar_field, "y")) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + shader.set_uniform("view_model_matrix", view_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_arrow.set_color(get_color(Y)); + m_arrow.render(); + } + else if (boost::ends_with(sidebar_field, "z")) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = view_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitX()); + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_arrow.set_color(get_color(Z)); + m_arrow.render(); + } +#else + if (boost::ends_with(sidebar_field, "x")) { + glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); + m_arrow.set_color(-1, get_color(X)); + m_arrow.render(); + } + else if (boost::ends_with(sidebar_field, "y")) { + m_arrow.set_color(-1, get_color(Y)); + m_arrow.render(); + } + else if (boost::ends_with(sidebar_field, "z")) { + glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); + m_arrow.set_color(-1, get_color(Z)); + m_arrow.render(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) +#else +void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + auto render_sidebar_rotation_hint = [this](GLShaderProgram& shader, const Transform3d& matrix) { + Transform3d view_model_matrix = matrix; + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_curved_arrow.render(); + view_model_matrix = matrix * Geometry::assemble_transform(Vec3d::Zero(), PI * Vec3d::UnitZ()); + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_curved_arrow.render(); + }; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_matrix = camera.get_view_matrix() * matrix; + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); +#else + auto render_sidebar_rotation_hint = [this]() { + m_curved_arrow.render(); + glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); + m_curved_arrow.render(); + }; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (boost::ends_with(sidebar_field, "x")) { +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + m_curved_arrow.set_color(get_color(X)); +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_sidebar_rotation_hint(shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY())); +#else + render_sidebar_rotation_hint(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + else if (boost::ends_with(sidebar_field, "y")) { +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + m_curved_arrow.set_color(get_color(Y)); +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_sidebar_rotation_hint(shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitX())); +#else + render_sidebar_rotation_hint(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + else if (boost::ends_with(sidebar_field, "z")) { + m_curved_arrow.set_color(get_color(Z)); +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_sidebar_rotation_hint(shader, view_matrix); +#else + render_sidebar_rotation_hint(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } +#else + auto render_sidebar_rotation_hint = [this]() { + m_curved_arrow.render(); + glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); + m_curved_arrow.render(); + }; + + if (boost::ends_with(sidebar_field, "x")) { + glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); + m_curved_arrow.set_color(-1, get_color(X)); + render_sidebar_rotation_hint(); + } + else if (boost::ends_with(sidebar_field, "y")) { + glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); + m_curved_arrow.set_color(-1, get_color(Y)); + render_sidebar_rotation_hint(); + } + else if (boost::ends_with(sidebar_field, "z")) { + m_curved_arrow.set_color(-1, get_color(Z)); + render_sidebar_rotation_hint(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, GLShaderProgram& shader, const Transform3d& matrix) +#else +void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ + const bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis, GLShaderProgram& shader, const Transform3d& matrix) { +#else + auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis) { +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_arrow.set_color(uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); +#else + m_arrow.set_color(-1, uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_GL_SHADERS_ATTRIBUTES + Transform3d view_model_matrix = matrix * Geometry::assemble_transform(5.0 * Vec3d::UnitY()); + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + GLShaderProgram* shader = wxGetApp().get_current_shader(); + if (shader != nullptr) + shader->set_uniform("emission_factor", 0.0f); + + glsafe(::glTranslated(0.0, 5.0, 0.0)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_arrow.render(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + view_model_matrix = matrix * Geometry::assemble_transform(-5.0 * Vec3d::UnitY(), PI * Vec3d::UnitZ()); + shader.set_uniform("view_model_matrix", view_model_matrix); + shader.set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glTranslated(0.0, -10.0, 0.0)); + glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_arrow.render(); + }; + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_matrix = camera.get_view_matrix() * matrix; + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (boost::ends_with(sidebar_field, "x") || uniform_scale) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_sidebar_scale_hint(X, shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ())); +#else + glsafe(::glPushMatrix()); + glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); + render_sidebar_scale_hint(X); + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + + if (boost::ends_with(sidebar_field, "y") || uniform_scale) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_sidebar_scale_hint(Y, shader, view_matrix); +#else + glsafe(::glPushMatrix()); + render_sidebar_scale_hint(Y); + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } + + if (boost::ends_with(sidebar_field, "z") || uniform_scale) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_sidebar_scale_hint(Z, shader, view_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitX())); +#else + glsafe(::glPushMatrix()); + glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); + render_sidebar_scale_hint(Z); + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void Selection::render_sidebar_layers_hints(const std::string& sidebar_field, GLShaderProgram& shader) +#else +void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ + static const float Margin = 10.0f; + + std::string field = sidebar_field; + + // extract max_z + std::string::size_type pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + const float max_z = float(string_to_double_decimal_point(field.substr(pos + 1))); + + // extract min_z + field = field.substr(0, pos); + pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + const float min_z = float(string_to_double_decimal_point(field.substr(pos + 1))); + + // extract type + field = field.substr(0, pos); + pos = field.rfind("_"); + if (pos == std::string::npos) + return; + + const int type = std::stoi(field.substr(pos + 1)); + + const BoundingBoxf3& box = get_bounding_box(); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + const float min_x = float(box.min.x()) - Margin; + const float max_x = float(box.max.x()) + Margin; + const float min_y = float(box.min.y()) - Margin; + const float max_y = float(box.max.y()) + Margin; +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + // view dependend order of rendering to keep correct transparency + const bool camera_on_top = wxGetApp().plater()->get_camera().is_looking_downward(); + const float z1 = camera_on_top ? min_z : max_z; + const float z2 = camera_on_top ? max_z : min_z; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + const Vec3f p1 = { float(box.min.x()) - Margin, float(box.min.y()) - Margin, z1 }; + const Vec3f p2 = { float(box.max.x()) + Margin, float(box.max.y()) + Margin, z2 }; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (!m_planes.models[0].is_initialized() || !is_approx(m_planes.check_points[0], p1)) { + m_planes.check_points[0] = p1; + m_planes.models[0].reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + init_data.add_vertex(Vec3f(p1.x(), p1.y(), z1)); + init_data.add_vertex(Vec3f(p2.x(), p1.y(), z1)); + init_data.add_vertex(Vec3f(p2.x(), p2.y(), z1)); + init_data.add_vertex(Vec3f(p1.x(), p2.y(), z1)); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_planes.models[0].init_from(std::move(init_data)); + } + + if (!m_planes.models[1].is_initialized() || !is_approx(m_planes.check_points[1], p2)) { + m_planes.check_points[1] = p2; + m_planes.models[1].reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + init_data.add_vertex(Vec3f(p1.x(), p1.y(), z2)); + init_data.add_vertex(Vec3f(p2.x(), p1.y(), z2)); + init_data.add_vertex(Vec3f(p2.x(), p2.y(), z2)); + init_data.add_vertex(Vec3f(p1.x(), p2.y(), z2)); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_planes.models[1].init_from(std::move(init_data)); + } + +#if ENABLE_GL_SHADERS_ATTRIBUTES + 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()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + m_planes.models[0].set_color((camera_on_top && type == 1) || (!camera_on_top && type == 2) ? SOLID_PLANE_COLOR : TRANSPARENT_PLANE_COLOR); + m_planes.models[0].render(); + m_planes.models[1].set_color((camera_on_top && type == 2) || (!camera_on_top && type == 1) ? SOLID_PLANE_COLOR : TRANSPARENT_PLANE_COLOR); + m_planes.models[1].render(); +#else + ::glBegin(GL_QUADS); + ::glColor4fv((camera_on_top && type == 1) || (!camera_on_top && type == 2) ? SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data()); + ::glVertex3f(min_x, min_y, z1); + ::glVertex3f(max_x, min_y, z1); + ::glVertex3f(max_x, max_y, z1); + ::glVertex3f(min_x, max_y, z1); + glsafe(::glEnd()); + + ::glBegin(GL_QUADS); + ::glColor4fv((camera_on_top && type == 2) || (!camera_on_top && type == 1) ? SOLID_PLANE_COLOR.data() : TRANSPARENT_PLANE_COLOR.data()); + ::glVertex3f(min_x, min_y, z2); + ::glVertex3f(max_x, min_y, z2); + ::glVertex3f(max_x, max_y, z2); + ::glVertex3f(min_x, max_y, z2); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); +} + +#ifndef NDEBUG +static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) +{ + const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); + const Vec3d axis = angle_axis.axis(); + const double angle = angle_axis.angle(); + if (std::abs(angle) < 1e-8) + return true; + assert(std::abs(axis.x()) < 1e-8); + assert(std::abs(axis.y()) < 1e-8); + assert(std::abs(std::abs(axis.z()) - 1.) < 1e-8); + return std::abs(axis.x()) < 1e-8 && std::abs(axis.y()) < 1e-8 && std::abs(std::abs(axis.z()) - 1.) < 1e-8; +} + +static void verify_instances_rotation_synchronized(const Model &model, const GLVolumePtrs &volumes) +{ + for (int idx_object = 0; idx_object < int(model.objects.size()); ++idx_object) { + int idx_volume_first = -1; + for (int i = 0; i < (int)volumes.size(); ++i) { + if (volumes[i]->object_idx() == idx_object) { + idx_volume_first = i; + break; + } + } + assert(idx_volume_first != -1); // object without instances? + if (idx_volume_first == -1) + continue; + const Vec3d &rotation0 = volumes[idx_volume_first]->get_instance_rotation(); + for (int i = idx_volume_first + 1; i < (int)volumes.size(); ++i) + if (volumes[i]->object_idx() == idx_object) { + const Vec3d &rotation = volumes[i]->get_instance_rotation(); + assert(is_rotation_xy_synchronized(rotation, rotation0)); + } + } +} +#endif /* NDEBUG */ + +void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_type) +{ + std::set done; // prevent processing volumes twice + done.insert(m_list.begin(), m_list.end()); + + for (unsigned int i : m_list) { + if (done.size() == m_volumes->size()) + break; + + const GLVolume* volume = (*m_volumes)[i]; +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (volume->is_wipe_tower) + continue; + + const int object_idx = volume->object_idx(); +#else + const int object_idx = volume->object_idx(); + if (object_idx >= 1000) + continue; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + + const int instance_idx = volume->instance_idx(); + const Vec3d& rotation = volume->get_instance_rotation(); + const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); + const Vec3d& mirror = volume->get_instance_mirror(); + + // Process unselected instances. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (done.size() == m_volumes->size()) + break; + + if (done.find(j) != done.end()) + continue; + + GLVolume* v = (*m_volumes)[j]; + if (v->object_idx() != object_idx || v->instance_idx() == instance_idx) + continue; + + assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); + switch (sync_rotation_type) { + case SYNC_ROTATION_NONE: { + // z only rotation -> synch instance z + // The X,Y rotations should be synchronized from start to end of the rotation. + assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) + v->set_instance_offset(Z, volume->get_instance_offset().z()); + break; + } + case SYNC_ROTATION_GENERAL: + // generic rotation -> update instance z with the delta of the rotation. + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); + v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); + break; + } + + v->set_instance_scaling_factor(scaling_factor); + v->set_instance_mirror(mirror); + + done.insert(j); + } + } + +#ifndef NDEBUG + verify_instances_rotation_synchronized(*m_model, *m_volumes); +#endif /* NDEBUG */ +} + +void Selection::synchronize_unselected_volumes() +{ + for (unsigned int i : m_list) { + const GLVolume* volume = (*m_volumes)[i]; +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + if (volume->is_wipe_tower) + continue; + + const int object_idx = volume->object_idx(); +#else + const int object_idx = volume->object_idx(); + if (object_idx >= 1000) + continue; +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + + const int volume_idx = volume->volume_idx(); + const Vec3d& offset = volume->get_volume_offset(); + const Vec3d& rotation = volume->get_volume_rotation(); + const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); + const Vec3d& mirror = volume->get_volume_mirror(); + + // Process unselected volumes. + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { + if (j == i) + continue; + + GLVolume* v = (*m_volumes)[j]; + if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) + continue; + + v->set_volume_offset(offset); + v->set_volume_rotation(rotation); + v->set_volume_scaling_factor(scaling_factor); + v->set_volume_mirror(mirror); + } + } +} + +void Selection::ensure_on_bed() +{ + typedef std::map, double> InstancesToZMap; + InstancesToZMap instances_min_z; + + for (size_t i = 0; i < m_volumes->size(); ++i) { + GLVolume* volume = (*m_volumes)[i]; + if (!volume->is_wipe_tower && !volume->is_modifier && + std::find(m_cache.sinking_volumes.begin(), m_cache.sinking_volumes.end(), i) == m_cache.sinking_volumes.end()) { + const double min_z = volume->transformed_convex_hull_bounding_box().min.z(); + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it == instances_min_z.end()) + it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first; + + it->second = std::min(it->second, min_z); + } + } + + for (GLVolume* volume : *m_volumes) { + std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_min_z.find(instance); + if (it != instances_min_z.end()) + volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second); + } +} + +void Selection::ensure_not_below_bed() +{ + typedef std::map, double> InstancesToZMap; + InstancesToZMap instances_max_z; + + for (size_t i = 0; i < m_volumes->size(); ++i) { + GLVolume* volume = (*m_volumes)[i]; + if (!volume->is_wipe_tower && !volume->is_modifier) { + const double max_z = volume->transformed_convex_hull_bounding_box().max.z(); + const std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::iterator it = instances_max_z.find(instance); + if (it == instances_max_z.end()) + it = instances_max_z.insert({ instance, -DBL_MAX }).first; + + it->second = std::max(it->second, max_z); + } + } + + if (is_any_volume()) { + for (unsigned int i : m_list) { + GLVolume& volume = *(*m_volumes)[i]; + const std::pair instance = std::make_pair(volume.object_idx(), volume.instance_idx()); + InstancesToZMap::const_iterator it = instances_max_z.find(instance); + const double z_shift = SINKING_MIN_Z_THRESHOLD - it->second; + if (it != instances_max_z.end() && z_shift > 0.0) + volume.set_volume_offset(Z, volume.get_volume_offset(Z) + z_shift); + } + } + else { + for (GLVolume* volume : *m_volumes) { + const std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::const_iterator it = instances_max_z.find(instance); + if (it != instances_max_z.end() && it->second < SINKING_MIN_Z_THRESHOLD) + volume->set_instance_offset(Z, volume->get_instance_offset(Z) + SINKING_MIN_Z_THRESHOLD - it->second); + } + } +} + +bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const +{ + struct SameInstance + { + int obj_idx; + int inst_idx; + GLVolumePtrs& volumes; + + SameInstance(int obj_idx, int inst_idx, GLVolumePtrs& volumes) : obj_idx(obj_idx), inst_idx(inst_idx), volumes(volumes) {} + bool operator () (unsigned int i) { return (volumes[i]->volume_idx() >= 0) && (volumes[i]->object_idx() == obj_idx) && (volumes[i]->instance_idx() == inst_idx); } + }; + + if ((unsigned int)m_volumes->size() <= volume_idx) + return false; + + GLVolume* volume = (*m_volumes)[volume_idx]; + int object_idx = volume->object_idx(); + if ((int)m_model->objects.size() <= object_idx) + return false; + + unsigned int count = (unsigned int)std::count_if(m_list.begin(), m_list.end(), SameInstance(object_idx, volume->instance_idx(), *m_volumes)); + return count == (unsigned int)m_model->objects[object_idx]->volumes.size(); +} + +void Selection::paste_volumes_from_clipboard() +{ +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + + int dst_obj_idx = get_object_idx(); + if ((dst_obj_idx < 0) || ((int)m_model->objects.size() <= dst_obj_idx)) + return; + + ModelObject* dst_object = m_model->objects[dst_obj_idx]; + + int dst_inst_idx = get_instance_idx(); + if ((dst_inst_idx < 0) || ((int)dst_object->instances.size() <= dst_inst_idx)) + return; + + ModelObject* src_object = m_clipboard.get_object(0); + if (src_object != nullptr) + { + ModelInstance* dst_instance = dst_object->instances[dst_inst_idx]; + BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx); + Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true); + Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true); + bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix); + + // used to keep relative position of multivolume selections when pasting from another object + BoundingBoxf3 total_bb; + + ModelVolumePtrs volumes; + for (ModelVolume* src_volume : src_object->volumes) + { + ModelVolume* dst_volume = dst_object->add_volume(*src_volume); + dst_volume->set_new_unique_id(); + if (from_same_object) + { +// // if the volume comes from the same object, apply the offset in world system +// double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); +// dst_volume->translate(dst_matrix.inverse() * Vec3d(offset, offset, 0.0)); + } + else + { + // if the volume comes from another object, apply the offset as done when adding modifiers + // see ObjectList::load_generic_subobject() + total_bb.merge(dst_volume->mesh().bounding_box().transformed(src_volume->get_matrix())); + } + + volumes.push_back(dst_volume); +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + } + + // keeps relative position of multivolume selections + if (!from_same_object) + { + for (ModelVolume* v : volumes) + { + v->set_offset((v->get_offset() - total_bb.center()) + dst_matrix.inverse() * (Vec3d(dst_instance_bb.max(0), dst_instance_bb.min(1), dst_instance_bb.min(2)) + 0.5 * total_bb.size() - dst_instance->get_transformation().get_offset())); + } + } + + wxGetApp().obj_list()->paste_volumes_into_list(dst_obj_idx, volumes); + } + +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ +} + +void Selection::paste_objects_from_clipboard() +{ +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + + std::vector object_idxs; + const ModelObjectPtrs& src_objects = m_clipboard.get_objects(); + for (const ModelObject* src_object : src_objects) + { + ModelObject* dst_object = m_model->add_object(*src_object); + double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05); + Vec3d displacement(offset, offset, 0.0); + for (ModelInstance* inst : dst_object->instances) + { + inst->set_offset(inst->get_offset() + displacement); + } + + object_idxs.push_back(m_model->objects.size() - 1); +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ + } + + wxGetApp().obj_list()->paste_objects_into_list(object_idxs); + +#ifdef _DEBUG + check_model_ids_validity(*m_model); +#endif /* _DEBUG */ +} + +} // namespace GUI +} // namespace Slic3r From 41b64e189a799239b959f6b3582bf4f7749d4940 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 19 May 2022 12:37:54 +0200 Subject: [PATCH 027/169] Bumped up version to 2.6.0-alpha0: the development of 2.5.x will be separated and based on 2.4.2. master branch will be used for development of 2.6.x --- version.inc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/version.inc b/version.inc index 93b376323..3e1460a0e 100644 --- a/version.inc +++ b/version.inc @@ -3,7 +3,7 @@ set(SLIC3R_APP_NAME "PrusaSlicer") set(SLIC3R_APP_KEY "PrusaSlicer") -set(SLIC3R_VERSION "2.5.0-alpha0") +set(SLIC3R_VERSION "2.6.0-alpha0") set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN") -set(SLIC3R_RC_VERSION "2,5,0,0") -set(SLIC3R_RC_VERSION_DOTS "2.5.0.0") +set(SLIC3R_RC_VERSION "2,6,0,0") +set(SLIC3R_RC_VERSION_DOTS "2.6.0.0") From f82d5c52b34b74304a53116ed9cd0aeef9071a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 19 May 2022 12:46:15 +0200 Subject: [PATCH 028/169] Refactored Lightning infill to get rid of unnecessary std::list and std::unordered_map, which was slow. --- .../Fill/Lightning/DistanceField.cpp | 21 ++-- .../Fill/Lightning/DistanceField.hpp | 98 +++++++++++++++++-- src/libslic3r/Fill/Lightning/Layer.cpp | 5 +- 3 files changed, 105 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Fill/Lightning/DistanceField.cpp b/src/libslic3r/Fill/Lightning/DistanceField.cpp index 308ca41c6..d956c4e23 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.cpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.cpp @@ -35,16 +35,18 @@ DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outl assert(m_unsupported_points_bbox.contains(p)); } } - m_unsupported_points.sort([&radius](const UnsupportedCell &a, const UnsupportedCell &b) { + std::stable_sort(m_unsupported_points.begin(), m_unsupported_points.end(), [&radius](const UnsupportedCell &a, const UnsupportedCell &b) { constexpr coord_t prime_for_hash = 191; return std::abs(b.dist_to_boundary - a.dist_to_boundary) > radius ? a.dist_to_boundary < b.dist_to_boundary : (PointHash{}(a.loc) % prime_for_hash) < (PointHash{}(b.loc) % prime_for_hash); }); - for (auto it = m_unsupported_points.begin(); it != m_unsupported_points.end(); ++it) { - UnsupportedCell& cell = *it; - m_unsupported_points_grid.emplace(this->to_grid_point(cell.loc), it); - } + + m_unsupported_points_erased.resize(m_unsupported_points.size()); + std::fill(m_unsupported_points_erased.begin(), m_unsupported_points_erased.end(), false); + + m_unsupported_points_grid.initialize(m_unsupported_points, [&self = std::as_const(*this)](const Point &p) -> Point { return self.to_grid_point(p); }); + // Because the distance between two points is at least one axis equal to m_cell_size, every cell // in m_unsupported_points_grid contains exactly one point. assert(m_unsupported_points.size() == m_unsupported_points_grid.size()); @@ -96,12 +98,11 @@ void DistanceField::update(const Point& to_node, const Point& added_leaf) } // Inside a circle at the end of the new leaf, or inside a rotated rectangle. // Remove unsupported leafs at this grid location. - if (auto it = m_unsupported_points_grid.find(grid_addr); it != m_unsupported_points_grid.end()) { - std::list::iterator& list_it = it->second; - UnsupportedCell& cell = *list_it; + if (const size_t cell_idx = m_unsupported_points_grid.find_cell_idx(grid_addr); cell_idx != std::numeric_limits::max()) { + const UnsupportedCell &cell = m_unsupported_points[cell_idx]; if ((cell.loc - added_leaf).cast().squaredNorm() <= m_supporting_radius2) { - m_unsupported_points.erase(list_it); - m_unsupported_points_grid.erase(it); + m_unsupported_points_erased[cell_idx] = true; + m_unsupported_points_grid.mark_erased(grid_addr); } } } diff --git a/src/libslic3r/Fill/Lightning/DistanceField.hpp b/src/libslic3r/Fill/Lightning/DistanceField.hpp index beb46c5c5..fc25beb62 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.hpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.hpp @@ -38,11 +38,17 @@ public: * \return ``true`` if successful, or ``false`` if there are no more points * to consider. */ - bool tryGetNextPoint(Point* p) const { - if (m_unsupported_points.empty()) - return false; - *p = m_unsupported_points.front().loc; - return true; + bool tryGetNextPoint(Point *out_unsupported_location, size_t *out_unsupported_cell_idx, const size_t start_idx = 0) const + { + for (size_t point_idx = start_idx; point_idx < m_unsupported_points.size(); ++point_idx) { + if (!m_unsupported_points_erased[point_idx]) { + *out_unsupported_cell_idx = point_idx; + *out_unsupported_location = m_unsupported_points[point_idx].loc; + return true; + } + } + + return false; } /*! @@ -87,7 +93,8 @@ protected: /*! * Cells which still need to be supported at some point. */ - std::list m_unsupported_points; + std::vector m_unsupported_points; + std::vector m_unsupported_points_erased; /*! * BoundingBox of all points in m_unsupported_points. Used for mapping of sign integer numbers to positive integer numbers. @@ -98,7 +105,84 @@ protected: * Links the unsupported points to a grid point, so that we can quickly look * up the cell belonging to a certain position in the grid. */ - std::unordered_map::iterator, PointHash> m_unsupported_points_grid; + + class UnsupportedPointsGrid + { + public: + UnsupportedPointsGrid() = default; + void initialize(const std::vector &unsupported_points, const std::function &map_cell_to_grid) + { + if (unsupported_points.empty()) + return; + + BoundingBox unsupported_points_bbox; + for (const UnsupportedCell &cell : unsupported_points) + unsupported_points_bbox.merge(cell.loc); + + m_size = unsupported_points.size(); + m_grid_range = BoundingBox(map_cell_to_grid(unsupported_points_bbox.min), map_cell_to_grid(unsupported_points_bbox.max)); + m_grid_size = m_grid_range.size() + Point::Ones(); + + m_data.assign(m_grid_size.y() * m_grid_size.x(), std::numeric_limits::max()); + m_data_erased.assign(m_grid_size.y() * m_grid_size.x(), true); + + for (size_t cell_idx = 0; cell_idx < unsupported_points.size(); ++cell_idx) { + const size_t flat_idx = map_to_flat_array(map_cell_to_grid(unsupported_points[cell_idx].loc)); + assert(m_data[flat_idx] == std::numeric_limits::max()); + m_data[flat_idx] = cell_idx; + m_data_erased[flat_idx] = false; + } + } + + size_t size() const { return m_size; } + + size_t find_cell_idx(const Point &grid_addr) + { + if (!m_grid_range.contains(grid_addr)) + return std::numeric_limits::max(); + + if (const size_t flat_idx = map_to_flat_array(grid_addr); !m_data_erased[flat_idx]) { + assert(m_data[flat_idx] != std::numeric_limits::max()); + return m_data[flat_idx]; + } + + return std::numeric_limits::max(); + } + + void mark_erased(const Point &grid_addr) + { + assert(m_grid_range.contains(grid_addr)); + if (!m_grid_range.contains(grid_addr)) + return; + + const size_t flat_idx = map_to_flat_array(grid_addr); + assert(!m_data_erased[flat_idx] && m_data[flat_idx] != std::numeric_limits::max()); + assert(m_size != 0); + + m_data_erased[flat_idx] = true; + --m_size; + } + + private: + size_t m_size = 0; + + BoundingBox m_grid_range; + Point m_grid_size; + + std::vector m_data; + std::vector m_data_erased; + + inline size_t map_to_flat_array(const Point &loc) const + { + const Point offset_loc = loc - m_grid_range.min; + const size_t flat_idx = m_grid_size.x() * offset_loc.y() + offset_loc.x(); + assert(offset_loc.x() >= 0 && offset_loc.y() >= 0); + assert(flat_idx < m_grid_size.y() * m_grid_size.x()); + return flat_idx; + } + }; + + UnsupportedPointsGrid m_unsupported_points_grid; /*! * Maps the point to the grid coordinates. diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index f3193afe4..e8f954a60 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -56,8 +56,9 @@ void Layer::generateNewTrees // Until no more points need to be added to support all: // Determine next point from tree/outline areas via distance-field - Point unsupported_location; - while (distance_field.tryGetNextPoint(&unsupported_location)) { + size_t unsupported_cell_idx = 0; + Point unsupported_location; + while (distance_field.tryGetNextPoint(&unsupported_location, &unsupported_cell_idx, unsupported_cell_idx)) { throw_on_cancel_callback(); GroundingLocation grounding_loc = getBestGroundingLocation( unsupported_location, current_outlines, current_outlines_bbox, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator); From 95041751a1dce8a817420cfd117cc6bc0de4df50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 19 May 2022 12:46:41 +0200 Subject: [PATCH 029/169] Refactored Lightning infill before parallelization. --- .../Fill/Lightning/DistanceField.cpp | 11 +++++++--- .../Fill/Lightning/DistanceField.hpp | 1 - src/libslic3r/Fill/Lightning/Layer.cpp | 21 +++++++++---------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/Fill/Lightning/DistanceField.cpp b/src/libslic3r/Fill/Lightning/DistanceField.cpp index d956c4e23..3602e60ab 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.cpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.cpp @@ -18,7 +18,12 @@ DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outl m_supporting_radius2 = Slic3r::sqr(int64_t(radius)); // Sample source polygons with a regular grid sampling pattern. for (const ExPolygon &expoly : union_ex(current_overhang)) { - for (const Point &p : sample_grid_pattern(expoly, m_cell_size)) { + const Points sampled_points = sample_grid_pattern(expoly, m_cell_size); + const size_t unsupported_points_prev_size = m_unsupported_points.size(); + m_unsupported_points.resize(unsupported_points_prev_size + sampled_points.size()); + + for (size_t sp_idx = 0; sp_idx < sampled_points.size(); ++sp_idx) { + const Point &sp = sampled_points[sp_idx]; // Find a squared distance to the source expolygon boundary. double d2 = std::numeric_limits::max(); for (size_t icontour = 0; icontour <= expoly.holes.size(); ++icontour) { @@ -26,12 +31,12 @@ DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outl if (contour.size() > 2) { Point prev = contour.points.back(); for (const Point &p2 : contour.points) { - d2 = std::min(d2, Line::distance_to_squared(p, prev, p2)); + d2 = std::min(d2, Line::distance_to_squared(sp, prev, p2)); prev = p2; } } } - m_unsupported_points.emplace_back(p, sqrt(d2)); + m_unsupported_points[unsupported_points_prev_size + sp_idx] = {sp, coord_t(std::sqrt(d2))}; assert(m_unsupported_points_bbox.contains(p)); } } diff --git a/src/libslic3r/Fill/Lightning/DistanceField.hpp b/src/libslic3r/Fill/Lightning/DistanceField.hpp index fc25beb62..d4a142c05 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.hpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.hpp @@ -83,7 +83,6 @@ protected: */ struct UnsupportedCell { - UnsupportedCell(const Point &loc, coord_t dist_to_boundary) : loc(loc), dist_to_boundary(dist_to_boundary) {} // The position of the center of this cell. Point loc; // How far this cell is removed from the ``current_outline`` polygon, the edge of the infill area. diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index e8f954a60..8539bb532 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -142,26 +142,25 @@ GroundingLocation Layer::getBestGroundingLocation const auto within_dist = coord_t((node_location - unsupported_location).cast().norm()); - NodeSPtr sub_tree{ nullptr }; - coord_t current_dist = getWeightedDistance(node_location, unsupported_location); + NodeSPtr sub_tree{nullptr}; + coord_t current_dist = getWeightedDistance(node_location, unsupported_location); if (current_dist >= wall_supporting_radius) { // Only reconnect tree roots to other trees if they are not already close to the outlines. const coord_t search_radius = std::min(current_dist, within_dist); BoundingBox region(unsupported_location - Point(search_radius, search_radius), unsupported_location + Point(search_radius + locator_cell_size, search_radius + locator_cell_size)); region.min = to_grid_point(region.min, current_outlines_bbox); region.max = to_grid_point(region.max, current_outlines_bbox); - Point grid_addr; - for (grid_addr.y() = region.min.y(); grid_addr.y() < region.max.y(); ++ grid_addr.y()) - for (grid_addr.x() = region.min.x(); grid_addr.x() < region.max.x(); ++ grid_addr.x()) { - auto it_range = tree_node_locator.equal_range(grid_addr); - for (auto it = it_range.first; it != it_range.second; ++ it) { - auto candidate_sub_tree = it->second.lock(); + + for (coord_t grid_addr_y = region.min.y(); grid_addr_y < region.max.y(); ++grid_addr_y) + for (coord_t grid_addr_x = region.min.x(); grid_addr_x < region.max.x(); ++grid_addr_x) { + const auto it_range = tree_node_locator.equal_range({grid_addr_x, grid_addr_y}); + for (auto it = it_range.first; it != it_range.second; ++it) { + const NodeSPtr candidate_sub_tree = it->second.lock(); if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) && !(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) && !polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) { - const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius); - if (candidate_dist < current_dist) { + if (const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius); candidate_dist < current_dist) { current_dist = candidate_dist; - sub_tree = candidate_sub_tree; + sub_tree = candidate_sub_tree; } } } From 4bde35cae3d263905887cce48565f8bc4b837cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 19 May 2022 12:47:25 +0200 Subject: [PATCH 030/169] Parallelized DistanceField::DistanceField() and Layer::getBestGroundingLocation() in Lightning infill. --- .../Fill/Lightning/DistanceField.cpp | 32 +++++++----- src/libslic3r/Fill/Lightning/Layer.cpp | 51 ++++++++++++++----- 2 files changed, 57 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/Fill/Lightning/DistanceField.cpp b/src/libslic3r/Fill/Lightning/DistanceField.cpp index 3602e60ab..ef407664c 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.cpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.cpp @@ -5,6 +5,8 @@ #include "../FillRectilinear.hpp" #include "../../ClipperUtils.hpp" +#include + namespace Slic3r::FillLightning { @@ -22,23 +24,25 @@ DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outl const size_t unsupported_points_prev_size = m_unsupported_points.size(); m_unsupported_points.resize(unsupported_points_prev_size + sampled_points.size()); - for (size_t sp_idx = 0; sp_idx < sampled_points.size(); ++sp_idx) { - const Point &sp = sampled_points[sp_idx]; - // Find a squared distance to the source expolygon boundary. - double d2 = std::numeric_limits::max(); - for (size_t icontour = 0; icontour <= expoly.holes.size(); ++icontour) { - const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1]; - if (contour.size() > 2) { - Point prev = contour.points.back(); - for (const Point &p2 : contour.points) { - d2 = std::min(d2, Line::distance_to_squared(sp, prev, p2)); - prev = p2; + tbb::parallel_for(tbb::blocked_range(0, sampled_points.size()), [&self = *this, &expoly = std::as_const(expoly), &sampled_points = std::as_const(sampled_points), &unsupported_points_prev_size = std::as_const(unsupported_points_prev_size)](const tbb::blocked_range &range) -> void { + for (size_t sp_idx = range.begin(); sp_idx < range.end(); ++sp_idx) { + const Point &sp = sampled_points[sp_idx]; + // Find a squared distance to the source expolygon boundary. + double d2 = std::numeric_limits::max(); + for (size_t icontour = 0; icontour <= expoly.holes.size(); ++icontour) { + const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1]; + if (contour.size() > 2) { + Point prev = contour.points.back(); + for (const Point &p2 : contour.points) { + d2 = std::min(d2, Line::distance_to_squared(sp, prev, p2)); + prev = p2; + } } } + self.m_unsupported_points[unsupported_points_prev_size + sp_idx] = {sp, coord_t(std::sqrt(d2))}; + assert(self.m_unsupported_points_bbox.contains(sp)); } - m_unsupported_points[unsupported_points_prev_size + sp_idx] = {sp, coord_t(std::sqrt(d2))}; - assert(m_unsupported_points_bbox.contains(p)); - } + }); // end of parallel_for } std::stable_sort(m_unsupported_points.begin(), m_unsupported_points.end(), [&radius](const UnsupportedCell &a, const UnsupportedCell &b) { constexpr coord_t prime_for_hash = 191; diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index 8539bb532..0bd2a65c4 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -10,6 +10,10 @@ #include "../../Geometry.hpp" #include "Utils.hpp" +#include +#include +#include + namespace Slic3r::FillLightning { coord_t Layer::getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location) @@ -150,21 +154,44 @@ GroundingLocation Layer::getBestGroundingLocation region.min = to_grid_point(region.min, current_outlines_bbox); region.max = to_grid_point(region.max, current_outlines_bbox); - for (coord_t grid_addr_y = region.min.y(); grid_addr_y < region.max.y(); ++grid_addr_y) - for (coord_t grid_addr_x = region.min.x(); grid_addr_x < region.max.x(); ++grid_addr_x) { - const auto it_range = tree_node_locator.equal_range({grid_addr_x, grid_addr_y}); - for (auto it = it_range.first; it != it_range.second; ++it) { - const NodeSPtr candidate_sub_tree = it->second.lock(); - if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) && - !(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) && - !polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) { - if (const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius); candidate_dist < current_dist) { - current_dist = candidate_dist; - sub_tree = candidate_sub_tree; + Point current_dist_grid_addr{std::numeric_limits::lowest(), std::numeric_limits::lowest()}; + std::mutex current_dist_mutex; + tbb::parallel_for(tbb::blocked_range2d(region.min.y(), region.max.y(), region.min.x(), region.max.x()), [¤t_dist, current_dist_copy = current_dist, ¤t_dist_mutex, &sub_tree, ¤t_dist_grid_addr, &exclude_tree = std::as_const(exclude_tree), &outline_locator = std::as_const(outline_locator), &supporting_radius = std::as_const(supporting_radius), &tree_node_locator = std::as_const(tree_node_locator), &unsupported_location = std::as_const(unsupported_location)](const tbb::blocked_range2d &range) -> void { + for (coord_t grid_addr_y = range.rows().begin(); grid_addr_y < range.rows().end(); ++grid_addr_y) + for (coord_t grid_addr_x = range.cols().begin(); grid_addr_x < range.cols().end(); ++grid_addr_x) { + const Point local_grid_addr{grid_addr_x, grid_addr_y}; + NodeSPtr local_sub_tree{nullptr}; + coord_t local_current_dist = current_dist_copy; + const auto it_range = tree_node_locator.equal_range(local_grid_addr); + for (auto it = it_range.first; it != it_range.second; ++it) { + const NodeSPtr candidate_sub_tree = it->second.lock(); + if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) && + !(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) && + !polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) { + if (const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius); candidate_dist < local_current_dist) { + local_current_dist = candidate_dist; + local_sub_tree = candidate_sub_tree; + } + } + } + // To always get the same result in a parallel version as in a non-parallel version, + // we need to preserve that for the same current_dist, we select the same sub_tree + // as in the non-parallel version. For this purpose, inside the variable + // current_dist_grid_addr is stored from with 2D grid position assigned sub_tree comes. + // And when there are two sub_tree with the same current_dist, one which will be found + // the first in the non-parallel version is selected. + { + std::lock_guard lock(current_dist_mutex); + if (local_current_dist < current_dist || + (local_current_dist == current_dist && (grid_addr_y < current_dist_grid_addr.y() || + (grid_addr_y == current_dist_grid_addr.y() && grid_addr_x < current_dist_grid_addr.x())))) { + current_dist = local_current_dist; + sub_tree = local_sub_tree; + current_dist_grid_addr = local_grid_addr; } } } - } + }); // end of parallel_for } return ! sub_tree ? From 1582d019fb973c90c9b0e0f270383dc0c9ec543c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 19 May 2022 15:08:50 +0200 Subject: [PATCH 031/169] Fixed another crash in Lightning infill. --- src/libslic3r/Fill/Lightning/Generator.cpp | 2 ++ src/libslic3r/Fill/Lightning/TreeNode.hpp | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index b4c07a338..e226fbbab 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -125,6 +125,8 @@ void Generator::generateTrees(const PrintObject &print_object, const std::functi if (const BoundingBox &outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined) below_outlines_bbox.merge(outlines_locator_bbox); + below_outlines_bbox.merge(get_extents(current_lightning_layer.tree_roots).inflated(SCALED_EPSILON)); + outlines_locator.set_bbox(below_outlines_bbox); outlines_locator.create(below_outlines, locator_cell_size); diff --git a/src/libslic3r/Fill/Lightning/TreeNode.hpp b/src/libslic3r/Fill/Lightning/TreeNode.hpp index fdb80d2e6..81c63f7f6 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.hpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.hpp @@ -269,6 +269,9 @@ protected: std::optional m_last_grounding_location; // &tree_roots); + #ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT friend void export_to_svg(const NodeSPtr &root_node, Slic3r::SVG &svg); friend void export_to_svg(const std::string &path, const Polygons &contour, const std::vector &root_nodes); @@ -278,6 +281,23 @@ protected: bool inside(const Polygons &polygons, const Point &p); bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, coord_t within_max_dist); +inline BoundingBox get_extents(const NodeSPtr &root_node) +{ + BoundingBox bbox; + for (const NodeSPtr &children : root_node->m_children) + bbox.merge(get_extents(children)); + bbox.merge(root_node->getLocation()); + return bbox; +} + +inline BoundingBox get_extents(const std::vector &tree_roots) +{ + BoundingBox bbox; + for (const NodeSPtr &root_node : tree_roots) + bbox.merge(get_extents(root_node)); + return bbox; +} + #ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT void export_to_svg(const NodeSPtr &root_node, SVG &svg); void export_to_svg(const std::string &path, const Polygons &contour, const std::vector &root_nodes); From 6365e54b1f98527696a47ea69e09f7d48be63043 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 20 May 2022 10:39:51 +0200 Subject: [PATCH 032/169] Fixed loading of 3mf files containing single volume instances where the volume is shifted with respect to the instance origin --- src/libslic3r/Format/3mf.cpp | 6419 +++++++++++++++++----------------- 1 file changed, 3221 insertions(+), 3198 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 4259562aa..abe705dc4 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1,3198 +1,3221 @@ -#include "../libslic3r.h" -#include "../Exception.hpp" -#include "../Model.hpp" -#include "../Utils.hpp" -#include "../LocalesUtils.hpp" -#include "../GCode.hpp" -#include "../Geometry.hpp" -#include "../GCode/ThumbnailData.hpp" -#include "../Semver.hpp" -#include "../Time.hpp" - -#include "../I18N.hpp" - -#include "3mf.hpp" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -namespace pt = boost::property_tree; - -#include -#include -#include "miniz_extension.hpp" - -#include - -// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, -// https://github.com/boostorg/spirit/pull/586 -// where the exported string is one digit shorter than it should be to guarantee lossless round trip. -// The code is left here for the ocasion boost guys improve. -#define EXPORT_3MF_USE_SPIRIT_KARMA_FP 0 - -// VERSION NUMBERS -// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them. -// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files. -// 2 : Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file, meshes transformed back to their coordinate system on loading. -// WARNING !! -> the version number has been rolled back to 1 -// the next change should use 3 -const unsigned int VERSION_3MF = 1; -// Allow loading version 2 file as well. -const unsigned int VERSION_3MF_COMPATIBLE = 2; -const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file - -// Painting gizmos data version numbers -// 0 : 3MF files saved by older PrusaSlicer or the painting gizmo wasn't used. No version definition in them. -// 1 : Introduction of painting gizmos data versioning. No other changes in painting gizmos data. -const unsigned int FDM_SUPPORTS_PAINTING_VERSION = 1; -const unsigned int SEAM_PAINTING_VERSION = 1; -const unsigned int MM_PAINTING_VERSION = 1; - -const std::string SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION = "slic3rpe:FdmSupportsPaintingVersion"; -const std::string SLIC3RPE_SEAM_PAINTING_VERSION = "slic3rpe:SeamPaintingVersion"; -const std::string SLIC3RPE_MM_PAINTING_VERSION = "slic3rpe:MmPaintingVersion"; - -const std::string MODEL_FOLDER = "3D/"; -const std::string MODEL_EXTENSION = ".model"; -const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA -const std::string CONTENT_TYPES_FILE = "[Content_Types].xml"; -const std::string RELATIONSHIPS_FILE = "_rels/.rels"; -const std::string THUMBNAIL_FILE = "Metadata/thumbnail.png"; -const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; -const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; -const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; -const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml"; -const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; -const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt"; -const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"; - -static constexpr const char* MODEL_TAG = "model"; -static constexpr const char* RESOURCES_TAG = "resources"; -static constexpr const char* OBJECT_TAG = "object"; -static constexpr const char* MESH_TAG = "mesh"; -static constexpr const char* VERTICES_TAG = "vertices"; -static constexpr const char* VERTEX_TAG = "vertex"; -static constexpr const char* TRIANGLES_TAG = "triangles"; -static constexpr const char* TRIANGLE_TAG = "triangle"; -static constexpr const char* COMPONENTS_TAG = "components"; -static constexpr const char* COMPONENT_TAG = "component"; -static constexpr const char* BUILD_TAG = "build"; -static constexpr const char* ITEM_TAG = "item"; -static constexpr const char* METADATA_TAG = "metadata"; - -static constexpr const char* CONFIG_TAG = "config"; -static constexpr const char* VOLUME_TAG = "volume"; - -static constexpr const char* UNIT_ATTR = "unit"; -static constexpr const char* NAME_ATTR = "name"; -static constexpr const char* TYPE_ATTR = "type"; -static constexpr const char* ID_ATTR = "id"; -static constexpr const char* X_ATTR = "x"; -static constexpr const char* Y_ATTR = "y"; -static constexpr const char* Z_ATTR = "z"; -static constexpr const char* V1_ATTR = "v1"; -static constexpr const char* V2_ATTR = "v2"; -static constexpr const char* V3_ATTR = "v3"; -static constexpr const char* OBJECTID_ATTR = "objectid"; -static constexpr const char* TRANSFORM_ATTR = "transform"; -static constexpr const char* PRINTABLE_ATTR = "printable"; -static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; -static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; -static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam"; -static constexpr const char* MMU_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation"; - -static constexpr const char* KEY_ATTR = "key"; -static constexpr const char* VALUE_ATTR = "value"; -static constexpr const char* FIRST_TRIANGLE_ID_ATTR = "firstid"; -static constexpr const char* LAST_TRIANGLE_ID_ATTR = "lastid"; - -static constexpr const char* OBJECT_TYPE = "object"; -static constexpr const char* VOLUME_TYPE = "volume"; - -static constexpr const char* NAME_KEY = "name"; -static constexpr const char* MODIFIER_KEY = "modifier"; -static constexpr const char* VOLUME_TYPE_KEY = "volume_type"; -static constexpr const char* MATRIX_KEY = "matrix"; -static constexpr const char* SOURCE_FILE_KEY = "source_file"; -static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id"; -static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id"; -static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x"; -static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y"; -static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z"; -static constexpr const char* SOURCE_IN_INCHES_KEY = "source_in_inches"; -static constexpr const char* SOURCE_IN_METERS_KEY = "source_in_meters"; -#if ENABLE_RELOAD_FROM_DISK_REWORK -static constexpr const char* SOURCE_IS_BUILTIN_VOLUME_KEY = "source_is_builtin_volume"; -#endif // ENABLE_RELOAD_FROM_DISK_REWORK - -static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed"; -static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets"; -static constexpr const char* MESH_STAT_FACETS_REMOVED = "facets_removed"; -static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed"; -static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges"; - - -const unsigned int VALID_OBJECT_TYPES_COUNT = 1; -const char* VALID_OBJECT_TYPES[] = -{ - "model" -}; - -const char* INVALID_OBJECT_TYPES[] = -{ - "solidsupport", - "support", - "surface", - "other" -}; - -class version_error : public Slic3r::FileIOError -{ -public: - version_error(const std::string& what_arg) : Slic3r::FileIOError(what_arg) {} - version_error(const char* what_arg) : Slic3r::FileIOError(what_arg) {} -}; - -const char* get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key) -{ - if ((attributes == nullptr) || (attributes_size == 0) || (attributes_size % 2 != 0) || (attribute_key == nullptr)) - return nullptr; - - for (unsigned int a = 0; a < attributes_size; a += 2) { - if (::strcmp(attributes[a], attribute_key) == 0) - return attributes[a + 1]; - } - - return nullptr; -} - -std::string get_attribute_value_string(const char** attributes, unsigned int attributes_size, const char* attribute_key) -{ - const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); - return (text != nullptr) ? text : ""; -} - -float get_attribute_value_float(const char** attributes, unsigned int attributes_size, const char* attribute_key) -{ - float value = 0.0f; - if (const char *text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr) - fast_float::from_chars(text, text + strlen(text), value); - return value; -} - -int get_attribute_value_int(const char** attributes, unsigned int attributes_size, const char* attribute_key) -{ - int value = 0; - if (const char *text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr) - boost::spirit::qi::parse(text, text + strlen(text), boost::spirit::qi::int_, value); - return value; -} - -bool get_attribute_value_bool(const char** attributes, unsigned int attributes_size, const char* attribute_key) -{ - const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); - return (text != nullptr) ? (bool)::atoi(text) : true; -} - -Slic3r::Transform3d get_transform_from_3mf_specs_string(const std::string& mat_str) -{ - // check: https://3mf.io/3d-manufacturing-format/ or https://github.com/3MFConsortium/spec_core/blob/master/3MF%20Core%20Specification.md - // to see how matrices are stored inside 3mf according to specifications - Slic3r::Transform3d ret = Slic3r::Transform3d::Identity(); - - if (mat_str.empty()) - // empty string means default identity matrix - return ret; - - std::vector mat_elements_str; - boost::split(mat_elements_str, mat_str, boost::is_any_of(" "), boost::token_compress_on); - - unsigned int size = (unsigned int)mat_elements_str.size(); - if (size != 12) - // invalid data, return identity matrix - return ret; - - unsigned int i = 0; - // matrices are stored into 3mf files as 4x3 - // we need to transpose them - for (unsigned int c = 0; c < 4; ++c) { - for (unsigned int r = 0; r < 3; ++r) { - ret(r, c) = ::atof(mat_elements_str[i++].c_str()); - } - } - return ret; -} - -float get_unit_factor(const std::string& unit) -{ - const char* text = unit.c_str(); - - if (::strcmp(text, "micron") == 0) - return 0.001f; - else if (::strcmp(text, "centimeter") == 0) - return 10.0f; - else if (::strcmp(text, "inch") == 0) - return 25.4f; - else if (::strcmp(text, "foot") == 0) - return 304.8f; - else if (::strcmp(text, "meter") == 0) - return 1000.0f; - else - // default "millimeters" (see specification) - return 1.0f; -} - -bool is_valid_object_type(const std::string& type) -{ - // if the type is empty defaults to "model" (see specification) - if (type.empty()) - return true; - - for (unsigned int i = 0; i < VALID_OBJECT_TYPES_COUNT; ++i) { - if (::strcmp(type.c_str(), VALID_OBJECT_TYPES[i]) == 0) - return true; - } - - return false; -} - -namespace Slic3r { - -//! macro used to mark string used at localization, -//! return same string -#define L(s) (s) -#define _(s) Slic3r::I18N::translate(s) - - // Base class with error messages management - class _3MF_Base - { - std::vector m_errors; - - protected: - void add_error(const std::string& error) { m_errors.push_back(error); } - void clear_errors() { m_errors.clear(); } - - public: - void log_errors() - { - for (const std::string& error : m_errors) - BOOST_LOG_TRIVIAL(error) << error; - } - }; - - class _3MF_Importer : public _3MF_Base - { - struct Component - { - int object_id; - Transform3d transform; - - explicit Component(int object_id) - : object_id(object_id) - , transform(Transform3d::Identity()) - { - } - - Component(int object_id, const Transform3d& transform) - : object_id(object_id) - , transform(transform) - { - } - }; - - typedef std::vector ComponentsList; - - struct Geometry - { - std::vector vertices; - std::vector triangles; - std::vector custom_supports; - std::vector custom_seam; - std::vector mmu_segmentation; - - bool empty() { return vertices.empty() || triangles.empty(); } - - void reset() { - vertices.clear(); - triangles.clear(); - custom_supports.clear(); - custom_seam.clear(); - mmu_segmentation.clear(); - } - }; - - struct CurrentObject - { - // ID of the object inside the 3MF file, 1 based. - int id; - // Index of the ModelObject in its respective Model, zero based. - int model_object_idx; - Geometry geometry; - ModelObject* object; - ComponentsList components; - - CurrentObject() { reset(); } - - void reset() { - id = -1; - model_object_idx = -1; - geometry.reset(); - object = nullptr; - components.clear(); - } - }; - - struct CurrentConfig - { - int object_id; - int volume_id; - }; - - struct Instance - { - ModelInstance* instance; - Transform3d transform; - - Instance(ModelInstance* instance, const Transform3d& transform) - : instance(instance) - , transform(transform) - { - } - }; - - struct Metadata - { - std::string key; - std::string value; - - Metadata(const std::string& key, const std::string& value) - : key(key) - , value(value) - { - } - }; - - typedef std::vector MetadataList; - - struct ObjectMetadata - { - struct VolumeMetadata - { - unsigned int first_triangle_id; - unsigned int last_triangle_id; - MetadataList metadata; - RepairedMeshErrors mesh_stats; - - VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id) - : first_triangle_id(first_triangle_id) - , last_triangle_id(last_triangle_id) - { - } - }; - - typedef std::vector VolumeMetadataList; - - MetadataList metadata; - VolumeMetadataList volumes; - }; - - // Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects. - typedef std::map IdToModelObjectMap; - typedef std::map IdToAliasesMap; - typedef std::vector InstancesList; - typedef std::map IdToMetadataMap; - typedef std::map IdToGeometryMap; - typedef std::map> IdToLayerHeightsProfileMap; - typedef std::map IdToLayerConfigRangesMap; - typedef std::map> IdToSlaSupportPointsMap; - typedef std::map> IdToSlaDrainHolesMap; - - // Version of the 3mf file - unsigned int m_version; - bool m_check_version; - - // Semantic version of PrusaSlicer, that generated this 3MF. - boost::optional m_prusaslicer_generator_version; - unsigned int m_fdm_supports_painting_version = 0; - unsigned int m_seam_painting_version = 0; - unsigned int m_mm_painting_version = 0; - - XML_Parser m_xml_parser; - // Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state - // after returning from XML_Parse() function, thus we keep the error state here. - bool m_parse_error { false }; - std::string m_parse_error_message; - Model* m_model; - float m_unit_factor; - CurrentObject m_curr_object; - IdToModelObjectMap m_objects; - IdToAliasesMap m_objects_aliases; - InstancesList m_instances; - IdToGeometryMap m_geometries; - CurrentConfig m_curr_config; - IdToMetadataMap m_objects_metadata; - IdToLayerHeightsProfileMap m_layer_heights_profiles; - IdToLayerConfigRangesMap m_layer_config_ranges; - IdToSlaSupportPointsMap m_sla_support_points; - IdToSlaDrainHolesMap m_sla_drain_holes; - std::string m_curr_metadata_name; - std::string m_curr_characters; - std::string m_name; - - public: - _3MF_Importer(); - ~_3MF_Importer(); - - bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version); - unsigned int version() const { return m_version; } - - private: - void _destroy_xml_parser(); - void _stop_xml_parser(const std::string& msg = std::string()); - - bool parse_error() const { return m_parse_error; } - const char* parse_error_message() const { - return m_parse_error ? - // The error was signalled by the user code, not the expat parser. - (m_parse_error_message.empty() ? "Invalid 3MF format" : m_parse_error_message.c_str()) : - // The error was signalled by the expat parser. - XML_ErrorString(XML_GetErrorCode(m_xml_parser)); - } - - bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions); - bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); - void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - - void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - - void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename); - bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); - - // handlers to parse the .model file - void _handle_start_model_xml_element(const char* name, const char** attributes); - void _handle_end_model_xml_element(const char* name); - void _handle_model_xml_characters(const XML_Char* s, int len); - - // handlers to parse the MODEL_CONFIG_FILE file - void _handle_start_config_xml_element(const char* name, const char** attributes); - void _handle_end_config_xml_element(const char* name); - - bool _handle_start_model(const char** attributes, unsigned int num_attributes); - bool _handle_end_model(); - - bool _handle_start_resources(const char** attributes, unsigned int num_attributes); - bool _handle_end_resources(); - - bool _handle_start_object(const char** attributes, unsigned int num_attributes); - bool _handle_end_object(); - - bool _handle_start_mesh(const char** attributes, unsigned int num_attributes); - bool _handle_end_mesh(); - - bool _handle_start_vertices(const char** attributes, unsigned int num_attributes); - bool _handle_end_vertices(); - - bool _handle_start_vertex(const char** attributes, unsigned int num_attributes); - bool _handle_end_vertex(); - - bool _handle_start_triangles(const char** attributes, unsigned int num_attributes); - bool _handle_end_triangles(); - - bool _handle_start_triangle(const char** attributes, unsigned int num_attributes); - bool _handle_end_triangle(); - - bool _handle_start_components(const char** attributes, unsigned int num_attributes); - bool _handle_end_components(); - - bool _handle_start_component(const char** attributes, unsigned int num_attributes); - bool _handle_end_component(); - - bool _handle_start_build(const char** attributes, unsigned int num_attributes); - bool _handle_end_build(); - - bool _handle_start_item(const char** attributes, unsigned int num_attributes); - bool _handle_end_item(); - - bool _handle_start_metadata(const char** attributes, unsigned int num_attributes); - bool _handle_end_metadata(); - - bool _create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter); - - void _apply_transform(ModelInstance& instance, const Transform3d& transform); - - bool _handle_start_config(const char** attributes, unsigned int num_attributes); - bool _handle_end_config(); - - bool _handle_start_config_object(const char** attributes, unsigned int num_attributes); - bool _handle_end_config_object(); - - bool _handle_start_config_volume(const char** attributes, unsigned int num_attributes); - bool _handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes); - bool _handle_end_config_volume(); - bool _handle_end_config_volume_mesh(); - - bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes); - bool _handle_end_config_metadata(); - - bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions); - - // callbacks to parse the .model file - static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes); - static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name); - static void XMLCALL _handle_model_xml_characters(void* userData, const XML_Char* s, int len); - - // callbacks to parse the MODEL_CONFIG_FILE file - static void XMLCALL _handle_start_config_xml_element(void* userData, const char* name, const char** attributes); - static void XMLCALL _handle_end_config_xml_element(void* userData, const char* name); - }; - - _3MF_Importer::_3MF_Importer() - : m_version(0) - , m_check_version(false) - , m_xml_parser(nullptr) - , m_model(nullptr) - , m_unit_factor(1.0f) - , m_curr_metadata_name("") - , m_curr_characters("") - , m_name("") - { - } - - _3MF_Importer::~_3MF_Importer() - { - _destroy_xml_parser(); - } - - bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version) - { - m_version = 0; - m_fdm_supports_painting_version = 0; - m_seam_painting_version = 0; - m_mm_painting_version = 0; - m_check_version = check_version; - m_model = &model; - m_unit_factor = 1.0f; - m_curr_object.reset(); - m_objects.clear(); - m_objects_aliases.clear(); - m_instances.clear(); - m_geometries.clear(); - m_curr_config.object_id = -1; - m_curr_config.volume_id = -1; - m_objects_metadata.clear(); - m_layer_heights_profiles.clear(); - m_layer_config_ranges.clear(); - m_sla_support_points.clear(); - m_curr_metadata_name.clear(); - m_curr_characters.clear(); - clear_errors(); - - return _load_model_from_file(filename, model, config, config_substitutions); - } - - void _3MF_Importer::_destroy_xml_parser() - { - if (m_xml_parser != nullptr) { - XML_ParserFree(m_xml_parser); - m_xml_parser = nullptr; - } - } - - void _3MF_Importer::_stop_xml_parser(const std::string &msg) - { - assert(! m_parse_error); - assert(m_parse_error_message.empty()); - assert(m_xml_parser != nullptr); - m_parse_error = true; - m_parse_error_message = msg; - XML_StopParser(m_xml_parser, false); - } - - bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions) - { - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - if (!open_zip_reader(&archive, filename)) { - add_error("Unable to open the file"); - return false; - } - - mz_uint num_entries = mz_zip_reader_get_num_files(&archive); - - mz_zip_archive_file_stat stat; - - m_name = boost::filesystem::path(filename).stem().string(); - - // we first loop the entries to read from the archive the .model file only, in order to extract the version from it - for (mz_uint i = 0; i < num_entries; ++i) { - if (mz_zip_reader_file_stat(&archive, i, &stat)) { - std::string name(stat.m_filename); - std::replace(name.begin(), name.end(), '\\', '/'); - - if (boost::algorithm::istarts_with(name, MODEL_FOLDER) && boost::algorithm::iends_with(name, MODEL_EXTENSION)) { - try - { - // valid model name -> extract model - if (!_extract_model_from_archive(archive, stat)) { - close_zip_reader(&archive); - add_error("Archive does not contain a valid model"); - return false; - } - } - catch (const std::exception& e) - { - // ensure the zip archive is closed and rethrow the exception - close_zip_reader(&archive); - throw Slic3r::FileIOError(e.what()); - } - } - } - } - - // we then loop again the entries to read other files stored in the archive - for (mz_uint i = 0; i < num_entries; ++i) { - if (mz_zip_reader_file_stat(&archive, i, &stat)) { - std::string name(stat.m_filename); - std::replace(name.begin(), name.end(), '\\', '/'); - - if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) { - // extract slic3r layer heights profile file - _extract_layer_heights_profile_config_from_archive(archive, stat); - } - else if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) { - // extract slic3r layer config ranges file - _extract_layer_config_ranges_from_archive(archive, stat, config_substitutions); - } - else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) { - // extract sla support points file - _extract_sla_support_points_from_archive(archive, stat); - } - else if (boost::algorithm::iequals(name, SLA_DRAIN_HOLES_FILE)) { - // extract sla support points file - _extract_sla_drain_holes_from_archive(archive, stat); - } - else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { - // extract slic3r print config file - _extract_print_config_from_archive(archive, stat, config, config_substitutions, filename); - } - else if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) { - // extract slic3r layer config ranges file - _extract_custom_gcode_per_print_z_from_archive(archive, stat); - } - else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) { - // extract slic3r model config file - if (!_extract_model_config_from_archive(archive, stat, model)) { - close_zip_reader(&archive); - add_error("Archive does not contain a valid model config"); - return false; - } - } - } - } - - close_zip_reader(&archive); - - if (m_version == 0) { - // if the 3mf was not produced by PrusaSlicer and there is more than one instance, - // split the object in as many objects as instances - size_t curr_models_count = m_model->objects.size(); - size_t i = 0; - while (i < curr_models_count) { - ModelObject* model_object = m_model->objects[i]; - if (model_object->instances.size() > 1) { - // select the geometry associated with the original model object - const Geometry* geometry = nullptr; - for (const IdToModelObjectMap::value_type& object : m_objects) { - if (object.second == int(i)) { - IdToGeometryMap::const_iterator obj_geometry = m_geometries.find(object.first); - if (obj_geometry == m_geometries.end()) { - add_error("Unable to find object geometry"); - return false; - } - geometry = &obj_geometry->second; - break; - } - } - - if (geometry == nullptr) { - add_error("Unable to find object geometry"); - return false; - } - - // use the geometry to create the volumes in the new model objects - ObjectMetadata::VolumeMetadataList volumes(1, { 0, (unsigned int)geometry->triangles.size() - 1 }); - - // for each instance after the 1st, create a new model object containing only that instance - // and copy into it the geometry - while (model_object->instances.size() > 1) { - ModelObject* new_model_object = m_model->add_object(*model_object); - new_model_object->clear_instances(); - new_model_object->add_instance(*model_object->instances.back()); - model_object->delete_last_instance(); - if (!_generate_volumes(*new_model_object, *geometry, volumes, config_substitutions)) - return false; - } - } - ++i; - } - } - - for (const IdToModelObjectMap::value_type& object : m_objects) { - if (object.second >= int(m_model->objects.size())) { - add_error("Unable to find object"); - return false; - } - ModelObject* model_object = m_model->objects[object.second]; - IdToGeometryMap::const_iterator obj_geometry = m_geometries.find(object.first); - if (obj_geometry == m_geometries.end()) { - add_error("Unable to find object geometry"); - return false; - } - - // m_layer_heights_profiles are indexed by a 1 based model object index. - IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.second + 1); - if (obj_layer_heights_profile != m_layer_heights_profiles.end()) - model_object->layer_height_profile.set(std::move(obj_layer_heights_profile->second)); - - // m_layer_config_ranges are indexed by a 1 based model object index. - IdToLayerConfigRangesMap::iterator obj_layer_config_ranges = m_layer_config_ranges.find(object.second + 1); - if (obj_layer_config_ranges != m_layer_config_ranges.end()) - model_object->layer_config_ranges = std::move(obj_layer_config_ranges->second); - - // m_sla_support_points are indexed by a 1 based model object index. - IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1); - if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { - model_object->sla_support_points = std::move(obj_sla_support_points->second); - model_object->sla_points_status = sla::PointsStatus::UserModified; - } - - IdToSlaDrainHolesMap::iterator obj_drain_holes = m_sla_drain_holes.find(object.second + 1); - if (obj_drain_holes != m_sla_drain_holes.end() && !obj_drain_holes->second.empty()) { - model_object->sla_drain_holes = std::move(obj_drain_holes->second); - } - - ObjectMetadata::VolumeMetadataList volumes; - ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr; - - IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first); - if (obj_metadata != m_objects_metadata.end()) { - // config data has been found, this model was saved using slic3r pe - - // apply object's name and config data - for (const Metadata& metadata : obj_metadata->second.metadata) { - if (metadata.key == "name") - model_object->name = metadata.value; - else - model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions); - } - - // select object's detected volumes - volumes_ptr = &obj_metadata->second.volumes; - } - else { - // config data not found, this model was not saved using slic3r pe - - // add the entire geometry as the single volume to generate - volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() - 1); - - // select as volumes - volumes_ptr = &volumes; - } - - if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions)) - return false; - } - -#if ENABLE_RELOAD_FROM_DISK_REWORK - for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) { - ModelObject* o = model.objects[obj_id]; - for (int vol_id = 0; vol_id < int(o->volumes.size()); ++vol_id) { - ModelVolume* v = o->volumes[vol_id]; - if (v->source.input_file.empty()) - v->source.input_file = v->name.empty() ? filename : v->name; - if (v->source.volume_idx == -1) - v->source.volume_idx = vol_id; - if (v->source.object_idx == -1) - v->source.object_idx = obj_id; - } - } -#else - int object_idx = 0; - for (ModelObject* o : model.objects) { - int volume_idx = 0; - for (ModelVolume* v : o->volumes) { - if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) { - v->source.input_file = filename; - if (v->source.volume_idx == -1) - v->source.volume_idx = volume_idx; - if (v->source.object_idx == -1) - v->source.object_idx = object_idx; - } - ++volume_idx; - } - ++object_idx; - } -#endif // ENABLE_RELOAD_FROM_DISK_REWORK - -// // fixes the min z of the model if negative -// model.adjust_min_z(); - - return true; - } - - bool _3MF_Importer::_extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) - { - if (stat.m_uncomp_size == 0) { - add_error("Found invalid size"); - return false; - } - - _destroy_xml_parser(); - - m_xml_parser = XML_ParserCreate(nullptr); - if (m_xml_parser == nullptr) { - add_error("Unable to create parser"); - return false; - } - - XML_SetUserData(m_xml_parser, (void*)this); - XML_SetElementHandler(m_xml_parser, _3MF_Importer::_handle_start_model_xml_element, _3MF_Importer::_handle_end_model_xml_element); - XML_SetCharacterDataHandler(m_xml_parser, _3MF_Importer::_handle_model_xml_characters); - - struct CallbackData - { - XML_Parser& parser; - _3MF_Importer& importer; - const mz_zip_archive_file_stat& stat; - - CallbackData(XML_Parser& parser, _3MF_Importer& importer, const mz_zip_archive_file_stat& stat) : parser(parser), importer(importer), stat(stat) {} - }; - - CallbackData data(m_xml_parser, *this, stat); - - mz_bool res = 0; - - try - { - res = mz_zip_reader_extract_file_to_callback(&archive, stat.m_filename, [](void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)->size_t { - CallbackData* data = (CallbackData*)pOpaque; - if (!XML_Parse(data->parser, (const char*)pBuf, (int)n, (file_ofs + n == data->stat.m_uncomp_size) ? 1 : 0) || data->importer.parse_error()) { - char error_buf[1024]; - ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", data->importer.parse_error_message(), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); - throw Slic3r::FileIOError(error_buf); - } - - return n; - }, &data, 0); - } - catch (const version_error& e) - { - // rethrow the exception - throw Slic3r::FileIOError(e.what()); - } - catch (std::exception& e) - { - add_error(e.what()); - return false; - } - - if (res == 0) { - add_error("Error while extracting model data from zip archive"); - return false; - } - - return true; - } - - void _3MF_Importer::_extract_print_config_from_archive( - mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, - DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, - const std::string& archive_filename) - { - if (stat.m_uncomp_size > 0) { - std::string buffer((size_t)stat.m_uncomp_size, 0); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) { - add_error("Error while reading config data to buffer"); - return; - } - //FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment. - // Each config line is prefixed with a semicolon (G-code comment), that is ugly. - - // Replacing the legacy function with load_from_ini_string_commented leads to issues when - // parsing 3MFs from before PrusaSlicer 2.0.0 (which can have duplicated entries in the INI. - // See https://github.com/prusa3d/PrusaSlicer/issues/7155. We'll revert it for now. - //config_substitutions.substitutions = config.load_from_ini_string_commented(std::move(buffer), config_substitutions.rule); - ConfigBase::load_from_gcode_string_legacy(config, buffer.data(), config_substitutions); - } - } - - void _3MF_Importer::_extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) - { - if (stat.m_uncomp_size > 0) { - std::string buffer((size_t)stat.m_uncomp_size, 0); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) { - add_error("Error while reading layer heights profile data to buffer"); - return; - } - - if (buffer.back() == '\n') - buffer.pop_back(); - - std::vector objects; - boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); - - for (const std::string& object : objects) { - std::vector object_data; - boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); - if (object_data.size() != 2) { - add_error("Error while reading object data"); - continue; - } - - std::vector object_data_id; - boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); - if (object_data_id.size() != 2) { - add_error("Error while reading object id"); - continue; - } - - int object_id = std::atoi(object_data_id[1].c_str()); - if (object_id == 0) { - add_error("Found invalid object id"); - continue; - } - - IdToLayerHeightsProfileMap::iterator object_item = m_layer_heights_profiles.find(object_id); - if (object_item != m_layer_heights_profiles.end()) { - add_error("Found duplicated layer heights profile"); - continue; - } - - std::vector object_data_profile; - boost::split(object_data_profile, object_data[1], boost::is_any_of(";"), boost::token_compress_off); - if (object_data_profile.size() <= 4 || object_data_profile.size() % 2 != 0) { - add_error("Found invalid layer heights profile"); - continue; - } - - std::vector profile; - profile.reserve(object_data_profile.size()); - - for (const std::string& value : object_data_profile) { - profile.push_back((coordf_t)std::atof(value.c_str())); - } - - m_layer_heights_profiles.insert({ object_id, profile }); - } - } - } - - void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions) - { - if (stat.m_uncomp_size > 0) { - std::string buffer((size_t)stat.m_uncomp_size, 0); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) { - add_error("Error while reading layer config ranges data to buffer"); - return; - } - - std::istringstream iss(buffer); // wrap returned xml to istringstream - pt::ptree objects_tree; - pt::read_xml(iss, objects_tree); - - for (const auto& object : objects_tree.get_child("objects")) { - pt::ptree object_tree = object.second; - int obj_idx = object_tree.get(".id", -1); - if (obj_idx <= 0) { - add_error("Found invalid object id"); - continue; - } - - IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(obj_idx); - if (object_item != m_layer_config_ranges.end()) { - add_error("Found duplicated layer config range"); - continue; - } - - t_layer_config_ranges config_ranges; - - for (const auto& range : object_tree) { - if (range.first != "range") - continue; - pt::ptree range_tree = range.second; - double min_z = range_tree.get(".min_z"); - double max_z = range_tree.get(".max_z"); - - // get Z range information - DynamicPrintConfig config; - - for (const auto& option : range_tree) { - if (option.first != "option") - continue; - std::string opt_key = option.second.get(".opt_key"); - std::string value = option.second.data(); - config.set_deserialize(opt_key, value, config_substitutions); - } - - config_ranges[{ min_z, max_z }].assign_config(std::move(config)); - } - - if (!config_ranges.empty()) - m_layer_config_ranges.insert({ obj_idx, std::move(config_ranges) }); - } - } - } - - void _3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) - { - if (stat.m_uncomp_size > 0) { - std::string buffer((size_t)stat.m_uncomp_size, 0); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) { - add_error("Error while reading sla support points data to buffer"); - return; - } - - if (buffer.back() == '\n') - buffer.pop_back(); - - std::vector objects; - boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); - - // Info on format versioning - see 3mf.hpp - int version = 0; - std::string key("support_points_format_version="); - if (!objects.empty() && objects[0].find(key) != std::string::npos) { - objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string - version = std::stoi(objects[0]); - objects.erase(objects.begin()); // pop the header - } - - for (const std::string& object : objects) { - std::vector object_data; - boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); - - if (object_data.size() != 2) { - add_error("Error while reading object data"); - continue; - } - - std::vector object_data_id; - boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); - if (object_data_id.size() != 2) { - add_error("Error while reading object id"); - continue; - } - - int object_id = std::atoi(object_data_id[1].c_str()); - if (object_id == 0) { - add_error("Found invalid object id"); - continue; - } - - IdToSlaSupportPointsMap::iterator object_item = m_sla_support_points.find(object_id); - if (object_item != m_sla_support_points.end()) { - add_error("Found duplicated SLA support points"); - continue; - } - - std::vector object_data_points; - boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); - - std::vector sla_support_points; - - if (version == 0) { - for (unsigned int i=0; i 0) { - std::string buffer(size_t(stat.m_uncomp_size), 0); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) { - add_error("Error while reading sla support points data to buffer"); - return; - } - - if (buffer.back() == '\n') - buffer.pop_back(); - - std::vector objects; - boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); - - // Info on format versioning - see 3mf.hpp - int version = 0; - std::string key("drain_holes_format_version="); - if (!objects.empty() && objects[0].find(key) != std::string::npos) { - objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string - version = std::stoi(objects[0]); - objects.erase(objects.begin()); // pop the header - } - - for (const std::string& object : objects) { - std::vector object_data; - boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); - - if (object_data.size() != 2) { - add_error("Error while reading object data"); - continue; - } - - std::vector object_data_id; - boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); - if (object_data_id.size() != 2) { - add_error("Error while reading object id"); - continue; - } - - int object_id = std::atoi(object_data_id[1].c_str()); - if (object_id == 0) { - add_error("Found invalid object id"); - continue; - } - - IdToSlaDrainHolesMap::iterator object_item = m_sla_drain_holes.find(object_id); - if (object_item != m_sla_drain_holes.end()) { - add_error("Found duplicated SLA drain holes"); - continue; - } - - std::vector object_data_points; - boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); - - sla::DrainHoles sla_drain_holes; - - if (version == 1) { - for (unsigned int i=0; i 0) { - std::string buffer((size_t)stat.m_uncomp_size, 0); - mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == 0) { - add_error("Error while reading custom Gcodes per height data to buffer"); - return; - } - - std::istringstream iss(buffer); // wrap returned xml to istringstream - pt::ptree main_tree; - pt::read_xml(iss, main_tree); - - if (main_tree.front().first != "custom_gcodes_per_print_z") - return; - pt::ptree code_tree = main_tree.front().second; - - m_model->custom_gcode_per_print_z.gcodes.clear(); - - for (const auto& code : code_tree) { - if (code.first == "mode") { - pt::ptree tree = code.second; - std::string mode = tree.get(".value"); - m_model->custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder : - mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle : - CustomGCode::Mode::MultiExtruder; - } - if (code.first != "code") - continue; - - pt::ptree tree = code.second; - double print_z = tree.get (".print_z" ); - int extruder = tree.get (".extruder"); - std::string color = tree.get (".color" ); - - CustomGCode::Type type; - std::string extra; - pt::ptree attr_tree = tree.find("")->second; - if (attr_tree.find("type") == attr_tree.not_found()) { - // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer - // read old data ... - std::string gcode = tree.get (".gcode"); - // ... and interpret them to the new data - type = gcode == "M600" ? CustomGCode::ColorChange : - gcode == "M601" ? CustomGCode::PausePrint : - gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom; - extra = type == CustomGCode::PausePrint ? color : - type == CustomGCode::Custom ? gcode : ""; - } - else { - type = static_cast(tree.get(".type")); - extra = tree.get(".extra"); - } - m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ; - } - } - } - - void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes) - { - if (m_xml_parser == nullptr) - return; - - bool res = true; - unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser); - - if (::strcmp(MODEL_TAG, name) == 0) - res = _handle_start_model(attributes, num_attributes); - else if (::strcmp(RESOURCES_TAG, name) == 0) - res = _handle_start_resources(attributes, num_attributes); - else if (::strcmp(OBJECT_TAG, name) == 0) - res = _handle_start_object(attributes, num_attributes); - else if (::strcmp(MESH_TAG, name) == 0) - res = _handle_start_mesh(attributes, num_attributes); - else if (::strcmp(VERTICES_TAG, name) == 0) - res = _handle_start_vertices(attributes, num_attributes); - else if (::strcmp(VERTEX_TAG, name) == 0) - res = _handle_start_vertex(attributes, num_attributes); - else if (::strcmp(TRIANGLES_TAG, name) == 0) - res = _handle_start_triangles(attributes, num_attributes); - else if (::strcmp(TRIANGLE_TAG, name) == 0) - res = _handle_start_triangle(attributes, num_attributes); - else if (::strcmp(COMPONENTS_TAG, name) == 0) - res = _handle_start_components(attributes, num_attributes); - else if (::strcmp(COMPONENT_TAG, name) == 0) - res = _handle_start_component(attributes, num_attributes); - else if (::strcmp(BUILD_TAG, name) == 0) - res = _handle_start_build(attributes, num_attributes); - else if (::strcmp(ITEM_TAG, name) == 0) - res = _handle_start_item(attributes, num_attributes); - else if (::strcmp(METADATA_TAG, name) == 0) - res = _handle_start_metadata(attributes, num_attributes); - - if (!res) - _stop_xml_parser(); - } - - void _3MF_Importer::_handle_end_model_xml_element(const char* name) - { - if (m_xml_parser == nullptr) - return; - - bool res = true; - - if (::strcmp(MODEL_TAG, name) == 0) - res = _handle_end_model(); - else if (::strcmp(RESOURCES_TAG, name) == 0) - res = _handle_end_resources(); - else if (::strcmp(OBJECT_TAG, name) == 0) - res = _handle_end_object(); - else if (::strcmp(MESH_TAG, name) == 0) - res = _handle_end_mesh(); - else if (::strcmp(VERTICES_TAG, name) == 0) - res = _handle_end_vertices(); - else if (::strcmp(VERTEX_TAG, name) == 0) - res = _handle_end_vertex(); - else if (::strcmp(TRIANGLES_TAG, name) == 0) - res = _handle_end_triangles(); - else if (::strcmp(TRIANGLE_TAG, name) == 0) - res = _handle_end_triangle(); - else if (::strcmp(COMPONENTS_TAG, name) == 0) - res = _handle_end_components(); - else if (::strcmp(COMPONENT_TAG, name) == 0) - res = _handle_end_component(); - else if (::strcmp(BUILD_TAG, name) == 0) - res = _handle_end_build(); - else if (::strcmp(ITEM_TAG, name) == 0) - res = _handle_end_item(); - else if (::strcmp(METADATA_TAG, name) == 0) - res = _handle_end_metadata(); - - if (!res) - _stop_xml_parser(); - } - - void _3MF_Importer::_handle_model_xml_characters(const XML_Char* s, int len) - { - m_curr_characters.append(s, len); - } - - void _3MF_Importer::_handle_start_config_xml_element(const char* name, const char** attributes) - { - if (m_xml_parser == nullptr) - return; - - bool res = true; - unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser); - - if (::strcmp(CONFIG_TAG, name) == 0) - res = _handle_start_config(attributes, num_attributes); - else if (::strcmp(OBJECT_TAG, name) == 0) - res = _handle_start_config_object(attributes, num_attributes); - else if (::strcmp(VOLUME_TAG, name) == 0) - res = _handle_start_config_volume(attributes, num_attributes); - else if (::strcmp(MESH_TAG, name) == 0) - res = _handle_start_config_volume_mesh(attributes, num_attributes); - else if (::strcmp(METADATA_TAG, name) == 0) - res = _handle_start_config_metadata(attributes, num_attributes); - - if (!res) - _stop_xml_parser(); - } - - void _3MF_Importer::_handle_end_config_xml_element(const char* name) - { - if (m_xml_parser == nullptr) - return; - - bool res = true; - - if (::strcmp(CONFIG_TAG, name) == 0) - res = _handle_end_config(); - else if (::strcmp(OBJECT_TAG, name) == 0) - res = _handle_end_config_object(); - else if (::strcmp(VOLUME_TAG, name) == 0) - res = _handle_end_config_volume(); - else if (::strcmp(MESH_TAG, name) == 0) - res = _handle_end_config_volume_mesh(); - else if (::strcmp(METADATA_TAG, name) == 0) - res = _handle_end_config_metadata(); - - if (!res) - _stop_xml_parser(); - } - - bool _3MF_Importer::_handle_start_model(const char** attributes, unsigned int num_attributes) - { - m_unit_factor = get_unit_factor(get_attribute_value_string(attributes, num_attributes, UNIT_ATTR)); - return true; - } - - bool _3MF_Importer::_handle_end_model() - { - // deletes all non-built or non-instanced objects - for (const IdToModelObjectMap::value_type& object : m_objects) { - if (object.second >= int(m_model->objects.size())) { - add_error("Unable to find object"); - return false; - } - ModelObject *model_object = m_model->objects[object.second]; - if (model_object != nullptr && model_object->instances.size() == 0) - m_model->delete_object(model_object); - } - - if (m_version == 0) { - // if the 3mf was not produced by PrusaSlicer and there is only one object, - // set the object name to match the filename - if (m_model->objects.size() == 1) - m_model->objects.front()->name = m_name; - } - - // applies instances' matrices - for (Instance& instance : m_instances) { - if (instance.instance != nullptr && instance.instance->get_object() != nullptr) - // apply the transform to the instance - _apply_transform(*instance.instance, instance.transform); - } - - return true; - } - - bool _3MF_Importer::_handle_start_resources(const char** attributes, unsigned int num_attributes) - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_end_resources() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_object(const char** attributes, unsigned int num_attributes) - { - // reset current data - m_curr_object.reset(); - - if (is_valid_object_type(get_attribute_value_string(attributes, num_attributes, TYPE_ATTR))) { - // create new object (it may be removed later if no instances are generated from it) - m_curr_object.model_object_idx = (int)m_model->objects.size(); - m_curr_object.object = m_model->add_object(); - if (m_curr_object.object == nullptr) { - add_error("Unable to create object"); - return false; - } - - // set object data - m_curr_object.object->name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR); - if (m_curr_object.object->name.empty()) - m_curr_object.object->name = m_name + "_" + std::to_string(m_model->objects.size()); - - m_curr_object.id = get_attribute_value_int(attributes, num_attributes, ID_ATTR); - } - - return true; - } - - bool _3MF_Importer::_handle_end_object() - { - if (m_curr_object.object != nullptr) { - if (m_curr_object.geometry.empty()) { - // no geometry defined - // remove the object from the model - m_model->delete_object(m_curr_object.object); - - if (m_curr_object.components.empty()) { - // no components defined -> invalid object, delete it - IdToModelObjectMap::iterator object_item = m_objects.find(m_curr_object.id); - if (object_item != m_objects.end()) - m_objects.erase(object_item); - - IdToAliasesMap::iterator alias_item = m_objects_aliases.find(m_curr_object.id); - if (alias_item != m_objects_aliases.end()) - m_objects_aliases.erase(alias_item); - } - else - // adds components to aliases - m_objects_aliases.insert({ m_curr_object.id, m_curr_object.components }); - } - else { - // geometry defined, store it for later use - m_geometries.insert({ m_curr_object.id, std::move(m_curr_object.geometry) }); - - // stores the object for later use - if (m_objects.find(m_curr_object.id) == m_objects.end()) { - m_objects.insert({ m_curr_object.id, m_curr_object.model_object_idx }); - m_objects_aliases.insert({ m_curr_object.id, { 1, Component(m_curr_object.id) } }); // aliases itself - } - else { - add_error("Found object with duplicate id"); - return false; - } - } - } - - return true; - } - - bool _3MF_Importer::_handle_start_mesh(const char** attributes, unsigned int num_attributes) - { - // reset current geometry - m_curr_object.geometry.reset(); - return true; - } - - bool _3MF_Importer::_handle_end_mesh() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_vertices(const char** attributes, unsigned int num_attributes) - { - // reset current vertices - m_curr_object.geometry.vertices.clear(); - return true; - } - - bool _3MF_Importer::_handle_end_vertices() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_vertex(const char** attributes, unsigned int num_attributes) - { - // appends the vertex coordinates - // missing values are set equal to ZERO - m_curr_object.geometry.vertices.emplace_back( - m_unit_factor * get_attribute_value_float(attributes, num_attributes, X_ATTR), - m_unit_factor * get_attribute_value_float(attributes, num_attributes, Y_ATTR), - m_unit_factor * get_attribute_value_float(attributes, num_attributes, Z_ATTR)); - return true; - } - - bool _3MF_Importer::_handle_end_vertex() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_triangles(const char** attributes, unsigned int num_attributes) - { - // reset current triangles - m_curr_object.geometry.triangles.clear(); - return true; - } - - bool _3MF_Importer::_handle_end_triangles() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_triangle(const char** attributes, unsigned int num_attributes) - { - // we are ignoring the following attributes: - // p1 - // p2 - // p3 - // pid - // see specifications - - // appends the triangle's vertices indices - // missing values are set equal to ZERO - m_curr_object.geometry.triangles.emplace_back( - get_attribute_value_int(attributes, num_attributes, V1_ATTR), - get_attribute_value_int(attributes, num_attributes, V2_ATTR), - get_attribute_value_int(attributes, num_attributes, V3_ATTR)); - - m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); - m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); - m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR)); - return true; - } - - bool _3MF_Importer::_handle_end_triangle() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_components(const char** attributes, unsigned int num_attributes) - { - // reset current components - m_curr_object.components.clear(); - return true; - } - - bool _3MF_Importer::_handle_end_components() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes) - { - int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); - Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); - - IdToModelObjectMap::iterator object_item = m_objects.find(object_id); - if (object_item == m_objects.end()) { - IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id); - if (alias_item == m_objects_aliases.end()) { - add_error("Found component with invalid object id"); - return false; - } - } - - m_curr_object.components.emplace_back(object_id, transform); - - return true; - } - - bool _3MF_Importer::_handle_end_component() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_build(const char** attributes, unsigned int num_attributes) - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_end_build() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_item(const char** attributes, unsigned int num_attributes) - { - // we are ignoring the following attributes - // thumbnail - // partnumber - // pid - // pindex - // see specifications - - int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); - Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); - int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR); - - return _create_object_instance(object_id, transform, printable, 1); - } - - bool _3MF_Importer::_handle_end_item() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_metadata(const char** attributes, unsigned int num_attributes) - { - m_curr_characters.clear(); - - std::string name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR); - if (!name.empty()) - m_curr_metadata_name = name; - - return true; - } - - inline static void check_painting_version(unsigned int loaded_version, unsigned int highest_supported_version, const std::string &error_msg) - { - if (loaded_version > highest_supported_version) - throw version_error(error_msg); - } - - bool _3MF_Importer::_handle_end_metadata() - { - if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION) { - m_version = (unsigned int)atoi(m_curr_characters.c_str()); - if (m_check_version && (m_version > VERSION_3MF_COMPATIBLE)) { - // std::string msg = _(L("The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible.")); - // throw version_error(msg.c_str()); - const std::string msg = (boost::format(_(L("The selected 3mf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str(); - throw version_error(msg); - } - } else if (m_curr_metadata_name == "Application") { - // Generator application of the 3MF. - // SLIC3R_APP_KEY - SLIC3R_VERSION - if (boost::starts_with(m_curr_characters, "PrusaSlicer-")) - m_prusaslicer_generator_version = Semver::parse(m_curr_characters.substr(12)); - } else if (m_curr_metadata_name == SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION) { - m_fdm_supports_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); - check_painting_version(m_fdm_supports_painting_version, FDM_SUPPORTS_PAINTING_VERSION, - _(L("The selected 3MF contains FDM supports painted object using a newer version of PrusaSlicer and is not compatible."))); - } else if (m_curr_metadata_name == SLIC3RPE_SEAM_PAINTING_VERSION) { - m_seam_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); - check_painting_version(m_seam_painting_version, SEAM_PAINTING_VERSION, - _(L("The selected 3MF contains seam painted object using a newer version of PrusaSlicer and is not compatible."))); - } else if (m_curr_metadata_name == SLIC3RPE_MM_PAINTING_VERSION) { - m_mm_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); - check_painting_version(m_mm_painting_version, MM_PAINTING_VERSION, - _(L("The selected 3MF contains multi-material painted object using a newer version of PrusaSlicer and is not compatible."))); - } - - return true; - } - - bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) - { - static const unsigned int MAX_RECURSIONS = 10; - - // escape from circular aliasing - if (recur_counter > MAX_RECURSIONS) { - add_error("Too many recursions"); - return false; - } - - IdToAliasesMap::iterator it = m_objects_aliases.find(object_id); - if (it == m_objects_aliases.end()) { - add_error("Found item with invalid object id"); - return false; - } - - if (it->second.size() == 1 && it->second[0].object_id == object_id) { - // aliasing to itself - - IdToModelObjectMap::iterator object_item = m_objects.find(object_id); - if (object_item == m_objects.end() || object_item->second == -1) { - add_error("Found invalid object"); - return false; - } - else { - ModelInstance* instance = m_model->objects[object_item->second]->add_instance(); - if (instance == nullptr) { - add_error("Unable to add object instance"); - return false; - } - instance->printable = printable; - - m_instances.emplace_back(instance, transform); - } - } - else { - // recursively process nested components - for (const Component& component : it->second) { - if (!_create_object_instance(component.object_id, transform * component.transform, printable, recur_counter + 1)) - return false; - } - } - - return true; - } - - void _3MF_Importer::_apply_transform(ModelInstance& instance, const Transform3d& transform) - { - Slic3r::Geometry::Transformation t(transform); - // invalid scale value, return - if (!t.get_scaling_factor().all()) - return; - - instance.set_transformation(t); - } - - bool _3MF_Importer::_handle_start_config(const char** attributes, unsigned int num_attributes) - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_end_config() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_config_object(const char** attributes, unsigned int num_attributes) - { - int object_id = get_attribute_value_int(attributes, num_attributes, ID_ATTR); - IdToMetadataMap::iterator object_item = m_objects_metadata.find(object_id); - if (object_item != m_objects_metadata.end()) { - add_error("Found duplicated object id"); - return false; - } - - // Added because of github #3435, currently not used by PrusaSlicer - // int instances_count_id = get_attribute_value_int(attributes, num_attributes, INSTANCESCOUNT_ATTR); - - m_objects_metadata.insert({ object_id, ObjectMetadata() }); - m_curr_config.object_id = object_id; - return true; - } - - bool _3MF_Importer::_handle_end_config_object() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_config_volume(const char** attributes, unsigned int num_attributes) - { - IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); - if (object == m_objects_metadata.end()) { - add_error("Cannot assign volume to a valid object"); - return false; - } - - m_curr_config.volume_id = (int)object->second.volumes.size(); - - unsigned int first_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, FIRST_TRIANGLE_ID_ATTR); - unsigned int last_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, LAST_TRIANGLE_ID_ATTR); - - object->second.volumes.emplace_back(first_triangle_id, last_triangle_id); - return true; - } - - bool _3MF_Importer::_handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes) - { - IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); - if (object == m_objects_metadata.end()) { - add_error("Cannot assign volume mesh to a valid object"); - return false; - } - if (object->second.volumes.empty()) { - add_error("Cannot assign mesh to a valid olume"); - return false; - } - - ObjectMetadata::VolumeMetadata& volume = object->second.volumes.back(); - - int edges_fixed = get_attribute_value_int(attributes, num_attributes, MESH_STAT_EDGES_FIXED ); - int degenerate_facets = get_attribute_value_int(attributes, num_attributes, MESH_STAT_DEGENERATED_FACETS); - int facets_removed = get_attribute_value_int(attributes, num_attributes, MESH_STAT_FACETS_REMOVED ); - int facets_reversed = get_attribute_value_int(attributes, num_attributes, MESH_STAT_FACETS_RESERVED ); - int backwards_edges = get_attribute_value_int(attributes, num_attributes, MESH_STAT_BACKWARDS_EDGES ); - - volume.mesh_stats = { edges_fixed, degenerate_facets, facets_removed, facets_reversed, backwards_edges }; - - return true; - } - - bool _3MF_Importer::_handle_end_config_volume() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_end_config_volume_mesh() - { - // do nothing - return true; - } - - bool _3MF_Importer::_handle_start_config_metadata(const char** attributes, unsigned int num_attributes) - { - IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); - if (object == m_objects_metadata.end()) { - add_error("Cannot assign metadata to valid object id"); - return false; - } - - std::string type = get_attribute_value_string(attributes, num_attributes, TYPE_ATTR); - std::string key = get_attribute_value_string(attributes, num_attributes, KEY_ATTR); - std::string value = get_attribute_value_string(attributes, num_attributes, VALUE_ATTR); - - if (type == OBJECT_TYPE) - object->second.metadata.emplace_back(key, value); - else if (type == VOLUME_TYPE) { - if (size_t(m_curr_config.volume_id) < object->second.volumes.size()) - object->second.volumes[m_curr_config.volume_id].metadata.emplace_back(key, value); - } - else { - add_error("Found invalid metadata type"); - return false; - } - - return true; - } - - bool _3MF_Importer::_handle_end_config_metadata() - { - // do nothing - return true; - } - - bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions) - { - if (!object.volumes.empty()) { - add_error("Found invalid volumes count"); - return false; - } - - unsigned int geo_tri_count = (unsigned int)geometry.triangles.size(); - unsigned int renamed_volumes_count = 0; - - for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) { - if (geo_tri_count <= volume_data.first_triangle_id || geo_tri_count <= volume_data.last_triangle_id || volume_data.last_triangle_id < volume_data.first_triangle_id) { - add_error("Found invalid triangle id"); - return false; - } - - Transform3d volume_matrix_to_object = Transform3d::Identity(); - bool has_transform = false; - // extract the volume transformation from the volume's metadata, if present - for (const Metadata& metadata : volume_data.metadata) { - if (metadata.key == MATRIX_KEY) { - volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value); - has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10); - break; - } - } - - // splits volume out of imported geometry - indexed_triangle_set its; - its.indices.assign(geometry.triangles.begin() + volume_data.first_triangle_id, geometry.triangles.begin() + volume_data.last_triangle_id + 1); - const size_t triangles_count = its.indices.size(); - if (triangles_count == 0) { - add_error("An empty triangle mesh found"); - return false; - } - - { - int min_id = its.indices.front()[0]; - int max_id = min_id; - for (const Vec3i& face : its.indices) { - for (const int tri_id : face) { - if (tri_id < 0 || tri_id >= int(geometry.vertices.size())) { - add_error("Found invalid vertex id"); - return false; - } - min_id = std::min(min_id, tri_id); - max_id = std::max(max_id, tri_id); - } - } - its.vertices.assign(geometry.vertices.begin() + min_id, geometry.vertices.begin() + max_id + 1); - - // rebase indices to the current vertices list - for (Vec3i& face : its.indices) - for (int& tri_id : face) - tri_id -= min_id; - } - - if (m_prusaslicer_generator_version && - *m_prusaslicer_generator_version >= *Semver::parse("2.4.0-alpha1") && - *m_prusaslicer_generator_version < *Semver::parse("2.4.0-alpha3")) - // PrusaSlicer 2.4.0-alpha2 contained a bug, where all vertices of a single object were saved for each volume the object contained. - // Remove the vertices, that are not referenced by any face. - its_compactify_vertices(its, true); - - TriangleMesh triangle_mesh(std::move(its), volume_data.mesh_stats); - - if (m_version == 0) { - // if the 3mf was not produced by PrusaSlicer and there is only one instance, - // bake the transformation into the geometry to allow the reload from disk command - // to work properly - if (object.instances.size() == 1) { - triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix(), false); - object.instances.front()->set_transformation(Slic3r::Geometry::Transformation()); - //FIXME do the mesh fixing? - } - } - if (triangle_mesh.volume() < 0) - triangle_mesh.flip_triangles(); - - ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); - // stores the volume matrix taken from the metadata, if present - if (has_transform) - volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); - - // recreate custom supports, seam and mmu segmentation from previously loaded attribute - volume->supported_facets.reserve(triangles_count); - volume->seam_facets.reserve(triangles_count); - volume->mmu_segmentation_facets.reserve(triangles_count); - for (size_t i=0; isupported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); - if (! geometry.custom_seam[index].empty()) - volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); - if (! geometry.mmu_segmentation[index].empty()) - volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]); - } - volume->supported_facets.shrink_to_fit(); - volume->seam_facets.shrink_to_fit(); - volume->mmu_segmentation_facets.shrink_to_fit(); - - // apply the remaining volume's metadata - for (const Metadata& metadata : volume_data.metadata) { - if (metadata.key == NAME_KEY) - volume->name = metadata.value; - else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1")) - volume->set_type(ModelVolumeType::PARAMETER_MODIFIER); - else if (metadata.key == VOLUME_TYPE_KEY) - volume->set_type(ModelVolume::type_from_string(metadata.value)); - else if (metadata.key == SOURCE_FILE_KEY) - volume->source.input_file = metadata.value; - else if (metadata.key == SOURCE_OBJECT_ID_KEY) - volume->source.object_idx = ::atoi(metadata.value.c_str()); - else if (metadata.key == SOURCE_VOLUME_ID_KEY) - volume->source.volume_idx = ::atoi(metadata.value.c_str()); - else if (metadata.key == SOURCE_OFFSET_X_KEY) - volume->source.mesh_offset.x() = ::atof(metadata.value.c_str()); - else if (metadata.key == SOURCE_OFFSET_Y_KEY) - volume->source.mesh_offset.y() = ::atof(metadata.value.c_str()); - else if (metadata.key == SOURCE_OFFSET_Z_KEY) - volume->source.mesh_offset.z() = ::atof(metadata.value.c_str()); - else if (metadata.key == SOURCE_IN_INCHES_KEY) - volume->source.is_converted_from_inches = metadata.value == "1"; - else if (metadata.key == SOURCE_IN_METERS_KEY) - volume->source.is_converted_from_meters = metadata.value == "1"; -#if ENABLE_RELOAD_FROM_DISK_REWORK - else if (metadata.key == SOURCE_IS_BUILTIN_VOLUME_KEY) - volume->source.is_from_builtin_objects = metadata.value == "1"; -#endif // ENABLE_RELOAD_FROM_DISK_REWORK - else - volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); - } - - // this may happen for 3mf saved by 3rd part softwares - if (volume->name.empty()) { - volume->name = object.name; - if (renamed_volumes_count > 0) - volume->name += "_" + std::to_string(renamed_volumes_count + 1); - ++renamed_volumes_count; - } - } - - return true; - } - - void XMLCALL _3MF_Importer::_handle_start_model_xml_element(void* userData, const char* name, const char** attributes) - { - _3MF_Importer* importer = (_3MF_Importer*)userData; - if (importer != nullptr) - importer->_handle_start_model_xml_element(name, attributes); - } - - void XMLCALL _3MF_Importer::_handle_end_model_xml_element(void* userData, const char* name) - { - _3MF_Importer* importer = (_3MF_Importer*)userData; - if (importer != nullptr) - importer->_handle_end_model_xml_element(name); - } - - void XMLCALL _3MF_Importer::_handle_model_xml_characters(void* userData, const XML_Char* s, int len) - { - _3MF_Importer* importer = (_3MF_Importer*)userData; - if (importer != nullptr) - importer->_handle_model_xml_characters(s, len); - } - - void XMLCALL _3MF_Importer::_handle_start_config_xml_element(void* userData, const char* name, const char** attributes) - { - _3MF_Importer* importer = (_3MF_Importer*)userData; - if (importer != nullptr) - importer->_handle_start_config_xml_element(name, attributes); - } - - void XMLCALL _3MF_Importer::_handle_end_config_xml_element(void* userData, const char* name) - { - _3MF_Importer* importer = (_3MF_Importer*)userData; - if (importer != nullptr) - importer->_handle_end_config_xml_element(name); - } - - class _3MF_Exporter : public _3MF_Base - { - struct BuildItem - { - unsigned int id; - Transform3d transform; - bool printable; - - BuildItem(unsigned int id, const Transform3d& transform, const bool printable) - : id(id) - , transform(transform) - , printable(printable) - { - } - }; - - struct Offsets - { - unsigned int first_vertex_id; - unsigned int first_triangle_id; - unsigned int last_triangle_id; - - Offsets(unsigned int first_vertex_id) - : first_vertex_id(first_vertex_id) - , first_triangle_id(-1) - , last_triangle_id(-1) - { - } - }; - - typedef std::map VolumeToOffsetsMap; - - struct ObjectData - { - ModelObject* object; - VolumeToOffsetsMap volumes_offsets; - - explicit ObjectData(ModelObject* object) - : object(object) - { - } - }; - - typedef std::vector BuildItemsList; - typedef std::map IdToObjectDataMap; - - bool m_fullpath_sources{ true }; - bool m_zip64 { true }; - - public: - bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64); - - private: - bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data); - bool _add_content_types_file_to_archive(mz_zip_archive& archive); - bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data); - bool _add_relationships_file_to_archive(mz_zip_archive& archive); - bool _add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data); - bool _add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets); - bool _add_mesh_to_object_stream(mz_zip_writer_staged_context &context, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); - bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); - bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); - bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); - bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); - bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model); - bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); - bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); - bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); - }; - - bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) - { - clear_errors(); - m_fullpath_sources = fullpath_sources; - m_zip64 = zip64; - return _save_model_to_file(filename, model, config, thumbnail_data); - } - - bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data) - { - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - if (!open_zip_writer(&archive, filename)) { - add_error("Unable to open the file"); - return false; - } - - // Adds content types file ("[Content_Types].xml";). - // The content of this file is the same for each PrusaSlicer 3mf. - if (!_add_content_types_file_to_archive(archive)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - if (thumbnail_data != nullptr && thumbnail_data->is_valid()) { - // Adds the file Metadata/thumbnail.png. - if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - } - - // Adds relationships file ("_rels/.rels"). - // The content of this file is the same for each PrusaSlicer 3mf. - // The relationshis file contains a reference to the geometry file "3D/3dmodel.model", the name was chosen to be compatible with CURA. - if (!_add_relationships_file_to_archive(archive)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - // Adds model file ("3D/3dmodel.model"). - // This is the one and only file that contains all the geometry (vertices and triangles) of all ModelVolumes. - IdToObjectDataMap objects_data; - if (!_add_model_file_to_archive(filename, archive, model, objects_data)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - // Adds layer height profile file ("Metadata/Slic3r_PE_layer_heights_profile.txt"). - // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. - // The index differes from the index of an object ID of an object instance of a 3MF file! - if (!_add_layer_height_profile_file_to_archive(archive, model)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - // Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt"). - // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. - // The index differes from the index of an object ID of an object instance of a 3MF file! - if (!_add_layer_config_ranges_file_to_archive(archive, model)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - // Adds sla support points file ("Metadata/Slic3r_PE_sla_support_points.txt"). - // All sla support points of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. - // The index differes from the index of an object ID of an object instance of a 3MF file! - if (!_add_sla_support_points_file_to_archive(archive, model)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - if (!_add_sla_drain_holes_file_to_archive(archive, model)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - - // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"). - // All custom gcode per height of whole Model are stored here - if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model, config)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - // Adds slic3r print config file ("Metadata/Slic3r_PE.config"). - // This file contains the content of FullPrintConfing / SLAFullPrintConfig. - if (config != nullptr) { - if (!_add_print_config_file_to_archive(archive, *config)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - } - - // Adds slic3r model config file ("Metadata/Slic3r_PE_model.config"). - // This file contains all the attributes of all ModelObjects and their ModelVolumes (names, parameter overrides). - // As there is just a single Indexed Triangle Set data stored per ModelObject, offsets of volumes into their respective Indexed Triangle Set data - // is stored here as well. - if (!_add_model_config_file_to_archive(archive, model, objects_data)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - - if (!mz_zip_writer_finalize_archive(&archive)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - add_error("Unable to finalize the archive"); - return false; - } - - close_zip_writer(&archive); - - return true; - } - - bool _3MF_Exporter::_add_content_types_file_to_archive(mz_zip_archive& archive) - { - std::stringstream stream; - stream << "\n"; - stream << "\n"; - stream << " \n"; - stream << " \n"; - stream << " \n"; - stream << ""; - - std::string out = stream.str(); - - if (!mz_zip_writer_add_mem(&archive, CONTENT_TYPES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add content types file to archive"); - return false; - } - - return true; - } - - bool _3MF_Exporter::_add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data) - { - bool res = false; - - size_t png_size = 0; - void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); - if (png_data != nullptr) { - res = mz_zip_writer_add_mem(&archive, THUMBNAIL_FILE.c_str(), (const void*)png_data, png_size, MZ_DEFAULT_COMPRESSION); - mz_free(png_data); - } - - if (!res) - add_error("Unable to add thumbnail file to archive"); - - return res; - } - - bool _3MF_Exporter::_add_relationships_file_to_archive(mz_zip_archive& archive) - { - std::stringstream stream; - stream << "\n"; - stream << "\n"; - stream << " \n"; - stream << " \n"; - stream << ""; - - std::string out = stream.str(); - - if (!mz_zip_writer_add_mem(&archive, RELATIONSHIPS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add relationships file to archive"); - return false; - } - - return true; - } - - static void reset_stream(std::stringstream &stream) - { - stream.str(""); - stream.clear(); - // https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 - // Conversion of a floating-point value to text and back is exact as long as at least max_digits10 were used (9 for float, 17 for double). - // It is guaranteed to produce the same floating-point value, even though the intermediate text representation is not exact. - // The default value of std::stream precision is 6 digits only! - stream << std::setprecision(std::numeric_limits::max_digits10); - } - - bool _3MF_Exporter::_add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data) - { - mz_zip_writer_staged_context context; - if (!mz_zip_writer_add_staged_open(&archive, &context, MODEL_FILE.c_str(), - m_zip64 ? - // Maximum expected and allowed 3MF file size is 16GiB. - // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records. - (uint64_t(1) << 30) * 16 : - // Maximum expected 3MF file size is 4GB-1. This is a workaround for interoperability with Windows 10 3D model fixing API, see - // GH issue #6193. - (uint64_t(1) << 32) - 1, - nullptr, nullptr, 0, MZ_DEFAULT_COMPRESSION, nullptr, 0, nullptr, 0)) { - add_error("Unable to add model file to archive"); - return false; - } - - { - std::stringstream stream; - reset_stream(stream); - stream << "\n"; - stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n"; - stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "\n"; - - if (model.is_fdm_support_painted()) - stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION << "\">" << FDM_SUPPORTS_PAINTING_VERSION << "\n"; - - if (model.is_seam_painted()) - stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_SEAM_PAINTING_VERSION << "\">" << SEAM_PAINTING_VERSION << "\n"; - - if (model.is_mm_painted()) - stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_MM_PAINTING_VERSION << "\">" << MM_PAINTING_VERSION << "\n"; - - std::string name = xml_escape(boost::filesystem::path(filename).stem().string()); - stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "\n"; - stream << " <" << METADATA_TAG << " name=\"Designer\">" << "\n"; - stream << " <" << METADATA_TAG << " name=\"Description\">" << name << "\n"; - stream << " <" << METADATA_TAG << " name=\"Copyright\">" << "\n"; - stream << " <" << METADATA_TAG << " name=\"LicenseTerms\">" << "\n"; - stream << " <" << METADATA_TAG << " name=\"Rating\">" << "\n"; - std::string date = Slic3r::Utils::utc_timestamp(Slic3r::Utils::get_current_time_utc()); - // keep only the date part of the string - date = date.substr(0, 10); - stream << " <" << METADATA_TAG << " name=\"CreationDate\">" << date << "\n"; - stream << " <" << METADATA_TAG << " name=\"ModificationDate\">" << date << "\n"; - stream << " <" << METADATA_TAG << " name=\"Application\">" << SLIC3R_APP_KEY << "-" << SLIC3R_VERSION << "\n"; - stream << " <" << RESOURCES_TAG << ">\n"; - std::string buf = stream.str(); - if (! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) { - add_error("Unable to add model file to archive"); - return false; - } - } - - // Instance transformations, indexed by the 3MF object ID (which is a linear serialization of all instances of all ModelObjects). - BuildItemsList build_items; - - // The object_id here is a one based identifier of the first instance of a ModelObject in the 3MF file, where - // all the object instances of all ModelObjects are stored and indexed in a 1 based linear fashion. - // Therefore the list of object_ids here may not be continuous. - unsigned int object_id = 1; - for (ModelObject* obj : model.objects) { - if (obj == nullptr) - continue; - - // Index of an object in the 3MF file corresponding to the 1st instance of a ModelObject. - unsigned int curr_id = object_id; - IdToObjectDataMap::iterator object_it = objects_data.insert({ curr_id, ObjectData(obj) }).first; - // Store geometry of all ModelVolumes contained in a single ModelObject into a single 3MF indexed triangle set object. - // object_it->second.volumes_offsets will contain the offsets of the ModelVolumes in that single indexed triangle set. - // object_id will be increased to point to the 1st instance of the next ModelObject. - if (!_add_object_to_model_stream(context, object_id, *obj, build_items, object_it->second.volumes_offsets)) { - add_error("Unable to add object to archive"); - mz_zip_writer_add_staged_finish(&context); - return false; - } - } - - { - std::stringstream stream; - reset_stream(stream); - stream << " \n"; - - // Store the transformations of all the ModelInstances of all ModelObjects, indexed in a linear fashion. - if (!_add_build_to_model_stream(stream, build_items)) { - add_error("Unable to add build to archive"); - mz_zip_writer_add_staged_finish(&context); - return false; - } - - stream << "\n"; - - std::string buf = stream.str(); - - if ((! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) || - ! mz_zip_writer_add_staged_finish(&context)) { - add_error("Unable to add model file to archive"); - return false; - } - } - - return true; - } - - bool _3MF_Exporter::_add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets) - { - std::stringstream stream; - reset_stream(stream); - unsigned int id = 0; - for (const ModelInstance* instance : object.instances) { - assert(instance != nullptr); - if (instance == nullptr) - continue; - - unsigned int instance_id = object_id + id; - stream << " <" << OBJECT_TAG << " id=\"" << instance_id << "\" type=\"model\">\n"; - - if (id == 0) { - std::string buf = stream.str(); - reset_stream(stream); - if ((! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) || - ! _add_mesh_to_object_stream(context, object, volumes_offsets)) { - add_error("Unable to add mesh to archive"); - return false; - } - } - else { - stream << " <" << COMPONENTS_TAG << ">\n"; - stream << " <" << COMPONENT_TAG << " objectid=\"" << object_id << "\"/>\n"; - stream << " \n"; - } - - Transform3d t = instance->get_matrix(); - // instance_id is just a 1 indexed index in build_items. - assert(instance_id == build_items.size() + 1); - build_items.emplace_back(instance_id, t, instance->printable); - - stream << " \n"; - - ++id; - } - - object_id += id; - std::string buf = stream.str(); - return buf.empty() || mz_zip_writer_add_staged_data(&context, buf.data(), buf.size()); - } - -#if EXPORT_3MF_USE_SPIRIT_KARMA_FP - template - struct coordinate_policy_fixed : boost::spirit::karma::real_policies - { - static int floatfield(Num n) { return fmtflags::fixed; } - // Number of decimal digits to maintain float accuracy when storing into a text file and parsing back. - static unsigned precision(Num /* n */) { return std::numeric_limits::max_digits10 + 1; } - // No trailing zeros, thus for fmtflags::fixed usually much less than max_digits10 decimal numbers will be produced. - static bool trailing_zeros(Num /* n */) { return false; } - }; - template - struct coordinate_policy_scientific : coordinate_policy_fixed - { - static int floatfield(Num n) { return fmtflags::scientific; } - }; - // Define a new generator type based on the new coordinate policy. - using coordinate_type_fixed = boost::spirit::karma::real_generator>; - using coordinate_type_scientific = boost::spirit::karma::real_generator>; -#endif // EXPORT_3MF_USE_SPIRIT_KARMA_FP - - bool _3MF_Exporter::_add_mesh_to_object_stream(mz_zip_writer_staged_context &context, ModelObject& object, VolumeToOffsetsMap& volumes_offsets) - { - std::string output_buffer; - output_buffer += " <"; - output_buffer += MESH_TAG; - output_buffer += ">\n <"; - output_buffer += VERTICES_TAG; - output_buffer += ">\n"; - - auto flush = [this, &output_buffer, &context](bool force = false) { - if ((force && ! output_buffer.empty()) || output_buffer.size() >= 65536 * 16) { - if (! mz_zip_writer_add_staged_data(&context, output_buffer.data(), output_buffer.size())) { - add_error("Error during writing or compression"); - return false; - } - output_buffer.clear(); - } - return true; - }; - - auto format_coordinate = [](float f, char *buf) -> char* { - assert(is_decimal_separator_point()); -#if EXPORT_3MF_USE_SPIRIT_KARMA_FP - // Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, - // https://github.com/boostorg/spirit/pull/586 - // where the exported string is one digit shorter than it should be to guarantee lossless round trip. - // The code is left here for the ocasion boost guys improve. - coordinate_type_fixed const coordinate_fixed = coordinate_type_fixed(); - coordinate_type_scientific const coordinate_scientific = coordinate_type_scientific(); - // Format "f" in a fixed format. - char *ptr = buf; - boost::spirit::karma::generate(ptr, coordinate_fixed, f); - // Format "f" in a scientific format. - char *ptr2 = ptr; - boost::spirit::karma::generate(ptr2, coordinate_scientific, f); - // Return end of the shorter string. - auto len2 = ptr2 - ptr; - if (ptr - buf > len2) { - // Move the shorter scientific form to the front. - memcpy(buf, ptr, len2); - ptr = buf + len2; - } - // Return pointer to the end. - return ptr; -#else - // Round-trippable float, shortest possible. - return buf + sprintf(buf, "%.9g", f); -#endif - }; - - char buf[256]; - unsigned int vertices_count = 0; - for (ModelVolume* volume : object.volumes) { - if (volume == nullptr) - continue; - - volumes_offsets.insert({ volume, Offsets(vertices_count) }); - - const indexed_triangle_set &its = volume->mesh().its; - if (its.vertices.empty()) { - add_error("Found invalid mesh"); - return false; - } - - vertices_count += (int)its.vertices.size(); - - const Transform3d& matrix = volume->get_matrix(); - - for (size_t i = 0; i < its.vertices.size(); ++i) { - Vec3f v = (matrix * its.vertices[i].cast()).cast(); - char *ptr = buf; - boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << VERTEX_TAG << " x=\""); - ptr = format_coordinate(v.x(), ptr); - boost::spirit::karma::generate(ptr, "\" y=\""); - ptr = format_coordinate(v.y(), ptr); - boost::spirit::karma::generate(ptr, "\" z=\""); - ptr = format_coordinate(v.z(), ptr); - boost::spirit::karma::generate(ptr, "\"/>\n"); - *ptr = '\0'; - output_buffer += buf; - if (! flush()) - return false; - } - } - - output_buffer += " \n <"; - output_buffer += TRIANGLES_TAG; - output_buffer += ">\n"; - - unsigned int triangles_count = 0; - for (ModelVolume* volume : object.volumes) { - if (volume == nullptr) - continue; - - bool is_left_handed = volume->is_left_handed(); - VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume); - assert(volume_it != volumes_offsets.end()); - - const indexed_triangle_set &its = volume->mesh().its; - - // updates triangle offsets - volume_it->second.first_triangle_id = triangles_count; - triangles_count += (int)its.indices.size(); - volume_it->second.last_triangle_id = triangles_count - 1; - - for (int i = 0; i < int(its.indices.size()); ++ i) { - { - const Vec3i &idx = its.indices[i]; - char *ptr = buf; - boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG << - " v1=\"" << boost::spirit::int_ << - "\" v2=\"" << boost::spirit::int_ << - "\" v3=\"" << boost::spirit::int_ << "\"", - idx[is_left_handed ? 2 : 0] + volume_it->second.first_vertex_id, - idx[1] + volume_it->second.first_vertex_id, - idx[is_left_handed ? 0 : 2] + volume_it->second.first_vertex_id); - *ptr = '\0'; - output_buffer += buf; - } - - std::string custom_supports_data_string = volume->supported_facets.get_triangle_as_string(i); - if (! custom_supports_data_string.empty()) { - output_buffer += " "; - output_buffer += CUSTOM_SUPPORTS_ATTR; - output_buffer += "=\""; - output_buffer += custom_supports_data_string; - output_buffer += "\""; - } - - std::string custom_seam_data_string = volume->seam_facets.get_triangle_as_string(i); - if (! custom_seam_data_string.empty()) { - output_buffer += " "; - output_buffer += CUSTOM_SEAM_ATTR; - output_buffer += "=\""; - output_buffer += custom_seam_data_string; - output_buffer += "\""; - } - - std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i); - if (! mmu_painting_data_string.empty()) { - output_buffer += " "; - output_buffer += MMU_SEGMENTATION_ATTR; - output_buffer += "=\""; - output_buffer += mmu_painting_data_string; - output_buffer += "\""; - } - - output_buffer += "/>\n"; - - if (! flush()) - return false; - } - } - - output_buffer += " \n \n"; - - // Force flush. - return flush(true); - } - - bool _3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) - { - // This happens for empty projects - if (build_items.size() == 0) { - add_error("No build item found"); - return true; - } - - stream << " <" << BUILD_TAG << ">\n"; - - for (const BuildItem& item : build_items) { - stream << " <" << ITEM_TAG << " " << OBJECTID_ATTR << "=\"" << item.id << "\" " << TRANSFORM_ATTR << "=\""; - for (unsigned c = 0; c < 4; ++c) { - for (unsigned r = 0; r < 3; ++r) { - stream << item.transform(r, c); - if (r != 2 || c != 3) - stream << " "; - } - } - stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\"/>\n"; - } - - stream << " \n"; - - return true; - } - - bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model) - { - assert(is_decimal_separator_point()); - std::string out = ""; - char buffer[1024]; - - unsigned int count = 0; - for (const ModelObject* object : model.objects) { - ++count; - const std::vector& layer_height_profile = object->layer_height_profile.get(); - if (layer_height_profile.size() >= 4 && layer_height_profile.size() % 2 == 0) { - sprintf(buffer, "object_id=%d|", count); - out += buffer; - - // Store the layer height profile as a single semicolon separated list. - for (size_t i = 0; i < layer_height_profile.size(); ++i) { - sprintf(buffer, (i == 0) ? "%f" : ";%f", layer_height_profile[i]); - out += buffer; - } - - out += "\n"; - } - } - - if (!out.empty()) { - if (!mz_zip_writer_add_mem(&archive, LAYER_HEIGHTS_PROFILE_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add layer heights profile file to archive"); - return false; - } - } - - return true; - } - - bool _3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) - { - std::string out = ""; - pt::ptree tree; - - unsigned int object_cnt = 0; - for (const ModelObject* object : model.objects) { - object_cnt++; - const t_layer_config_ranges& ranges = object->layer_config_ranges; - if (!ranges.empty()) - { - pt::ptree& obj_tree = tree.add("objects.object",""); - - obj_tree.put(".id", object_cnt); - - // Store the layer config ranges. - for (const auto& range : ranges) { - pt::ptree& range_tree = obj_tree.add("range", ""); - - // store minX and maxZ - range_tree.put(".min_z", range.first.first); - range_tree.put(".max_z", range.first.second); - - // store range configuration - const ModelConfig& config = range.second; - for (const std::string& opt_key : config.keys()) { - pt::ptree& opt_tree = range_tree.add("option", config.opt_serialize(opt_key)); - opt_tree.put(".opt_key", opt_key); - } - } - } - } - - if (!tree.empty()) { - std::ostringstream oss; - pt::write_xml(oss, tree); - out = oss.str(); - - // Post processing("beautification") of the output string for a better preview - boost::replace_all(out, ">\n \n \n ", ">\n "); - boost::replace_all(out, ">", ">\n "); - // OR just - boost::replace_all(out, "><", ">\n<"); - } - - if (!out.empty()) { - if (!mz_zip_writer_add_mem(&archive, LAYER_CONFIG_RANGES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add layer heights profile file to archive"); - return false; - } - } - - return true; - } - - bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) - { - assert(is_decimal_separator_point()); - std::string out = ""; - char buffer[1024]; - - unsigned int count = 0; - for (const ModelObject* object : model.objects) { - ++count; - const std::vector& sla_support_points = object->sla_support_points; - if (!sla_support_points.empty()) { - sprintf(buffer, "object_id=%d|", count); - out += buffer; - - // Store the layer height profile as a single space separated list. - for (size_t i = 0; i < sla_support_points.size(); ++i) { - sprintf(buffer, (i==0 ? "%f %f %f %f %f" : " %f %f %f %f %f"), sla_support_points[i].pos(0), sla_support_points[i].pos(1), sla_support_points[i].pos(2), sla_support_points[i].head_front_radius, (float)sla_support_points[i].is_new_island); - out += buffer; - } - out += "\n"; - } - } - - if (!out.empty()) { - // Adds version header at the beginning: - out = std::string("support_points_format_version=") + std::to_string(support_points_format_version) + std::string("\n") + out; - - if (!mz_zip_writer_add_mem(&archive, SLA_SUPPORT_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add sla support points file to archive"); - return false; - } - } - return true; - } - - bool _3MF_Exporter::_add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model) - { - assert(is_decimal_separator_point()); - const char *const fmt = "object_id=%d|"; - std::string out; - - unsigned int count = 0; - for (const ModelObject* object : model.objects) { - ++count; - sla::DrainHoles drain_holes = object->sla_drain_holes; - - // The holes were placed 1mm above the mesh in the first implementation. - // This was a bad idea and the reference point was changed in 2.3 so - // to be on the mesh exactly. The elevated position is still saved - // in 3MFs for compatibility reasons. - for (sla::DrainHole& hole : drain_holes) { - hole.pos -= hole.normal.normalized(); - hole.height += 1.f; - } - - if (!drain_holes.empty()) { - out += string_printf(fmt, count); - - // Store the layer height profile as a single space separated list. - for (size_t i = 0; i < drain_holes.size(); ++i) - out += string_printf((i == 0 ? "%f %f %f %f %f %f %f %f" : " %f %f %f %f %f %f %f %f"), - drain_holes[i].pos(0), - drain_holes[i].pos(1), - drain_holes[i].pos(2), - drain_holes[i].normal(0), - drain_holes[i].normal(1), - drain_holes[i].normal(2), - drain_holes[i].radius, - drain_holes[i].height); - - out += "\n"; - } - } - - if (!out.empty()) { - // Adds version header at the beginning: - out = std::string("drain_holes_format_version=") + std::to_string(drain_holes_format_version) + std::string("\n") + out; - - if (!mz_zip_writer_add_mem(&archive, SLA_DRAIN_HOLES_FILE.c_str(), static_cast(out.data()), out.length(), mz_uint(MZ_DEFAULT_COMPRESSION))) { - add_error("Unable to add sla support points file to archive"); - return false; - } - } - return true; - } - - bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config) - { - assert(is_decimal_separator_point()); - char buffer[1024]; - sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str()); - std::string out = buffer; - - for (const std::string &key : config.keys()) - if (key != "compatible_printers") - out += "; " + key + " = " + config.opt_serialize(key) + "\n"; - - if (!out.empty()) { - if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add print config file to archive"); - return false; - } - } - - return true; - } - - bool _3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data) - { - std::stringstream stream; - // Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back - // when loaded as accurately as possible. - stream << std::setprecision(std::numeric_limits::max_digits10); - stream << "\n"; - stream << "<" << CONFIG_TAG << ">\n"; - - for (const IdToObjectDataMap::value_type& obj_metadata : objects_data) { - const ModelObject* obj = obj_metadata.second.object; - if (obj != nullptr) { - // Output of instances count added because of github #3435, currently not used by PrusaSlicer - stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << obj_metadata.first << "\" " << INSTANCESCOUNT_ATTR << "=\"" << obj->instances.size() << "\">\n"; - - // stores object's name - if (!obj->name.empty()) - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << xml_escape(obj->name) << "\"/>\n"; - - // stores object's config data - for (const std::string& key : obj->config.keys()) { - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.opt_serialize(key) << "\"/>\n"; - } - - for (const ModelVolume* volume : obj_metadata.second.object->volumes) { - if (volume != nullptr) { - const VolumeToOffsetsMap& offsets = obj_metadata.second.volumes_offsets; - VolumeToOffsetsMap::const_iterator it = offsets.find(volume); - if (it != offsets.end()) { - // stores volume's offsets - stream << " <" << VOLUME_TAG << " "; - stream << FIRST_TRIANGLE_ID_ATTR << "=\"" << it->second.first_triangle_id << "\" "; - stream << LAST_TRIANGLE_ID_ATTR << "=\"" << it->second.last_triangle_id << "\">\n"; - - // stores volume's name - if (!volume->name.empty()) - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n"; - - // stores volume's modifier field (legacy, to support old slicers) - if (volume->is_modifier()) - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; - // stores volume's type (overrides the modifier field above) - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " << - VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n"; - - // stores volume's local matrix - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; - const Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); - for (int r = 0; r < 4; ++r) { - for (int c = 0; c < 4; ++c) { - stream << matrix(r, c); - if (r != 3 || c != 3) - stream << " "; - } - } - stream << "\"/>\n"; - - // stores volume's source data - { - std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); - std::string prefix = std::string(" <") + METADATA_TAG + " " + TYPE_ATTR + "=\"" + VOLUME_TYPE + "\" " + KEY_ATTR + "=\""; - if (! volume->source.input_file.empty()) { - stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n"; - stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n"; - stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n"; - stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n"; - stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n"; - stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n"; - } - assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); - if (volume->source.is_converted_from_inches) - stream << prefix << SOURCE_IN_INCHES_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; - else if (volume->source.is_converted_from_meters) - stream << prefix << SOURCE_IN_METERS_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; -#if ENABLE_RELOAD_FROM_DISK_REWORK - if (volume->source.is_from_builtin_objects) - stream << prefix << SOURCE_IS_BUILTIN_VOLUME_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; -#endif // ENABLE_RELOAD_FROM_DISK_REWORK - } - - // stores volume's config data - for (const std::string& key : volume->config.keys()) { - stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n"; - } - - // stores mesh's statistics - const RepairedMeshErrors& stats = volume->mesh().stats().repaired_errors; - stream << " <" << MESH_TAG << " "; - stream << MESH_STAT_EDGES_FIXED << "=\"" << stats.edges_fixed << "\" "; - stream << MESH_STAT_DEGENERATED_FACETS << "=\"" << stats.degenerate_facets << "\" "; - stream << MESH_STAT_FACETS_REMOVED << "=\"" << stats.facets_removed << "\" "; - stream << MESH_STAT_FACETS_RESERVED << "=\"" << stats.facets_reversed << "\" "; - stream << MESH_STAT_BACKWARDS_EDGES << "=\"" << stats.backwards_edges << "\"/>\n"; - - stream << " \n"; - } - } - } - - stream << " \n"; - } - } - - stream << "\n"; - - std::string out = stream.str(); - - if (!mz_zip_writer_add_mem(&archive, MODEL_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add model config file to archive"); - return false; - } - - return true; - } - -bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config) -{ - std::string out = ""; - - if (!model.custom_gcode_per_print_z.gcodes.empty()) { - pt::ptree tree; - pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", ""); - - for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) { - pt::ptree& code_tree = main_tree.add("code", ""); - - // store data of custom_gcode_per_print_z - code_tree.put(".print_z" , code.print_z ); - code_tree.put(".type" , static_cast(code.type)); - code_tree.put(".extruder" , code.extruder ); - code_tree.put(".color" , code.color ); - code_tree.put(".extra" , code.extra ); - - // add gcode field data for the old version of the PrusaSlicer - std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") : - code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") : - code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") : - code.type == CustomGCode::ToolChange ? "tool_change" : code.extra; - code_tree.put(".gcode" , gcode ); - } - - pt::ptree& mode_tree = main_tree.add("mode", ""); - // store mode of a custom_gcode_per_print_z - mode_tree.put(".value", model.custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode : - model.custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode : - CustomGCode::MultiExtruderMode); - - if (!tree.empty()) { - std::ostringstream oss; - boost::property_tree::write_xml(oss, tree); - out = oss.str(); - - // Post processing("beautification") of the output string - boost::replace_all(out, "><", ">\n<"); - } - } - - if (!out.empty()) { - if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_PRINT_Z_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add custom Gcodes per print_z file to archive"); - return false; - } - } - - return true; -} - -// Perform conversions based on the config values available. -//FIXME provide a version of PrusaSlicer that stored the project file (3MF). -static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config) -{ - if (! config.has("brim_separation")) { - if (auto *opt_elephant_foot = config.option("elefant_foot_compensation", false); opt_elephant_foot) { - // Conversion from older PrusaSlicer which applied brim separation equal to elephant foot compensation. - auto *opt_brim_separation = config.option("brim_separation", true); - opt_brim_separation->value = opt_elephant_foot->value; - } - } -} - -bool is_project_3mf(const std::string& filename) -{ - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - if (!open_zip_reader(&archive, filename)) - return false; - - mz_uint num_entries = mz_zip_reader_get_num_files(&archive); - - // loop the entries to search for config - mz_zip_archive_file_stat stat; - bool config_found = false; - for (mz_uint i = 0; i < num_entries; ++i) { - if (mz_zip_reader_file_stat(&archive, i, &stat)) { - std::string name(stat.m_filename); - std::replace(name.begin(), name.end(), '\\', '/'); - - if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { - config_found = true; - break; - } - } - } - - close_zip_reader(&archive); - - return config_found; -} - -bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) -{ - if (path == nullptr || model == nullptr) - return false; - - // All import should use "C" locales for number formatting. - CNumericLocalesSetter locales_setter; - _3MF_Importer importer; - bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version); - importer.log_errors(); - handle_legacy_project_loaded(importer.version(), config); - return res; -} - -bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) -{ - // All export should use "C" locales for number formatting. - CNumericLocalesSetter locales_setter; - - if (path == nullptr || model == nullptr) - return false; - - _3MF_Exporter exporter; - bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data, zip64); - if (!res) - exporter.log_errors(); - - return res; -} -} // namespace Slic3r +#include "../libslic3r.h" +#include "../Exception.hpp" +#include "../Model.hpp" +#include "../Utils.hpp" +#include "../LocalesUtils.hpp" +#include "../GCode.hpp" +#include "../Geometry.hpp" +#include "../GCode/ThumbnailData.hpp" +#include "../Semver.hpp" +#include "../Time.hpp" + +#include "../I18N.hpp" + +#include "3mf.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +namespace pt = boost::property_tree; + +#include +#include +#include "miniz_extension.hpp" + +#include + +// Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, +// https://github.com/boostorg/spirit/pull/586 +// where the exported string is one digit shorter than it should be to guarantee lossless round trip. +// The code is left here for the ocasion boost guys improve. +#define EXPORT_3MF_USE_SPIRIT_KARMA_FP 0 + +// VERSION NUMBERS +// 0 : .3mf, files saved by older slic3r or other applications. No version definition in them. +// 1 : Introduction of 3mf versioning. No other change in data saved into 3mf files. +// 2 : Volumes' matrices and source data added to Metadata/Slic3r_PE_model.config file, meshes transformed back to their coordinate system on loading. +// WARNING !! -> the version number has been rolled back to 1 +// the next change should use 3 +const unsigned int VERSION_3MF = 1; +// Allow loading version 2 file as well. +const unsigned int VERSION_3MF_COMPATIBLE = 2; +const char* SLIC3RPE_3MF_VERSION = "slic3rpe:Version3mf"; // definition of the metadata name saved into .model file + +// Painting gizmos data version numbers +// 0 : 3MF files saved by older PrusaSlicer or the painting gizmo wasn't used. No version definition in them. +// 1 : Introduction of painting gizmos data versioning. No other changes in painting gizmos data. +const unsigned int FDM_SUPPORTS_PAINTING_VERSION = 1; +const unsigned int SEAM_PAINTING_VERSION = 1; +const unsigned int MM_PAINTING_VERSION = 1; + +const std::string SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION = "slic3rpe:FdmSupportsPaintingVersion"; +const std::string SLIC3RPE_SEAM_PAINTING_VERSION = "slic3rpe:SeamPaintingVersion"; +const std::string SLIC3RPE_MM_PAINTING_VERSION = "slic3rpe:MmPaintingVersion"; + +const std::string MODEL_FOLDER = "3D/"; +const std::string MODEL_EXTENSION = ".model"; +const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA +const std::string CONTENT_TYPES_FILE = "[Content_Types].xml"; +const std::string RELATIONSHIPS_FILE = "_rels/.rels"; +const std::string THUMBNAIL_FILE = "Metadata/thumbnail.png"; +const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; +const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; +const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; +const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml"; +const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; +const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt"; +const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"; + +static constexpr const char* MODEL_TAG = "model"; +static constexpr const char* RESOURCES_TAG = "resources"; +static constexpr const char* OBJECT_TAG = "object"; +static constexpr const char* MESH_TAG = "mesh"; +static constexpr const char* VERTICES_TAG = "vertices"; +static constexpr const char* VERTEX_TAG = "vertex"; +static constexpr const char* TRIANGLES_TAG = "triangles"; +static constexpr const char* TRIANGLE_TAG = "triangle"; +static constexpr const char* COMPONENTS_TAG = "components"; +static constexpr const char* COMPONENT_TAG = "component"; +static constexpr const char* BUILD_TAG = "build"; +static constexpr const char* ITEM_TAG = "item"; +static constexpr const char* METADATA_TAG = "metadata"; + +static constexpr const char* CONFIG_TAG = "config"; +static constexpr const char* VOLUME_TAG = "volume"; + +static constexpr const char* UNIT_ATTR = "unit"; +static constexpr const char* NAME_ATTR = "name"; +static constexpr const char* TYPE_ATTR = "type"; +static constexpr const char* ID_ATTR = "id"; +static constexpr const char* X_ATTR = "x"; +static constexpr const char* Y_ATTR = "y"; +static constexpr const char* Z_ATTR = "z"; +static constexpr const char* V1_ATTR = "v1"; +static constexpr const char* V2_ATTR = "v2"; +static constexpr const char* V3_ATTR = "v3"; +static constexpr const char* OBJECTID_ATTR = "objectid"; +static constexpr const char* TRANSFORM_ATTR = "transform"; +static constexpr const char* PRINTABLE_ATTR = "printable"; +static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; +static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; +static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam"; +static constexpr const char* MMU_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation"; + +static constexpr const char* KEY_ATTR = "key"; +static constexpr const char* VALUE_ATTR = "value"; +static constexpr const char* FIRST_TRIANGLE_ID_ATTR = "firstid"; +static constexpr const char* LAST_TRIANGLE_ID_ATTR = "lastid"; + +static constexpr const char* OBJECT_TYPE = "object"; +static constexpr const char* VOLUME_TYPE = "volume"; + +static constexpr const char* NAME_KEY = "name"; +static constexpr const char* MODIFIER_KEY = "modifier"; +static constexpr const char* VOLUME_TYPE_KEY = "volume_type"; +static constexpr const char* MATRIX_KEY = "matrix"; +static constexpr const char* SOURCE_FILE_KEY = "source_file"; +static constexpr const char* SOURCE_OBJECT_ID_KEY = "source_object_id"; +static constexpr const char* SOURCE_VOLUME_ID_KEY = "source_volume_id"; +static constexpr const char* SOURCE_OFFSET_X_KEY = "source_offset_x"; +static constexpr const char* SOURCE_OFFSET_Y_KEY = "source_offset_y"; +static constexpr const char* SOURCE_OFFSET_Z_KEY = "source_offset_z"; +static constexpr const char* SOURCE_IN_INCHES_KEY = "source_in_inches"; +static constexpr const char* SOURCE_IN_METERS_KEY = "source_in_meters"; +#if ENABLE_RELOAD_FROM_DISK_REWORK +static constexpr const char* SOURCE_IS_BUILTIN_VOLUME_KEY = "source_is_builtin_volume"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + +static constexpr const char* MESH_STAT_EDGES_FIXED = "edges_fixed"; +static constexpr const char* MESH_STAT_DEGENERATED_FACETS = "degenerate_facets"; +static constexpr const char* MESH_STAT_FACETS_REMOVED = "facets_removed"; +static constexpr const char* MESH_STAT_FACETS_RESERVED = "facets_reversed"; +static constexpr const char* MESH_STAT_BACKWARDS_EDGES = "backwards_edges"; + + +const unsigned int VALID_OBJECT_TYPES_COUNT = 1; +const char* VALID_OBJECT_TYPES[] = +{ + "model" +}; + +const char* INVALID_OBJECT_TYPES[] = +{ + "solidsupport", + "support", + "surface", + "other" +}; + +class version_error : public Slic3r::FileIOError +{ +public: + version_error(const std::string& what_arg) : Slic3r::FileIOError(what_arg) {} + version_error(const char* what_arg) : Slic3r::FileIOError(what_arg) {} +}; + +const char* get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + if ((attributes == nullptr) || (attributes_size == 0) || (attributes_size % 2 != 0) || (attribute_key == nullptr)) + return nullptr; + + for (unsigned int a = 0; a < attributes_size; a += 2) { + if (::strcmp(attributes[a], attribute_key) == 0) + return attributes[a + 1]; + } + + return nullptr; +} + +std::string get_attribute_value_string(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); + return (text != nullptr) ? text : ""; +} + +float get_attribute_value_float(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + float value = 0.0f; + if (const char *text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr) + fast_float::from_chars(text, text + strlen(text), value); + return value; +} + +int get_attribute_value_int(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + int value = 0; + if (const char *text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); text != nullptr) + boost::spirit::qi::parse(text, text + strlen(text), boost::spirit::qi::int_, value); + return value; +} + +bool get_attribute_value_bool(const char** attributes, unsigned int attributes_size, const char* attribute_key) +{ + const char* text = get_attribute_value_charptr(attributes, attributes_size, attribute_key); + return (text != nullptr) ? (bool)::atoi(text) : true; +} + +Slic3r::Transform3d get_transform_from_3mf_specs_string(const std::string& mat_str) +{ + // check: https://3mf.io/3d-manufacturing-format/ or https://github.com/3MFConsortium/spec_core/blob/master/3MF%20Core%20Specification.md + // to see how matrices are stored inside 3mf according to specifications + Slic3r::Transform3d ret = Slic3r::Transform3d::Identity(); + + if (mat_str.empty()) + // empty string means default identity matrix + return ret; + + std::vector mat_elements_str; + boost::split(mat_elements_str, mat_str, boost::is_any_of(" "), boost::token_compress_on); + + unsigned int size = (unsigned int)mat_elements_str.size(); + if (size != 12) + // invalid data, return identity matrix + return ret; + + unsigned int i = 0; + // matrices are stored into 3mf files as 4x3 + // we need to transpose them + for (unsigned int c = 0; c < 4; ++c) { + for (unsigned int r = 0; r < 3; ++r) { + ret(r, c) = ::atof(mat_elements_str[i++].c_str()); + } + } + return ret; +} + +float get_unit_factor(const std::string& unit) +{ + const char* text = unit.c_str(); + + if (::strcmp(text, "micron") == 0) + return 0.001f; + else if (::strcmp(text, "centimeter") == 0) + return 10.0f; + else if (::strcmp(text, "inch") == 0) + return 25.4f; + else if (::strcmp(text, "foot") == 0) + return 304.8f; + else if (::strcmp(text, "meter") == 0) + return 1000.0f; + else + // default "millimeters" (see specification) + return 1.0f; +} + +bool is_valid_object_type(const std::string& type) +{ + // if the type is empty defaults to "model" (see specification) + if (type.empty()) + return true; + + for (unsigned int i = 0; i < VALID_OBJECT_TYPES_COUNT; ++i) { + if (::strcmp(type.c_str(), VALID_OBJECT_TYPES[i]) == 0) + return true; + } + + return false; +} + +namespace Slic3r { + +//! macro used to mark string used at localization, +//! return same string +#define L(s) (s) +#define _(s) Slic3r::I18N::translate(s) + + // Base class with error messages management + class _3MF_Base + { + std::vector m_errors; + + protected: + void add_error(const std::string& error) { m_errors.push_back(error); } + void clear_errors() { m_errors.clear(); } + + public: + void log_errors() + { + for (const std::string& error : m_errors) + BOOST_LOG_TRIVIAL(error) << error; + } + }; + + class _3MF_Importer : public _3MF_Base + { + struct Component + { + int object_id; + Transform3d transform; + + explicit Component(int object_id) + : object_id(object_id) + , transform(Transform3d::Identity()) + { + } + + Component(int object_id, const Transform3d& transform) + : object_id(object_id) + , transform(transform) + { + } + }; + + typedef std::vector ComponentsList; + + struct Geometry + { + std::vector vertices; + std::vector triangles; + std::vector custom_supports; + std::vector custom_seam; + std::vector mmu_segmentation; + + bool empty() { return vertices.empty() || triangles.empty(); } + + void reset() { + vertices.clear(); + triangles.clear(); + custom_supports.clear(); + custom_seam.clear(); + mmu_segmentation.clear(); + } + }; + + struct CurrentObject + { + // ID of the object inside the 3MF file, 1 based. + int id; + // Index of the ModelObject in its respective Model, zero based. + int model_object_idx; + Geometry geometry; + ModelObject* object; + ComponentsList components; + + CurrentObject() { reset(); } + + void reset() { + id = -1; + model_object_idx = -1; + geometry.reset(); + object = nullptr; + components.clear(); + } + }; + + struct CurrentConfig + { + int object_id; + int volume_id; + }; + + struct Instance + { + ModelInstance* instance; + Transform3d transform; + + Instance(ModelInstance* instance, const Transform3d& transform) + : instance(instance) + , transform(transform) + { + } + }; + + struct Metadata + { + std::string key; + std::string value; + + Metadata(const std::string& key, const std::string& value) + : key(key) + , value(value) + { + } + }; + + typedef std::vector MetadataList; + + struct ObjectMetadata + { + struct VolumeMetadata + { + unsigned int first_triangle_id; + unsigned int last_triangle_id; + MetadataList metadata; + RepairedMeshErrors mesh_stats; + + VolumeMetadata(unsigned int first_triangle_id, unsigned int last_triangle_id) + : first_triangle_id(first_triangle_id) + , last_triangle_id(last_triangle_id) + { + } + }; + + typedef std::vector VolumeMetadataList; + + MetadataList metadata; + VolumeMetadataList volumes; + }; + + // Map from a 1 based 3MF object ID to a 0 based ModelObject index inside m_model->objects. + typedef std::map IdToModelObjectMap; + typedef std::map IdToAliasesMap; + typedef std::vector InstancesList; + typedef std::map IdToMetadataMap; + typedef std::map IdToGeometryMap; + typedef std::map> IdToLayerHeightsProfileMap; + typedef std::map IdToLayerConfigRangesMap; + typedef std::map> IdToSlaSupportPointsMap; + typedef std::map> IdToSlaDrainHolesMap; + + // Version of the 3mf file + unsigned int m_version; + bool m_check_version; + + // Semantic version of PrusaSlicer, that generated this 3MF. + boost::optional m_prusaslicer_generator_version; + unsigned int m_fdm_supports_painting_version = 0; + unsigned int m_seam_painting_version = 0; + unsigned int m_mm_painting_version = 0; + + XML_Parser m_xml_parser; + // Error code returned by the application side of the parser. In that case the expat may not reliably deliver the error state + // after returning from XML_Parse() function, thus we keep the error state here. + bool m_parse_error { false }; + std::string m_parse_error_message; + Model* m_model; + float m_unit_factor; + CurrentObject m_curr_object; + IdToModelObjectMap m_objects; + IdToAliasesMap m_objects_aliases; + InstancesList m_instances; + IdToGeometryMap m_geometries; + CurrentConfig m_curr_config; + IdToMetadataMap m_objects_metadata; + IdToLayerHeightsProfileMap m_layer_heights_profiles; + IdToLayerConfigRangesMap m_layer_config_ranges; + IdToSlaSupportPointsMap m_sla_support_points; + IdToSlaDrainHolesMap m_sla_drain_holes; + std::string m_curr_metadata_name; + std::string m_curr_characters; + std::string m_name; + + public: + _3MF_Importer(); + ~_3MF_Importer(); + + bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version); + unsigned int version() const { return m_version; } + + private: + void _destroy_xml_parser(); + void _stop_xml_parser(const std::string& msg = std::string()); + + bool parse_error() const { return m_parse_error; } + const char* parse_error_message() const { + return m_parse_error ? + // The error was signalled by the user code, not the expat parser. + (m_parse_error_message.empty() ? "Invalid 3MF format" : m_parse_error_message.c_str()) : + // The error was signalled by the expat parser. + XML_ErrorString(XML_GetErrorCode(m_xml_parser)); + } + + bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions); + bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); + void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + + void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + + void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename); + bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); + + // handlers to parse the .model file + void _handle_start_model_xml_element(const char* name, const char** attributes); + void _handle_end_model_xml_element(const char* name); + void _handle_model_xml_characters(const XML_Char* s, int len); + + // handlers to parse the MODEL_CONFIG_FILE file + void _handle_start_config_xml_element(const char* name, const char** attributes); + void _handle_end_config_xml_element(const char* name); + + bool _handle_start_model(const char** attributes, unsigned int num_attributes); + bool _handle_end_model(); + + bool _handle_start_resources(const char** attributes, unsigned int num_attributes); + bool _handle_end_resources(); + + bool _handle_start_object(const char** attributes, unsigned int num_attributes); + bool _handle_end_object(); + + bool _handle_start_mesh(const char** attributes, unsigned int num_attributes); + bool _handle_end_mesh(); + + bool _handle_start_vertices(const char** attributes, unsigned int num_attributes); + bool _handle_end_vertices(); + + bool _handle_start_vertex(const char** attributes, unsigned int num_attributes); + bool _handle_end_vertex(); + + bool _handle_start_triangles(const char** attributes, unsigned int num_attributes); + bool _handle_end_triangles(); + + bool _handle_start_triangle(const char** attributes, unsigned int num_attributes); + bool _handle_end_triangle(); + + bool _handle_start_components(const char** attributes, unsigned int num_attributes); + bool _handle_end_components(); + + bool _handle_start_component(const char** attributes, unsigned int num_attributes); + bool _handle_end_component(); + + bool _handle_start_build(const char** attributes, unsigned int num_attributes); + bool _handle_end_build(); + + bool _handle_start_item(const char** attributes, unsigned int num_attributes); + bool _handle_end_item(); + + bool _handle_start_metadata(const char** attributes, unsigned int num_attributes); + bool _handle_end_metadata(); + + bool _create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter); + + void _apply_transform(ModelInstance& instance, const Transform3d& transform); + + bool _handle_start_config(const char** attributes, unsigned int num_attributes); + bool _handle_end_config(); + + bool _handle_start_config_object(const char** attributes, unsigned int num_attributes); + bool _handle_end_config_object(); + + bool _handle_start_config_volume(const char** attributes, unsigned int num_attributes); + bool _handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes); + bool _handle_end_config_volume(); + bool _handle_end_config_volume_mesh(); + + bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes); + bool _handle_end_config_metadata(); + + bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions); + + // callbacks to parse the .model file + static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes); + static void XMLCALL _handle_end_model_xml_element(void* userData, const char* name); + static void XMLCALL _handle_model_xml_characters(void* userData, const XML_Char* s, int len); + + // callbacks to parse the MODEL_CONFIG_FILE file + static void XMLCALL _handle_start_config_xml_element(void* userData, const char* name, const char** attributes); + static void XMLCALL _handle_end_config_xml_element(void* userData, const char* name); + }; + + _3MF_Importer::_3MF_Importer() + : m_version(0) + , m_check_version(false) + , m_xml_parser(nullptr) + , m_model(nullptr) + , m_unit_factor(1.0f) + , m_curr_metadata_name("") + , m_curr_characters("") + , m_name("") + { + } + + _3MF_Importer::~_3MF_Importer() + { + _destroy_xml_parser(); + } + + bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version) + { + m_version = 0; + m_fdm_supports_painting_version = 0; + m_seam_painting_version = 0; + m_mm_painting_version = 0; + m_check_version = check_version; + m_model = &model; + m_unit_factor = 1.0f; + m_curr_object.reset(); + m_objects.clear(); + m_objects_aliases.clear(); + m_instances.clear(); + m_geometries.clear(); + m_curr_config.object_id = -1; + m_curr_config.volume_id = -1; + m_objects_metadata.clear(); + m_layer_heights_profiles.clear(); + m_layer_config_ranges.clear(); + m_sla_support_points.clear(); + m_curr_metadata_name.clear(); + m_curr_characters.clear(); + clear_errors(); + + return _load_model_from_file(filename, model, config, config_substitutions); + } + + void _3MF_Importer::_destroy_xml_parser() + { + if (m_xml_parser != nullptr) { + XML_ParserFree(m_xml_parser); + m_xml_parser = nullptr; + } + } + + void _3MF_Importer::_stop_xml_parser(const std::string &msg) + { + assert(! m_parse_error); + assert(m_parse_error_message.empty()); + assert(m_xml_parser != nullptr); + m_parse_error = true; + m_parse_error_message = msg; + XML_StopParser(m_xml_parser, false); + } + + bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions) + { + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + if (!open_zip_reader(&archive, filename)) { + add_error("Unable to open the file"); + return false; + } + + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + + mz_zip_archive_file_stat stat; + + m_name = boost::filesystem::path(filename).stem().string(); + + // we first loop the entries to read from the archive the .model file only, in order to extract the version from it + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::istarts_with(name, MODEL_FOLDER) && boost::algorithm::iends_with(name, MODEL_EXTENSION)) { + try + { + // valid model name -> extract model + if (!_extract_model_from_archive(archive, stat)) { + close_zip_reader(&archive); + add_error("Archive does not contain a valid model"); + return false; + } + } + catch (const std::exception& e) + { + // ensure the zip archive is closed and rethrow the exception + close_zip_reader(&archive); + throw Slic3r::FileIOError(e.what()); + } + } + } + } + + // we then loop again the entries to read other files stored in the archive + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::iequals(name, LAYER_HEIGHTS_PROFILE_FILE)) { + // extract slic3r layer heights profile file + _extract_layer_heights_profile_config_from_archive(archive, stat); + } + else if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) { + // extract slic3r layer config ranges file + _extract_layer_config_ranges_from_archive(archive, stat, config_substitutions); + } + else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) { + // extract sla support points file + _extract_sla_support_points_from_archive(archive, stat); + } + else if (boost::algorithm::iequals(name, SLA_DRAIN_HOLES_FILE)) { + // extract sla support points file + _extract_sla_drain_holes_from_archive(archive, stat); + } + else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { + // extract slic3r print config file + _extract_print_config_from_archive(archive, stat, config, config_substitutions, filename); + } + else if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) { + // extract slic3r layer config ranges file + _extract_custom_gcode_per_print_z_from_archive(archive, stat); + } + else if (boost::algorithm::iequals(name, MODEL_CONFIG_FILE)) { + // extract slic3r model config file + if (!_extract_model_config_from_archive(archive, stat, model)) { + close_zip_reader(&archive); + add_error("Archive does not contain a valid model config"); + return false; + } + } + } + } + + close_zip_reader(&archive); + + if (m_version == 0) { + // if the 3mf was not produced by PrusaSlicer and there is more than one instance, + // split the object in as many objects as instances + size_t curr_models_count = m_model->objects.size(); + size_t i = 0; + while (i < curr_models_count) { + ModelObject* model_object = m_model->objects[i]; + if (model_object->instances.size() > 1) { + // select the geometry associated with the original model object + const Geometry* geometry = nullptr; + for (const IdToModelObjectMap::value_type& object : m_objects) { + if (object.second == int(i)) { + IdToGeometryMap::const_iterator obj_geometry = m_geometries.find(object.first); + if (obj_geometry == m_geometries.end()) { + add_error("Unable to find object geometry"); + return false; + } + geometry = &obj_geometry->second; + break; + } + } + + if (geometry == nullptr) { + add_error("Unable to find object geometry"); + return false; + } + + // use the geometry to create the volumes in the new model objects + ObjectMetadata::VolumeMetadataList volumes(1, { 0, (unsigned int)geometry->triangles.size() - 1 }); + + // for each instance after the 1st, create a new model object containing only that instance + // and copy into it the geometry + while (model_object->instances.size() > 1) { + ModelObject* new_model_object = m_model->add_object(*model_object); + new_model_object->clear_instances(); + new_model_object->add_instance(*model_object->instances.back()); + model_object->delete_last_instance(); + if (!_generate_volumes(*new_model_object, *geometry, volumes, config_substitutions)) + return false; + } + } + ++i; + } + } + + for (const IdToModelObjectMap::value_type& object : m_objects) { + if (object.second >= int(m_model->objects.size())) { + add_error("Unable to find object"); + return false; + } + ModelObject* model_object = m_model->objects[object.second]; + IdToGeometryMap::const_iterator obj_geometry = m_geometries.find(object.first); + if (obj_geometry == m_geometries.end()) { + add_error("Unable to find object geometry"); + return false; + } + + // m_layer_heights_profiles are indexed by a 1 based model object index. + IdToLayerHeightsProfileMap::iterator obj_layer_heights_profile = m_layer_heights_profiles.find(object.second + 1); + if (obj_layer_heights_profile != m_layer_heights_profiles.end()) + model_object->layer_height_profile.set(std::move(obj_layer_heights_profile->second)); + + // m_layer_config_ranges are indexed by a 1 based model object index. + IdToLayerConfigRangesMap::iterator obj_layer_config_ranges = m_layer_config_ranges.find(object.second + 1); + if (obj_layer_config_ranges != m_layer_config_ranges.end()) + model_object->layer_config_ranges = std::move(obj_layer_config_ranges->second); + + // m_sla_support_points are indexed by a 1 based model object index. + IdToSlaSupportPointsMap::iterator obj_sla_support_points = m_sla_support_points.find(object.second + 1); + if (obj_sla_support_points != m_sla_support_points.end() && !obj_sla_support_points->second.empty()) { + model_object->sla_support_points = std::move(obj_sla_support_points->second); + model_object->sla_points_status = sla::PointsStatus::UserModified; + } + + IdToSlaDrainHolesMap::iterator obj_drain_holes = m_sla_drain_holes.find(object.second + 1); + if (obj_drain_holes != m_sla_drain_holes.end() && !obj_drain_holes->second.empty()) { + model_object->sla_drain_holes = std::move(obj_drain_holes->second); + } + + ObjectMetadata::VolumeMetadataList volumes; + ObjectMetadata::VolumeMetadataList* volumes_ptr = nullptr; + + IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first); + if (obj_metadata != m_objects_metadata.end()) { + // config data has been found, this model was saved using slic3r pe + + // apply object's name and config data + for (const Metadata& metadata : obj_metadata->second.metadata) { + if (metadata.key == "name") + model_object->name = metadata.value; + else + model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions); + } + + // select object's detected volumes + volumes_ptr = &obj_metadata->second.volumes; + } + else { + // config data not found, this model was not saved using slic3r pe + + // add the entire geometry as the single volume to generate + volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() - 1); + + // select as volumes + volumes_ptr = &volumes; + } + + if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions)) + return false; + } + + // If instances contain a single volume, the volume offset should be 0,0,0 + // This equals to say that instance world position and volume world position should match + // Correct all instances/volumes for which this does not hold + for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) { + ModelObject* o = model.objects[obj_id]; + if (o->volumes.size() == 1) { + ModelVolume* v = o->volumes.front(); + const Slic3r::Geometry::Transformation& first_inst_trafo = o->instances.front()->get_transformation(); + const Vec3d world_vol_offset = (first_inst_trafo * v->get_transformation()).get_offset(); + const Vec3d world_inst_offset = first_inst_trafo.get_offset(); + + if (!world_vol_offset.isApprox(world_inst_offset)) { + const Slic3r::Geometry::Transformation& vol_trafo = v->get_transformation(); + for (int inst_id = 0; inst_id < int(o->instances.size()); ++inst_id) { + ModelInstance* i = o->instances[inst_id]; + const Slic3r::Geometry::Transformation& inst_trafo = i->get_transformation(); + i->set_offset((inst_trafo * vol_trafo).get_offset()); + } + v->set_offset(Vec3d::Zero()); + } + } + } + +#if ENABLE_RELOAD_FROM_DISK_REWORK + for (int obj_id = 0; obj_id < int(model.objects.size()); ++obj_id) { + ModelObject* o = model.objects[obj_id]; + for (int vol_id = 0; vol_id < int(o->volumes.size()); ++vol_id) { + ModelVolume* v = o->volumes[vol_id]; + if (v->source.input_file.empty()) + v->source.input_file = v->name.empty() ? filename : v->name; + if (v->source.volume_idx == -1) + v->source.volume_idx = vol_id; + if (v->source.object_idx == -1) + v->source.object_idx = obj_id; + } + } +#else + int object_idx = 0; + for (ModelObject* o : model.objects) { + int volume_idx = 0; + for (ModelVolume* v : o->volumes) { + if (v->source.input_file.empty() && v->type() == ModelVolumeType::MODEL_PART) { + v->source.input_file = filename; + if (v->source.volume_idx == -1) + v->source.volume_idx = volume_idx; + if (v->source.object_idx == -1) + v->source.object_idx = object_idx; + } + ++volume_idx; + } + ++object_idx; + } +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + +// // fixes the min z of the model if negative +// model.adjust_min_z(); + + return true; + } + + bool _3MF_Importer::_extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size == 0) { + add_error("Found invalid size"); + return false; + } + + _destroy_xml_parser(); + + m_xml_parser = XML_ParserCreate(nullptr); + if (m_xml_parser == nullptr) { + add_error("Unable to create parser"); + return false; + } + + XML_SetUserData(m_xml_parser, (void*)this); + XML_SetElementHandler(m_xml_parser, _3MF_Importer::_handle_start_model_xml_element, _3MF_Importer::_handle_end_model_xml_element); + XML_SetCharacterDataHandler(m_xml_parser, _3MF_Importer::_handle_model_xml_characters); + + struct CallbackData + { + XML_Parser& parser; + _3MF_Importer& importer; + const mz_zip_archive_file_stat& stat; + + CallbackData(XML_Parser& parser, _3MF_Importer& importer, const mz_zip_archive_file_stat& stat) : parser(parser), importer(importer), stat(stat) {} + }; + + CallbackData data(m_xml_parser, *this, stat); + + mz_bool res = 0; + + try + { + res = mz_zip_reader_extract_file_to_callback(&archive, stat.m_filename, [](void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)->size_t { + CallbackData* data = (CallbackData*)pOpaque; + if (!XML_Parse(data->parser, (const char*)pBuf, (int)n, (file_ofs + n == data->stat.m_uncomp_size) ? 1 : 0) || data->importer.parse_error()) { + char error_buf[1024]; + ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", data->importer.parse_error_message(), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); + throw Slic3r::FileIOError(error_buf); + } + + return n; + }, &data, 0); + } + catch (const version_error& e) + { + // rethrow the exception + throw Slic3r::FileIOError(e.what()); + } + catch (std::exception& e) + { + add_error(e.what()); + return false; + } + + if (res == 0) { + add_error("Error while extracting model data from zip archive"); + return false; + } + + return true; + } + + void _3MF_Importer::_extract_print_config_from_archive( + mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, + DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, + const std::string& archive_filename) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading config data to buffer"); + return; + } + //FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment. + // Each config line is prefixed with a semicolon (G-code comment), that is ugly. + + // Replacing the legacy function with load_from_ini_string_commented leads to issues when + // parsing 3MFs from before PrusaSlicer 2.0.0 (which can have duplicated entries in the INI. + // See https://github.com/prusa3d/PrusaSlicer/issues/7155. We'll revert it for now. + //config_substitutions.substitutions = config.load_from_ini_string_commented(std::move(buffer), config_substitutions.rule); + ConfigBase::load_from_gcode_string_legacy(config, buffer.data(), config_substitutions); + } + } + + void _3MF_Importer::_extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading layer heights profile data to buffer"); + return; + } + + if (buffer.back() == '\n') + buffer.pop_back(); + + std::vector objects; + boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + + for (const std::string& object : objects) { + std::vector object_data; + boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); + if (object_data.size() != 2) { + add_error("Error while reading object data"); + continue; + } + + std::vector object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) { + add_error("Error while reading object id"); + continue; + } + + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) { + add_error("Found invalid object id"); + continue; + } + + IdToLayerHeightsProfileMap::iterator object_item = m_layer_heights_profiles.find(object_id); + if (object_item != m_layer_heights_profiles.end()) { + add_error("Found duplicated layer heights profile"); + continue; + } + + std::vector object_data_profile; + boost::split(object_data_profile, object_data[1], boost::is_any_of(";"), boost::token_compress_off); + if (object_data_profile.size() <= 4 || object_data_profile.size() % 2 != 0) { + add_error("Found invalid layer heights profile"); + continue; + } + + std::vector profile; + profile.reserve(object_data_profile.size()); + + for (const std::string& value : object_data_profile) { + profile.push_back((coordf_t)std::atof(value.c_str())); + } + + m_layer_heights_profiles.insert({ object_id, profile }); + } + } + } + + void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading layer config ranges data to buffer"); + return; + } + + std::istringstream iss(buffer); // wrap returned xml to istringstream + pt::ptree objects_tree; + pt::read_xml(iss, objects_tree); + + for (const auto& object : objects_tree.get_child("objects")) { + pt::ptree object_tree = object.second; + int obj_idx = object_tree.get(".id", -1); + if (obj_idx <= 0) { + add_error("Found invalid object id"); + continue; + } + + IdToLayerConfigRangesMap::iterator object_item = m_layer_config_ranges.find(obj_idx); + if (object_item != m_layer_config_ranges.end()) { + add_error("Found duplicated layer config range"); + continue; + } + + t_layer_config_ranges config_ranges; + + for (const auto& range : object_tree) { + if (range.first != "range") + continue; + pt::ptree range_tree = range.second; + double min_z = range_tree.get(".min_z"); + double max_z = range_tree.get(".max_z"); + + // get Z range information + DynamicPrintConfig config; + + for (const auto& option : range_tree) { + if (option.first != "option") + continue; + std::string opt_key = option.second.get(".opt_key"); + std::string value = option.second.data(); + config.set_deserialize(opt_key, value, config_substitutions); + } + + config_ranges[{ min_z, max_z }].assign_config(std::move(config)); + } + + if (!config_ranges.empty()) + m_layer_config_ranges.insert({ obj_idx, std::move(config_ranges) }); + } + } + } + + void _3MF_Importer::_extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + { + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading sla support points data to buffer"); + return; + } + + if (buffer.back() == '\n') + buffer.pop_back(); + + std::vector objects; + boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + + // Info on format versioning - see 3mf.hpp + int version = 0; + std::string key("support_points_format_version="); + if (!objects.empty() && objects[0].find(key) != std::string::npos) { + objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string + version = std::stoi(objects[0]); + objects.erase(objects.begin()); // pop the header + } + + for (const std::string& object : objects) { + std::vector object_data; + boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); + + if (object_data.size() != 2) { + add_error("Error while reading object data"); + continue; + } + + std::vector object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) { + add_error("Error while reading object id"); + continue; + } + + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) { + add_error("Found invalid object id"); + continue; + } + + IdToSlaSupportPointsMap::iterator object_item = m_sla_support_points.find(object_id); + if (object_item != m_sla_support_points.end()) { + add_error("Found duplicated SLA support points"); + continue; + } + + std::vector object_data_points; + boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); + + std::vector sla_support_points; + + if (version == 0) { + for (unsigned int i=0; i 0) { + std::string buffer(size_t(stat.m_uncomp_size), 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading sla support points data to buffer"); + return; + } + + if (buffer.back() == '\n') + buffer.pop_back(); + + std::vector objects; + boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off); + + // Info on format versioning - see 3mf.hpp + int version = 0; + std::string key("drain_holes_format_version="); + if (!objects.empty() && objects[0].find(key) != std::string::npos) { + objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string + version = std::stoi(objects[0]); + objects.erase(objects.begin()); // pop the header + } + + for (const std::string& object : objects) { + std::vector object_data; + boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off); + + if (object_data.size() != 2) { + add_error("Error while reading object data"); + continue; + } + + std::vector object_data_id; + boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off); + if (object_data_id.size() != 2) { + add_error("Error while reading object id"); + continue; + } + + int object_id = std::atoi(object_data_id[1].c_str()); + if (object_id == 0) { + add_error("Found invalid object id"); + continue; + } + + IdToSlaDrainHolesMap::iterator object_item = m_sla_drain_holes.find(object_id); + if (object_item != m_sla_drain_holes.end()) { + add_error("Found duplicated SLA drain holes"); + continue; + } + + std::vector object_data_points; + boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off); + + sla::DrainHoles sla_drain_holes; + + if (version == 1) { + for (unsigned int i=0; i 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + add_error("Error while reading custom Gcodes per height data to buffer"); + return; + } + + std::istringstream iss(buffer); // wrap returned xml to istringstream + pt::ptree main_tree; + pt::read_xml(iss, main_tree); + + if (main_tree.front().first != "custom_gcodes_per_print_z") + return; + pt::ptree code_tree = main_tree.front().second; + + m_model->custom_gcode_per_print_z.gcodes.clear(); + + for (const auto& code : code_tree) { + if (code.first == "mode") { + pt::ptree tree = code.second; + std::string mode = tree.get(".value"); + m_model->custom_gcode_per_print_z.mode = mode == CustomGCode::SingleExtruderMode ? CustomGCode::Mode::SingleExtruder : + mode == CustomGCode::MultiAsSingleMode ? CustomGCode::Mode::MultiAsSingle : + CustomGCode::Mode::MultiExtruder; + } + if (code.first != "code") + continue; + + pt::ptree tree = code.second; + double print_z = tree.get (".print_z" ); + int extruder = tree.get (".extruder"); + std::string color = tree.get (".color" ); + + CustomGCode::Type type; + std::string extra; + pt::ptree attr_tree = tree.find("")->second; + if (attr_tree.find("type") == attr_tree.not_found()) { + // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer + // read old data ... + std::string gcode = tree.get (".gcode"); + // ... and interpret them to the new data + type = gcode == "M600" ? CustomGCode::ColorChange : + gcode == "M601" ? CustomGCode::PausePrint : + gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom; + extra = type == CustomGCode::PausePrint ? color : + type == CustomGCode::Custom ? gcode : ""; + } + else { + type = static_cast(tree.get(".type")); + extra = tree.get(".extra"); + } + m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ; + } + } + } + + void _3MF_Importer::_handle_start_model_xml_element(const char* name, const char** attributes) + { + if (m_xml_parser == nullptr) + return; + + bool res = true; + unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser); + + if (::strcmp(MODEL_TAG, name) == 0) + res = _handle_start_model(attributes, num_attributes); + else if (::strcmp(RESOURCES_TAG, name) == 0) + res = _handle_start_resources(attributes, num_attributes); + else if (::strcmp(OBJECT_TAG, name) == 0) + res = _handle_start_object(attributes, num_attributes); + else if (::strcmp(MESH_TAG, name) == 0) + res = _handle_start_mesh(attributes, num_attributes); + else if (::strcmp(VERTICES_TAG, name) == 0) + res = _handle_start_vertices(attributes, num_attributes); + else if (::strcmp(VERTEX_TAG, name) == 0) + res = _handle_start_vertex(attributes, num_attributes); + else if (::strcmp(TRIANGLES_TAG, name) == 0) + res = _handle_start_triangles(attributes, num_attributes); + else if (::strcmp(TRIANGLE_TAG, name) == 0) + res = _handle_start_triangle(attributes, num_attributes); + else if (::strcmp(COMPONENTS_TAG, name) == 0) + res = _handle_start_components(attributes, num_attributes); + else if (::strcmp(COMPONENT_TAG, name) == 0) + res = _handle_start_component(attributes, num_attributes); + else if (::strcmp(BUILD_TAG, name) == 0) + res = _handle_start_build(attributes, num_attributes); + else if (::strcmp(ITEM_TAG, name) == 0) + res = _handle_start_item(attributes, num_attributes); + else if (::strcmp(METADATA_TAG, name) == 0) + res = _handle_start_metadata(attributes, num_attributes); + + if (!res) + _stop_xml_parser(); + } + + void _3MF_Importer::_handle_end_model_xml_element(const char* name) + { + if (m_xml_parser == nullptr) + return; + + bool res = true; + + if (::strcmp(MODEL_TAG, name) == 0) + res = _handle_end_model(); + else if (::strcmp(RESOURCES_TAG, name) == 0) + res = _handle_end_resources(); + else if (::strcmp(OBJECT_TAG, name) == 0) + res = _handle_end_object(); + else if (::strcmp(MESH_TAG, name) == 0) + res = _handle_end_mesh(); + else if (::strcmp(VERTICES_TAG, name) == 0) + res = _handle_end_vertices(); + else if (::strcmp(VERTEX_TAG, name) == 0) + res = _handle_end_vertex(); + else if (::strcmp(TRIANGLES_TAG, name) == 0) + res = _handle_end_triangles(); + else if (::strcmp(TRIANGLE_TAG, name) == 0) + res = _handle_end_triangle(); + else if (::strcmp(COMPONENTS_TAG, name) == 0) + res = _handle_end_components(); + else if (::strcmp(COMPONENT_TAG, name) == 0) + res = _handle_end_component(); + else if (::strcmp(BUILD_TAG, name) == 0) + res = _handle_end_build(); + else if (::strcmp(ITEM_TAG, name) == 0) + res = _handle_end_item(); + else if (::strcmp(METADATA_TAG, name) == 0) + res = _handle_end_metadata(); + + if (!res) + _stop_xml_parser(); + } + + void _3MF_Importer::_handle_model_xml_characters(const XML_Char* s, int len) + { + m_curr_characters.append(s, len); + } + + void _3MF_Importer::_handle_start_config_xml_element(const char* name, const char** attributes) + { + if (m_xml_parser == nullptr) + return; + + bool res = true; + unsigned int num_attributes = (unsigned int)XML_GetSpecifiedAttributeCount(m_xml_parser); + + if (::strcmp(CONFIG_TAG, name) == 0) + res = _handle_start_config(attributes, num_attributes); + else if (::strcmp(OBJECT_TAG, name) == 0) + res = _handle_start_config_object(attributes, num_attributes); + else if (::strcmp(VOLUME_TAG, name) == 0) + res = _handle_start_config_volume(attributes, num_attributes); + else if (::strcmp(MESH_TAG, name) == 0) + res = _handle_start_config_volume_mesh(attributes, num_attributes); + else if (::strcmp(METADATA_TAG, name) == 0) + res = _handle_start_config_metadata(attributes, num_attributes); + + if (!res) + _stop_xml_parser(); + } + + void _3MF_Importer::_handle_end_config_xml_element(const char* name) + { + if (m_xml_parser == nullptr) + return; + + bool res = true; + + if (::strcmp(CONFIG_TAG, name) == 0) + res = _handle_end_config(); + else if (::strcmp(OBJECT_TAG, name) == 0) + res = _handle_end_config_object(); + else if (::strcmp(VOLUME_TAG, name) == 0) + res = _handle_end_config_volume(); + else if (::strcmp(MESH_TAG, name) == 0) + res = _handle_end_config_volume_mesh(); + else if (::strcmp(METADATA_TAG, name) == 0) + res = _handle_end_config_metadata(); + + if (!res) + _stop_xml_parser(); + } + + bool _3MF_Importer::_handle_start_model(const char** attributes, unsigned int num_attributes) + { + m_unit_factor = get_unit_factor(get_attribute_value_string(attributes, num_attributes, UNIT_ATTR)); + return true; + } + + bool _3MF_Importer::_handle_end_model() + { + // deletes all non-built or non-instanced objects + for (const IdToModelObjectMap::value_type& object : m_objects) { + if (object.second >= int(m_model->objects.size())) { + add_error("Unable to find object"); + return false; + } + ModelObject *model_object = m_model->objects[object.second]; + if (model_object != nullptr && model_object->instances.size() == 0) + m_model->delete_object(model_object); + } + + if (m_version == 0) { + // if the 3mf was not produced by PrusaSlicer and there is only one object, + // set the object name to match the filename + if (m_model->objects.size() == 1) + m_model->objects.front()->name = m_name; + } + + // applies instances' matrices + for (Instance& instance : m_instances) { + if (instance.instance != nullptr && instance.instance->get_object() != nullptr) + // apply the transform to the instance + _apply_transform(*instance.instance, instance.transform); + } + + return true; + } + + bool _3MF_Importer::_handle_start_resources(const char** attributes, unsigned int num_attributes) + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_end_resources() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_object(const char** attributes, unsigned int num_attributes) + { + // reset current data + m_curr_object.reset(); + + if (is_valid_object_type(get_attribute_value_string(attributes, num_attributes, TYPE_ATTR))) { + // create new object (it may be removed later if no instances are generated from it) + m_curr_object.model_object_idx = (int)m_model->objects.size(); + m_curr_object.object = m_model->add_object(); + if (m_curr_object.object == nullptr) { + add_error("Unable to create object"); + return false; + } + + // set object data + m_curr_object.object->name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR); + if (m_curr_object.object->name.empty()) + m_curr_object.object->name = m_name + "_" + std::to_string(m_model->objects.size()); + + m_curr_object.id = get_attribute_value_int(attributes, num_attributes, ID_ATTR); + } + + return true; + } + + bool _3MF_Importer::_handle_end_object() + { + if (m_curr_object.object != nullptr) { + if (m_curr_object.geometry.empty()) { + // no geometry defined + // remove the object from the model + m_model->delete_object(m_curr_object.object); + + if (m_curr_object.components.empty()) { + // no components defined -> invalid object, delete it + IdToModelObjectMap::iterator object_item = m_objects.find(m_curr_object.id); + if (object_item != m_objects.end()) + m_objects.erase(object_item); + + IdToAliasesMap::iterator alias_item = m_objects_aliases.find(m_curr_object.id); + if (alias_item != m_objects_aliases.end()) + m_objects_aliases.erase(alias_item); + } + else + // adds components to aliases + m_objects_aliases.insert({ m_curr_object.id, m_curr_object.components }); + } + else { + // geometry defined, store it for later use + m_geometries.insert({ m_curr_object.id, std::move(m_curr_object.geometry) }); + + // stores the object for later use + if (m_objects.find(m_curr_object.id) == m_objects.end()) { + m_objects.insert({ m_curr_object.id, m_curr_object.model_object_idx }); + m_objects_aliases.insert({ m_curr_object.id, { 1, Component(m_curr_object.id) } }); // aliases itself + } + else { + add_error("Found object with duplicate id"); + return false; + } + } + } + + return true; + } + + bool _3MF_Importer::_handle_start_mesh(const char** attributes, unsigned int num_attributes) + { + // reset current geometry + m_curr_object.geometry.reset(); + return true; + } + + bool _3MF_Importer::_handle_end_mesh() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_vertices(const char** attributes, unsigned int num_attributes) + { + // reset current vertices + m_curr_object.geometry.vertices.clear(); + return true; + } + + bool _3MF_Importer::_handle_end_vertices() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_vertex(const char** attributes, unsigned int num_attributes) + { + // appends the vertex coordinates + // missing values are set equal to ZERO + m_curr_object.geometry.vertices.emplace_back( + m_unit_factor * get_attribute_value_float(attributes, num_attributes, X_ATTR), + m_unit_factor * get_attribute_value_float(attributes, num_attributes, Y_ATTR), + m_unit_factor * get_attribute_value_float(attributes, num_attributes, Z_ATTR)); + return true; + } + + bool _3MF_Importer::_handle_end_vertex() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_triangles(const char** attributes, unsigned int num_attributes) + { + // reset current triangles + m_curr_object.geometry.triangles.clear(); + return true; + } + + bool _3MF_Importer::_handle_end_triangles() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_triangle(const char** attributes, unsigned int num_attributes) + { + // we are ignoring the following attributes: + // p1 + // p2 + // p3 + // pid + // see specifications + + // appends the triangle's vertices indices + // missing values are set equal to ZERO + m_curr_object.geometry.triangles.emplace_back( + get_attribute_value_int(attributes, num_attributes, V1_ATTR), + get_attribute_value_int(attributes, num_attributes, V2_ATTR), + get_attribute_value_int(attributes, num_attributes, V3_ATTR)); + + m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); + m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); + m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR)); + return true; + } + + bool _3MF_Importer::_handle_end_triangle() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_components(const char** attributes, unsigned int num_attributes) + { + // reset current components + m_curr_object.components.clear(); + return true; + } + + bool _3MF_Importer::_handle_end_components() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_component(const char** attributes, unsigned int num_attributes) + { + int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); + Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); + + IdToModelObjectMap::iterator object_item = m_objects.find(object_id); + if (object_item == m_objects.end()) { + IdToAliasesMap::iterator alias_item = m_objects_aliases.find(object_id); + if (alias_item == m_objects_aliases.end()) { + add_error("Found component with invalid object id"); + return false; + } + } + + m_curr_object.components.emplace_back(object_id, transform); + + return true; + } + + bool _3MF_Importer::_handle_end_component() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_build(const char** attributes, unsigned int num_attributes) + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_end_build() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_item(const char** attributes, unsigned int num_attributes) + { + // we are ignoring the following attributes + // thumbnail + // partnumber + // pid + // pindex + // see specifications + + int object_id = get_attribute_value_int(attributes, num_attributes, OBJECTID_ATTR); + Transform3d transform = get_transform_from_3mf_specs_string(get_attribute_value_string(attributes, num_attributes, TRANSFORM_ATTR)); + int printable = get_attribute_value_bool(attributes, num_attributes, PRINTABLE_ATTR); + + return _create_object_instance(object_id, transform, printable, 1); + } + + bool _3MF_Importer::_handle_end_item() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_metadata(const char** attributes, unsigned int num_attributes) + { + m_curr_characters.clear(); + + std::string name = get_attribute_value_string(attributes, num_attributes, NAME_ATTR); + if (!name.empty()) + m_curr_metadata_name = name; + + return true; + } + + inline static void check_painting_version(unsigned int loaded_version, unsigned int highest_supported_version, const std::string &error_msg) + { + if (loaded_version > highest_supported_version) + throw version_error(error_msg); + } + + bool _3MF_Importer::_handle_end_metadata() + { + if (m_curr_metadata_name == SLIC3RPE_3MF_VERSION) { + m_version = (unsigned int)atoi(m_curr_characters.c_str()); + if (m_check_version && (m_version > VERSION_3MF_COMPATIBLE)) { + // std::string msg = _(L("The selected 3mf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible.")); + // throw version_error(msg.c_str()); + const std::string msg = (boost::format(_(L("The selected 3mf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str(); + throw version_error(msg); + } + } else if (m_curr_metadata_name == "Application") { + // Generator application of the 3MF. + // SLIC3R_APP_KEY - SLIC3R_VERSION + if (boost::starts_with(m_curr_characters, "PrusaSlicer-")) + m_prusaslicer_generator_version = Semver::parse(m_curr_characters.substr(12)); + } else if (m_curr_metadata_name == SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION) { + m_fdm_supports_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); + check_painting_version(m_fdm_supports_painting_version, FDM_SUPPORTS_PAINTING_VERSION, + _(L("The selected 3MF contains FDM supports painted object using a newer version of PrusaSlicer and is not compatible."))); + } else if (m_curr_metadata_name == SLIC3RPE_SEAM_PAINTING_VERSION) { + m_seam_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); + check_painting_version(m_seam_painting_version, SEAM_PAINTING_VERSION, + _(L("The selected 3MF contains seam painted object using a newer version of PrusaSlicer and is not compatible."))); + } else if (m_curr_metadata_name == SLIC3RPE_MM_PAINTING_VERSION) { + m_mm_painting_version = (unsigned int) atoi(m_curr_characters.c_str()); + check_painting_version(m_mm_painting_version, MM_PAINTING_VERSION, + _(L("The selected 3MF contains multi-material painted object using a newer version of PrusaSlicer and is not compatible."))); + } + + return true; + } + + bool _3MF_Importer::_create_object_instance(int object_id, const Transform3d& transform, const bool printable, unsigned int recur_counter) + { + static const unsigned int MAX_RECURSIONS = 10; + + // escape from circular aliasing + if (recur_counter > MAX_RECURSIONS) { + add_error("Too many recursions"); + return false; + } + + IdToAliasesMap::iterator it = m_objects_aliases.find(object_id); + if (it == m_objects_aliases.end()) { + add_error("Found item with invalid object id"); + return false; + } + + if (it->second.size() == 1 && it->second[0].object_id == object_id) { + // aliasing to itself + + IdToModelObjectMap::iterator object_item = m_objects.find(object_id); + if (object_item == m_objects.end() || object_item->second == -1) { + add_error("Found invalid object"); + return false; + } + else { + ModelInstance* instance = m_model->objects[object_item->second]->add_instance(); + if (instance == nullptr) { + add_error("Unable to add object instance"); + return false; + } + instance->printable = printable; + + m_instances.emplace_back(instance, transform); + } + } + else { + // recursively process nested components + for (const Component& component : it->second) { + if (!_create_object_instance(component.object_id, transform * component.transform, printable, recur_counter + 1)) + return false; + } + } + + return true; + } + + void _3MF_Importer::_apply_transform(ModelInstance& instance, const Transform3d& transform) + { + Slic3r::Geometry::Transformation t(transform); + // invalid scale value, return + if (!t.get_scaling_factor().all()) + return; + + instance.set_transformation(t); + } + + bool _3MF_Importer::_handle_start_config(const char** attributes, unsigned int num_attributes) + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_end_config() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_config_object(const char** attributes, unsigned int num_attributes) + { + int object_id = get_attribute_value_int(attributes, num_attributes, ID_ATTR); + IdToMetadataMap::iterator object_item = m_objects_metadata.find(object_id); + if (object_item != m_objects_metadata.end()) { + add_error("Found duplicated object id"); + return false; + } + + // Added because of github #3435, currently not used by PrusaSlicer + // int instances_count_id = get_attribute_value_int(attributes, num_attributes, INSTANCESCOUNT_ATTR); + + m_objects_metadata.insert({ object_id, ObjectMetadata() }); + m_curr_config.object_id = object_id; + return true; + } + + bool _3MF_Importer::_handle_end_config_object() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_config_volume(const char** attributes, unsigned int num_attributes) + { + IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); + if (object == m_objects_metadata.end()) { + add_error("Cannot assign volume to a valid object"); + return false; + } + + m_curr_config.volume_id = (int)object->second.volumes.size(); + + unsigned int first_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, FIRST_TRIANGLE_ID_ATTR); + unsigned int last_triangle_id = (unsigned int)get_attribute_value_int(attributes, num_attributes, LAST_TRIANGLE_ID_ATTR); + + object->second.volumes.emplace_back(first_triangle_id, last_triangle_id); + return true; + } + + bool _3MF_Importer::_handle_start_config_volume_mesh(const char** attributes, unsigned int num_attributes) + { + IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); + if (object == m_objects_metadata.end()) { + add_error("Cannot assign volume mesh to a valid object"); + return false; + } + if (object->second.volumes.empty()) { + add_error("Cannot assign mesh to a valid olume"); + return false; + } + + ObjectMetadata::VolumeMetadata& volume = object->second.volumes.back(); + + int edges_fixed = get_attribute_value_int(attributes, num_attributes, MESH_STAT_EDGES_FIXED ); + int degenerate_facets = get_attribute_value_int(attributes, num_attributes, MESH_STAT_DEGENERATED_FACETS); + int facets_removed = get_attribute_value_int(attributes, num_attributes, MESH_STAT_FACETS_REMOVED ); + int facets_reversed = get_attribute_value_int(attributes, num_attributes, MESH_STAT_FACETS_RESERVED ); + int backwards_edges = get_attribute_value_int(attributes, num_attributes, MESH_STAT_BACKWARDS_EDGES ); + + volume.mesh_stats = { edges_fixed, degenerate_facets, facets_removed, facets_reversed, backwards_edges }; + + return true; + } + + bool _3MF_Importer::_handle_end_config_volume() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_end_config_volume_mesh() + { + // do nothing + return true; + } + + bool _3MF_Importer::_handle_start_config_metadata(const char** attributes, unsigned int num_attributes) + { + IdToMetadataMap::iterator object = m_objects_metadata.find(m_curr_config.object_id); + if (object == m_objects_metadata.end()) { + add_error("Cannot assign metadata to valid object id"); + return false; + } + + std::string type = get_attribute_value_string(attributes, num_attributes, TYPE_ATTR); + std::string key = get_attribute_value_string(attributes, num_attributes, KEY_ATTR); + std::string value = get_attribute_value_string(attributes, num_attributes, VALUE_ATTR); + + if (type == OBJECT_TYPE) + object->second.metadata.emplace_back(key, value); + else if (type == VOLUME_TYPE) { + if (size_t(m_curr_config.volume_id) < object->second.volumes.size()) + object->second.volumes[m_curr_config.volume_id].metadata.emplace_back(key, value); + } + else { + add_error("Found invalid metadata type"); + return false; + } + + return true; + } + + bool _3MF_Importer::_handle_end_config_metadata() + { + // do nothing + return true; + } + + bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions) + { + if (!object.volumes.empty()) { + add_error("Found invalid volumes count"); + return false; + } + + unsigned int geo_tri_count = (unsigned int)geometry.triangles.size(); + unsigned int renamed_volumes_count = 0; + + for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) { + if (geo_tri_count <= volume_data.first_triangle_id || geo_tri_count <= volume_data.last_triangle_id || volume_data.last_triangle_id < volume_data.first_triangle_id) { + add_error("Found invalid triangle id"); + return false; + } + + Transform3d volume_matrix_to_object = Transform3d::Identity(); + bool has_transform = false; + // extract the volume transformation from the volume's metadata, if present + for (const Metadata& metadata : volume_data.metadata) { + if (metadata.key == MATRIX_KEY) { + volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value); + has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10); + break; + } + } + + // splits volume out of imported geometry + indexed_triangle_set its; + its.indices.assign(geometry.triangles.begin() + volume_data.first_triangle_id, geometry.triangles.begin() + volume_data.last_triangle_id + 1); + const size_t triangles_count = its.indices.size(); + if (triangles_count == 0) { + add_error("An empty triangle mesh found"); + return false; + } + + { + int min_id = its.indices.front()[0]; + int max_id = min_id; + for (const Vec3i& face : its.indices) { + for (const int tri_id : face) { + if (tri_id < 0 || tri_id >= int(geometry.vertices.size())) { + add_error("Found invalid vertex id"); + return false; + } + min_id = std::min(min_id, tri_id); + max_id = std::max(max_id, tri_id); + } + } + its.vertices.assign(geometry.vertices.begin() + min_id, geometry.vertices.begin() + max_id + 1); + + // rebase indices to the current vertices list + for (Vec3i& face : its.indices) + for (int& tri_id : face) + tri_id -= min_id; + } + + if (m_prusaslicer_generator_version && + *m_prusaslicer_generator_version >= *Semver::parse("2.4.0-alpha1") && + *m_prusaslicer_generator_version < *Semver::parse("2.4.0-alpha3")) + // PrusaSlicer 2.4.0-alpha2 contained a bug, where all vertices of a single object were saved for each volume the object contained. + // Remove the vertices, that are not referenced by any face. + its_compactify_vertices(its, true); + + TriangleMesh triangle_mesh(std::move(its), volume_data.mesh_stats); + + if (m_version == 0) { + // if the 3mf was not produced by PrusaSlicer and there is only one instance, + // bake the transformation into the geometry to allow the reload from disk command + // to work properly + if (object.instances.size() == 1) { + triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix(), false); + object.instances.front()->set_transformation(Slic3r::Geometry::Transformation()); + //FIXME do the mesh fixing? + } + } + if (triangle_mesh.volume() < 0) + triangle_mesh.flip_triangles(); + + ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); + // stores the volume matrix taken from the metadata, if present + if (has_transform) + volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); + + // recreate custom supports, seam and mmu segmentation from previously loaded attribute + volume->supported_facets.reserve(triangles_count); + volume->seam_facets.reserve(triangles_count); + volume->mmu_segmentation_facets.reserve(triangles_count); + for (size_t i=0; isupported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); + if (! geometry.custom_seam[index].empty()) + volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); + if (! geometry.mmu_segmentation[index].empty()) + volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]); + } + volume->supported_facets.shrink_to_fit(); + volume->seam_facets.shrink_to_fit(); + volume->mmu_segmentation_facets.shrink_to_fit(); + + // apply the remaining volume's metadata + for (const Metadata& metadata : volume_data.metadata) { + if (metadata.key == NAME_KEY) + volume->name = metadata.value; + else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1")) + volume->set_type(ModelVolumeType::PARAMETER_MODIFIER); + else if (metadata.key == VOLUME_TYPE_KEY) + volume->set_type(ModelVolume::type_from_string(metadata.value)); + else if (metadata.key == SOURCE_FILE_KEY) + volume->source.input_file = metadata.value; + else if (metadata.key == SOURCE_OBJECT_ID_KEY) + volume->source.object_idx = ::atoi(metadata.value.c_str()); + else if (metadata.key == SOURCE_VOLUME_ID_KEY) + volume->source.volume_idx = ::atoi(metadata.value.c_str()); + else if (metadata.key == SOURCE_OFFSET_X_KEY) + volume->source.mesh_offset.x() = ::atof(metadata.value.c_str()); + else if (metadata.key == SOURCE_OFFSET_Y_KEY) + volume->source.mesh_offset.y() = ::atof(metadata.value.c_str()); + else if (metadata.key == SOURCE_OFFSET_Z_KEY) + volume->source.mesh_offset.z() = ::atof(metadata.value.c_str()); + else if (metadata.key == SOURCE_IN_INCHES_KEY) + volume->source.is_converted_from_inches = metadata.value == "1"; + else if (metadata.key == SOURCE_IN_METERS_KEY) + volume->source.is_converted_from_meters = metadata.value == "1"; +#if ENABLE_RELOAD_FROM_DISK_REWORK + else if (metadata.key == SOURCE_IS_BUILTIN_VOLUME_KEY) + volume->source.is_from_builtin_objects = metadata.value == "1"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + else + volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); + } + + // this may happen for 3mf saved by 3rd part softwares + if (volume->name.empty()) { + volume->name = object.name; + if (renamed_volumes_count > 0) + volume->name += "_" + std::to_string(renamed_volumes_count + 1); + ++renamed_volumes_count; + } + } + + return true; + } + + void XMLCALL _3MF_Importer::_handle_start_model_xml_element(void* userData, const char* name, const char** attributes) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_start_model_xml_element(name, attributes); + } + + void XMLCALL _3MF_Importer::_handle_end_model_xml_element(void* userData, const char* name) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_end_model_xml_element(name); + } + + void XMLCALL _3MF_Importer::_handle_model_xml_characters(void* userData, const XML_Char* s, int len) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_model_xml_characters(s, len); + } + + void XMLCALL _3MF_Importer::_handle_start_config_xml_element(void* userData, const char* name, const char** attributes) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_start_config_xml_element(name, attributes); + } + + void XMLCALL _3MF_Importer::_handle_end_config_xml_element(void* userData, const char* name) + { + _3MF_Importer* importer = (_3MF_Importer*)userData; + if (importer != nullptr) + importer->_handle_end_config_xml_element(name); + } + + class _3MF_Exporter : public _3MF_Base + { + struct BuildItem + { + unsigned int id; + Transform3d transform; + bool printable; + + BuildItem(unsigned int id, const Transform3d& transform, const bool printable) + : id(id) + , transform(transform) + , printable(printable) + { + } + }; + + struct Offsets + { + unsigned int first_vertex_id; + unsigned int first_triangle_id; + unsigned int last_triangle_id; + + Offsets(unsigned int first_vertex_id) + : first_vertex_id(first_vertex_id) + , first_triangle_id(-1) + , last_triangle_id(-1) + { + } + }; + + typedef std::map VolumeToOffsetsMap; + + struct ObjectData + { + ModelObject* object; + VolumeToOffsetsMap volumes_offsets; + + explicit ObjectData(ModelObject* object) + : object(object) + { + } + }; + + typedef std::vector BuildItemsList; + typedef std::map IdToObjectDataMap; + + bool m_fullpath_sources{ true }; + bool m_zip64 { true }; + + public: + bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64); + + private: + bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data); + bool _add_content_types_file_to_archive(mz_zip_archive& archive); + bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data); + bool _add_relationships_file_to_archive(mz_zip_archive& archive); + bool _add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data); + bool _add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets); + bool _add_mesh_to_object_stream(mz_zip_writer_staged_context &context, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); + bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); + bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); + bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); + bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); + }; + + bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) + { + clear_errors(); + m_fullpath_sources = fullpath_sources; + m_zip64 = zip64; + return _save_model_to_file(filename, model, config, thumbnail_data); + } + + bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data) + { + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + if (!open_zip_writer(&archive, filename)) { + add_error("Unable to open the file"); + return false; + } + + // Adds content types file ("[Content_Types].xml";). + // The content of this file is the same for each PrusaSlicer 3mf. + if (!_add_content_types_file_to_archive(archive)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + if (thumbnail_data != nullptr && thumbnail_data->is_valid()) { + // Adds the file Metadata/thumbnail.png. + if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + } + + // Adds relationships file ("_rels/.rels"). + // The content of this file is the same for each PrusaSlicer 3mf. + // The relationshis file contains a reference to the geometry file "3D/3dmodel.model", the name was chosen to be compatible with CURA. + if (!_add_relationships_file_to_archive(archive)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds model file ("3D/3dmodel.model"). + // This is the one and only file that contains all the geometry (vertices and triangles) of all ModelVolumes. + IdToObjectDataMap objects_data; + if (!_add_model_file_to_archive(filename, archive, model, objects_data)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds layer height profile file ("Metadata/Slic3r_PE_layer_heights_profile.txt"). + // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. + // The index differes from the index of an object ID of an object instance of a 3MF file! + if (!_add_layer_height_profile_file_to_archive(archive, model)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt"). + // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. + // The index differes from the index of an object ID of an object instance of a 3MF file! + if (!_add_layer_config_ranges_file_to_archive(archive, model)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds sla support points file ("Metadata/Slic3r_PE_sla_support_points.txt"). + // All sla support points of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. + // The index differes from the index of an object ID of an object instance of a 3MF file! + if (!_add_sla_support_points_file_to_archive(archive, model)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + if (!_add_sla_drain_holes_file_to_archive(archive, model)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + + // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"). + // All custom gcode per height of whole Model are stored here + if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model, config)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + // Adds slic3r print config file ("Metadata/Slic3r_PE.config"). + // This file contains the content of FullPrintConfing / SLAFullPrintConfig. + if (config != nullptr) { + if (!_add_print_config_file_to_archive(archive, *config)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + } + + // Adds slic3r model config file ("Metadata/Slic3r_PE_model.config"). + // This file contains all the attributes of all ModelObjects and their ModelVolumes (names, parameter overrides). + // As there is just a single Indexed Triangle Set data stored per ModelObject, offsets of volumes into their respective Indexed Triangle Set data + // is stored here as well. + if (!_add_model_config_file_to_archive(archive, model, objects_data)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + return false; + } + + if (!mz_zip_writer_finalize_archive(&archive)) { + close_zip_writer(&archive); + boost::filesystem::remove(filename); + add_error("Unable to finalize the archive"); + return false; + } + + close_zip_writer(&archive); + + return true; + } + + bool _3MF_Exporter::_add_content_types_file_to_archive(mz_zip_archive& archive) + { + std::stringstream stream; + stream << "\n"; + stream << "\n"; + stream << " \n"; + stream << " \n"; + stream << " \n"; + stream << ""; + + std::string out = stream.str(); + + if (!mz_zip_writer_add_mem(&archive, CONTENT_TYPES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add content types file to archive"); + return false; + } + + return true; + } + + bool _3MF_Exporter::_add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data) + { + bool res = false; + + size_t png_size = 0; + void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); + if (png_data != nullptr) { + res = mz_zip_writer_add_mem(&archive, THUMBNAIL_FILE.c_str(), (const void*)png_data, png_size, MZ_DEFAULT_COMPRESSION); + mz_free(png_data); + } + + if (!res) + add_error("Unable to add thumbnail file to archive"); + + return res; + } + + bool _3MF_Exporter::_add_relationships_file_to_archive(mz_zip_archive& archive) + { + std::stringstream stream; + stream << "\n"; + stream << "\n"; + stream << " \n"; + stream << " \n"; + stream << ""; + + std::string out = stream.str(); + + if (!mz_zip_writer_add_mem(&archive, RELATIONSHIPS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add relationships file to archive"); + return false; + } + + return true; + } + + static void reset_stream(std::stringstream &stream) + { + stream.str(""); + stream.clear(); + // https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 + // Conversion of a floating-point value to text and back is exact as long as at least max_digits10 were used (9 for float, 17 for double). + // It is guaranteed to produce the same floating-point value, even though the intermediate text representation is not exact. + // The default value of std::stream precision is 6 digits only! + stream << std::setprecision(std::numeric_limits::max_digits10); + } + + bool _3MF_Exporter::_add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data) + { + mz_zip_writer_staged_context context; + if (!mz_zip_writer_add_staged_open(&archive, &context, MODEL_FILE.c_str(), + m_zip64 ? + // Maximum expected and allowed 3MF file size is 16GiB. + // This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records. + (uint64_t(1) << 30) * 16 : + // Maximum expected 3MF file size is 4GB-1. This is a workaround for interoperability with Windows 10 3D model fixing API, see + // GH issue #6193. + (uint64_t(1) << 32) - 1, + nullptr, nullptr, 0, MZ_DEFAULT_COMPRESSION, nullptr, 0, nullptr, 0)) { + add_error("Unable to add model file to archive"); + return false; + } + + { + std::stringstream stream; + reset_stream(stream); + stream << "\n"; + stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n"; + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "\n"; + + if (model.is_fdm_support_painted()) + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_FDM_SUPPORTS_PAINTING_VERSION << "\">" << FDM_SUPPORTS_PAINTING_VERSION << "\n"; + + if (model.is_seam_painted()) + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_SEAM_PAINTING_VERSION << "\">" << SEAM_PAINTING_VERSION << "\n"; + + if (model.is_mm_painted()) + stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_MM_PAINTING_VERSION << "\">" << MM_PAINTING_VERSION << "\n"; + + std::string name = xml_escape(boost::filesystem::path(filename).stem().string()); + stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "\n"; + stream << " <" << METADATA_TAG << " name=\"Designer\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"Description\">" << name << "\n"; + stream << " <" << METADATA_TAG << " name=\"Copyright\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"LicenseTerms\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"Rating\">" << "\n"; + std::string date = Slic3r::Utils::utc_timestamp(Slic3r::Utils::get_current_time_utc()); + // keep only the date part of the string + date = date.substr(0, 10); + stream << " <" << METADATA_TAG << " name=\"CreationDate\">" << date << "\n"; + stream << " <" << METADATA_TAG << " name=\"ModificationDate\">" << date << "\n"; + stream << " <" << METADATA_TAG << " name=\"Application\">" << SLIC3R_APP_KEY << "-" << SLIC3R_VERSION << "\n"; + stream << " <" << RESOURCES_TAG << ">\n"; + std::string buf = stream.str(); + if (! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) { + add_error("Unable to add model file to archive"); + return false; + } + } + + // Instance transformations, indexed by the 3MF object ID (which is a linear serialization of all instances of all ModelObjects). + BuildItemsList build_items; + + // The object_id here is a one based identifier of the first instance of a ModelObject in the 3MF file, where + // all the object instances of all ModelObjects are stored and indexed in a 1 based linear fashion. + // Therefore the list of object_ids here may not be continuous. + unsigned int object_id = 1; + for (ModelObject* obj : model.objects) { + if (obj == nullptr) + continue; + + // Index of an object in the 3MF file corresponding to the 1st instance of a ModelObject. + unsigned int curr_id = object_id; + IdToObjectDataMap::iterator object_it = objects_data.insert({ curr_id, ObjectData(obj) }).first; + // Store geometry of all ModelVolumes contained in a single ModelObject into a single 3MF indexed triangle set object. + // object_it->second.volumes_offsets will contain the offsets of the ModelVolumes in that single indexed triangle set. + // object_id will be increased to point to the 1st instance of the next ModelObject. + if (!_add_object_to_model_stream(context, object_id, *obj, build_items, object_it->second.volumes_offsets)) { + add_error("Unable to add object to archive"); + mz_zip_writer_add_staged_finish(&context); + return false; + } + } + + { + std::stringstream stream; + reset_stream(stream); + stream << " \n"; + + // Store the transformations of all the ModelInstances of all ModelObjects, indexed in a linear fashion. + if (!_add_build_to_model_stream(stream, build_items)) { + add_error("Unable to add build to archive"); + mz_zip_writer_add_staged_finish(&context); + return false; + } + + stream << "\n"; + + std::string buf = stream.str(); + + if ((! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) || + ! mz_zip_writer_add_staged_finish(&context)) { + add_error("Unable to add model file to archive"); + return false; + } + } + + return true; + } + + bool _3MF_Exporter::_add_object_to_model_stream(mz_zip_writer_staged_context &context, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets) + { + std::stringstream stream; + reset_stream(stream); + unsigned int id = 0; + for (const ModelInstance* instance : object.instances) { + assert(instance != nullptr); + if (instance == nullptr) + continue; + + unsigned int instance_id = object_id + id; + stream << " <" << OBJECT_TAG << " id=\"" << instance_id << "\" type=\"model\">\n"; + + if (id == 0) { + std::string buf = stream.str(); + reset_stream(stream); + if ((! buf.empty() && ! mz_zip_writer_add_staged_data(&context, buf.data(), buf.size())) || + ! _add_mesh_to_object_stream(context, object, volumes_offsets)) { + add_error("Unable to add mesh to archive"); + return false; + } + } + else { + stream << " <" << COMPONENTS_TAG << ">\n"; + stream << " <" << COMPONENT_TAG << " objectid=\"" << object_id << "\"/>\n"; + stream << " \n"; + } + + Transform3d t = instance->get_matrix(); + // instance_id is just a 1 indexed index in build_items. + assert(instance_id == build_items.size() + 1); + build_items.emplace_back(instance_id, t, instance->printable); + + stream << " \n"; + + ++id; + } + + object_id += id; + std::string buf = stream.str(); + return buf.empty() || mz_zip_writer_add_staged_data(&context, buf.data(), buf.size()); + } + +#if EXPORT_3MF_USE_SPIRIT_KARMA_FP + template + struct coordinate_policy_fixed : boost::spirit::karma::real_policies + { + static int floatfield(Num n) { return fmtflags::fixed; } + // Number of decimal digits to maintain float accuracy when storing into a text file and parsing back. + static unsigned precision(Num /* n */) { return std::numeric_limits::max_digits10 + 1; } + // No trailing zeros, thus for fmtflags::fixed usually much less than max_digits10 decimal numbers will be produced. + static bool trailing_zeros(Num /* n */) { return false; } + }; + template + struct coordinate_policy_scientific : coordinate_policy_fixed + { + static int floatfield(Num n) { return fmtflags::scientific; } + }; + // Define a new generator type based on the new coordinate policy. + using coordinate_type_fixed = boost::spirit::karma::real_generator>; + using coordinate_type_scientific = boost::spirit::karma::real_generator>; +#endif // EXPORT_3MF_USE_SPIRIT_KARMA_FP + + bool _3MF_Exporter::_add_mesh_to_object_stream(mz_zip_writer_staged_context &context, ModelObject& object, VolumeToOffsetsMap& volumes_offsets) + { + std::string output_buffer; + output_buffer += " <"; + output_buffer += MESH_TAG; + output_buffer += ">\n <"; + output_buffer += VERTICES_TAG; + output_buffer += ">\n"; + + auto flush = [this, &output_buffer, &context](bool force = false) { + if ((force && ! output_buffer.empty()) || output_buffer.size() >= 65536 * 16) { + if (! mz_zip_writer_add_staged_data(&context, output_buffer.data(), output_buffer.size())) { + add_error("Error during writing or compression"); + return false; + } + output_buffer.clear(); + } + return true; + }; + + auto format_coordinate = [](float f, char *buf) -> char* { + assert(is_decimal_separator_point()); +#if EXPORT_3MF_USE_SPIRIT_KARMA_FP + // Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, + // https://github.com/boostorg/spirit/pull/586 + // where the exported string is one digit shorter than it should be to guarantee lossless round trip. + // The code is left here for the ocasion boost guys improve. + coordinate_type_fixed const coordinate_fixed = coordinate_type_fixed(); + coordinate_type_scientific const coordinate_scientific = coordinate_type_scientific(); + // Format "f" in a fixed format. + char *ptr = buf; + boost::spirit::karma::generate(ptr, coordinate_fixed, f); + // Format "f" in a scientific format. + char *ptr2 = ptr; + boost::spirit::karma::generate(ptr2, coordinate_scientific, f); + // Return end of the shorter string. + auto len2 = ptr2 - ptr; + if (ptr - buf > len2) { + // Move the shorter scientific form to the front. + memcpy(buf, ptr, len2); + ptr = buf + len2; + } + // Return pointer to the end. + return ptr; +#else + // Round-trippable float, shortest possible. + return buf + sprintf(buf, "%.9g", f); +#endif + }; + + char buf[256]; + unsigned int vertices_count = 0; + for (ModelVolume* volume : object.volumes) { + if (volume == nullptr) + continue; + + volumes_offsets.insert({ volume, Offsets(vertices_count) }); + + const indexed_triangle_set &its = volume->mesh().its; + if (its.vertices.empty()) { + add_error("Found invalid mesh"); + return false; + } + + vertices_count += (int)its.vertices.size(); + + const Transform3d& matrix = volume->get_matrix(); + + for (size_t i = 0; i < its.vertices.size(); ++i) { + Vec3f v = (matrix * its.vertices[i].cast()).cast(); + char *ptr = buf; + boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << VERTEX_TAG << " x=\""); + ptr = format_coordinate(v.x(), ptr); + boost::spirit::karma::generate(ptr, "\" y=\""); + ptr = format_coordinate(v.y(), ptr); + boost::spirit::karma::generate(ptr, "\" z=\""); + ptr = format_coordinate(v.z(), ptr); + boost::spirit::karma::generate(ptr, "\"/>\n"); + *ptr = '\0'; + output_buffer += buf; + if (! flush()) + return false; + } + } + + output_buffer += " \n <"; + output_buffer += TRIANGLES_TAG; + output_buffer += ">\n"; + + unsigned int triangles_count = 0; + for (ModelVolume* volume : object.volumes) { + if (volume == nullptr) + continue; + + bool is_left_handed = volume->is_left_handed(); + VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume); + assert(volume_it != volumes_offsets.end()); + + const indexed_triangle_set &its = volume->mesh().its; + + // updates triangle offsets + volume_it->second.first_triangle_id = triangles_count; + triangles_count += (int)its.indices.size(); + volume_it->second.last_triangle_id = triangles_count - 1; + + for (int i = 0; i < int(its.indices.size()); ++ i) { + { + const Vec3i &idx = its.indices[i]; + char *ptr = buf; + boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG << + " v1=\"" << boost::spirit::int_ << + "\" v2=\"" << boost::spirit::int_ << + "\" v3=\"" << boost::spirit::int_ << "\"", + idx[is_left_handed ? 2 : 0] + volume_it->second.first_vertex_id, + idx[1] + volume_it->second.first_vertex_id, + idx[is_left_handed ? 0 : 2] + volume_it->second.first_vertex_id); + *ptr = '\0'; + output_buffer += buf; + } + + std::string custom_supports_data_string = volume->supported_facets.get_triangle_as_string(i); + if (! custom_supports_data_string.empty()) { + output_buffer += " "; + output_buffer += CUSTOM_SUPPORTS_ATTR; + output_buffer += "=\""; + output_buffer += custom_supports_data_string; + output_buffer += "\""; + } + + std::string custom_seam_data_string = volume->seam_facets.get_triangle_as_string(i); + if (! custom_seam_data_string.empty()) { + output_buffer += " "; + output_buffer += CUSTOM_SEAM_ATTR; + output_buffer += "=\""; + output_buffer += custom_seam_data_string; + output_buffer += "\""; + } + + std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i); + if (! mmu_painting_data_string.empty()) { + output_buffer += " "; + output_buffer += MMU_SEGMENTATION_ATTR; + output_buffer += "=\""; + output_buffer += mmu_painting_data_string; + output_buffer += "\""; + } + + output_buffer += "/>\n"; + + if (! flush()) + return false; + } + } + + output_buffer += " \n \n"; + + // Force flush. + return flush(true); + } + + bool _3MF_Exporter::_add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) + { + // This happens for empty projects + if (build_items.size() == 0) { + add_error("No build item found"); + return true; + } + + stream << " <" << BUILD_TAG << ">\n"; + + for (const BuildItem& item : build_items) { + stream << " <" << ITEM_TAG << " " << OBJECTID_ATTR << "=\"" << item.id << "\" " << TRANSFORM_ATTR << "=\""; + for (unsigned c = 0; c < 4; ++c) { + for (unsigned r = 0; r < 3; ++r) { + stream << item.transform(r, c); + if (r != 2 || c != 3) + stream << " "; + } + } + stream << "\" " << PRINTABLE_ATTR << "=\"" << item.printable << "\"/>\n"; + } + + stream << " \n"; + + return true; + } + + bool _3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model) + { + assert(is_decimal_separator_point()); + std::string out = ""; + char buffer[1024]; + + unsigned int count = 0; + for (const ModelObject* object : model.objects) { + ++count; + const std::vector& layer_height_profile = object->layer_height_profile.get(); + if (layer_height_profile.size() >= 4 && layer_height_profile.size() % 2 == 0) { + sprintf(buffer, "object_id=%d|", count); + out += buffer; + + // Store the layer height profile as a single semicolon separated list. + for (size_t i = 0; i < layer_height_profile.size(); ++i) { + sprintf(buffer, (i == 0) ? "%f" : ";%f", layer_height_profile[i]); + out += buffer; + } + + out += "\n"; + } + } + + if (!out.empty()) { + if (!mz_zip_writer_add_mem(&archive, LAYER_HEIGHTS_PROFILE_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add layer heights profile file to archive"); + return false; + } + } + + return true; + } + + bool _3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) + { + std::string out = ""; + pt::ptree tree; + + unsigned int object_cnt = 0; + for (const ModelObject* object : model.objects) { + object_cnt++; + const t_layer_config_ranges& ranges = object->layer_config_ranges; + if (!ranges.empty()) + { + pt::ptree& obj_tree = tree.add("objects.object",""); + + obj_tree.put(".id", object_cnt); + + // Store the layer config ranges. + for (const auto& range : ranges) { + pt::ptree& range_tree = obj_tree.add("range", ""); + + // store minX and maxZ + range_tree.put(".min_z", range.first.first); + range_tree.put(".max_z", range.first.second); + + // store range configuration + const ModelConfig& config = range.second; + for (const std::string& opt_key : config.keys()) { + pt::ptree& opt_tree = range_tree.add("option", config.opt_serialize(opt_key)); + opt_tree.put(".opt_key", opt_key); + } + } + } + } + + if (!tree.empty()) { + std::ostringstream oss; + pt::write_xml(oss, tree); + out = oss.str(); + + // Post processing("beautification") of the output string for a better preview + boost::replace_all(out, ">\n \n \n ", ">\n "); + boost::replace_all(out, ">", ">\n "); + // OR just + boost::replace_all(out, "><", ">\n<"); + } + + if (!out.empty()) { + if (!mz_zip_writer_add_mem(&archive, LAYER_CONFIG_RANGES_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add layer heights profile file to archive"); + return false; + } + } + + return true; + } + + bool _3MF_Exporter::_add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model) + { + assert(is_decimal_separator_point()); + std::string out = ""; + char buffer[1024]; + + unsigned int count = 0; + for (const ModelObject* object : model.objects) { + ++count; + const std::vector& sla_support_points = object->sla_support_points; + if (!sla_support_points.empty()) { + sprintf(buffer, "object_id=%d|", count); + out += buffer; + + // Store the layer height profile as a single space separated list. + for (size_t i = 0; i < sla_support_points.size(); ++i) { + sprintf(buffer, (i==0 ? "%f %f %f %f %f" : " %f %f %f %f %f"), sla_support_points[i].pos(0), sla_support_points[i].pos(1), sla_support_points[i].pos(2), sla_support_points[i].head_front_radius, (float)sla_support_points[i].is_new_island); + out += buffer; + } + out += "\n"; + } + } + + if (!out.empty()) { + // Adds version header at the beginning: + out = std::string("support_points_format_version=") + std::to_string(support_points_format_version) + std::string("\n") + out; + + if (!mz_zip_writer_add_mem(&archive, SLA_SUPPORT_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add sla support points file to archive"); + return false; + } + } + return true; + } + + bool _3MF_Exporter::_add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model) + { + assert(is_decimal_separator_point()); + const char *const fmt = "object_id=%d|"; + std::string out; + + unsigned int count = 0; + for (const ModelObject* object : model.objects) { + ++count; + sla::DrainHoles drain_holes = object->sla_drain_holes; + + // The holes were placed 1mm above the mesh in the first implementation. + // This was a bad idea and the reference point was changed in 2.3 so + // to be on the mesh exactly. The elevated position is still saved + // in 3MFs for compatibility reasons. + for (sla::DrainHole& hole : drain_holes) { + hole.pos -= hole.normal.normalized(); + hole.height += 1.f; + } + + if (!drain_holes.empty()) { + out += string_printf(fmt, count); + + // Store the layer height profile as a single space separated list. + for (size_t i = 0; i < drain_holes.size(); ++i) + out += string_printf((i == 0 ? "%f %f %f %f %f %f %f %f" : " %f %f %f %f %f %f %f %f"), + drain_holes[i].pos(0), + drain_holes[i].pos(1), + drain_holes[i].pos(2), + drain_holes[i].normal(0), + drain_holes[i].normal(1), + drain_holes[i].normal(2), + drain_holes[i].radius, + drain_holes[i].height); + + out += "\n"; + } + } + + if (!out.empty()) { + // Adds version header at the beginning: + out = std::string("drain_holes_format_version=") + std::to_string(drain_holes_format_version) + std::string("\n") + out; + + if (!mz_zip_writer_add_mem(&archive, SLA_DRAIN_HOLES_FILE.c_str(), static_cast(out.data()), out.length(), mz_uint(MZ_DEFAULT_COMPRESSION))) { + add_error("Unable to add sla support points file to archive"); + return false; + } + } + return true; + } + + bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config) + { + assert(is_decimal_separator_point()); + char buffer[1024]; + sprintf(buffer, "; %s\n\n", header_slic3r_generated().c_str()); + std::string out = buffer; + + for (const std::string &key : config.keys()) + if (key != "compatible_printers") + out += "; " + key + " = " + config.opt_serialize(key) + "\n"; + + if (!out.empty()) { + if (!mz_zip_writer_add_mem(&archive, PRINT_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add print config file to archive"); + return false; + } + } + + return true; + } + + bool _3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data) + { + std::stringstream stream; + // Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back + // when loaded as accurately as possible. + stream << std::setprecision(std::numeric_limits::max_digits10); + stream << "\n"; + stream << "<" << CONFIG_TAG << ">\n"; + + for (const IdToObjectDataMap::value_type& obj_metadata : objects_data) { + const ModelObject* obj = obj_metadata.second.object; + if (obj != nullptr) { + // Output of instances count added because of github #3435, currently not used by PrusaSlicer + stream << " <" << OBJECT_TAG << " " << ID_ATTR << "=\"" << obj_metadata.first << "\" " << INSTANCESCOUNT_ATTR << "=\"" << obj->instances.size() << "\">\n"; + + // stores object's name + if (!obj->name.empty()) + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"name\" " << VALUE_ATTR << "=\"" << xml_escape(obj->name) << "\"/>\n"; + + // stores object's config data + for (const std::string& key : obj->config.keys()) { + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << OBJECT_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << obj->config.opt_serialize(key) << "\"/>\n"; + } + + for (const ModelVolume* volume : obj_metadata.second.object->volumes) { + if (volume != nullptr) { + const VolumeToOffsetsMap& offsets = obj_metadata.second.volumes_offsets; + VolumeToOffsetsMap::const_iterator it = offsets.find(volume); + if (it != offsets.end()) { + // stores volume's offsets + stream << " <" << VOLUME_TAG << " "; + stream << FIRST_TRIANGLE_ID_ATTR << "=\"" << it->second.first_triangle_id << "\" "; + stream << LAST_TRIANGLE_ID_ATTR << "=\"" << it->second.last_triangle_id << "\">\n"; + + // stores volume's name + if (!volume->name.empty()) + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << NAME_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->name) << "\"/>\n"; + + // stores volume's modifier field (legacy, to support old slicers) + if (volume->is_modifier()) + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MODIFIER_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + // stores volume's type (overrides the modifier field above) + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << VOLUME_TYPE_KEY << "\" " << + VALUE_ATTR << "=\"" << ModelVolume::type_to_string(volume->type()) << "\"/>\n"; + + // stores volume's local matrix + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; + const Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); + for (int r = 0; r < 4; ++r) { + for (int c = 0; c < 4; ++c) { + stream << matrix(r, c); + if (r != 3 || c != 3) + stream << " "; + } + } + stream << "\"/>\n"; + + // stores volume's source data + { + std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); + std::string prefix = std::string(" <") + METADATA_TAG + " " + TYPE_ATTR + "=\"" + VOLUME_TYPE + "\" " + KEY_ATTR + "=\""; + if (! volume->source.input_file.empty()) { + stream << prefix << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n"; + stream << prefix << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n"; + stream << prefix << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n"; + stream << prefix << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n"; + stream << prefix << SOURCE_OFFSET_Y_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(1) << "\"/>\n"; + stream << prefix << SOURCE_OFFSET_Z_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(2) << "\"/>\n"; + } + assert(! volume->source.is_converted_from_inches || ! volume->source.is_converted_from_meters); + if (volume->source.is_converted_from_inches) + stream << prefix << SOURCE_IN_INCHES_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; + else if (volume->source.is_converted_from_meters) + stream << prefix << SOURCE_IN_METERS_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; +#if ENABLE_RELOAD_FROM_DISK_REWORK + if (volume->source.is_from_builtin_objects) + stream << prefix << SOURCE_IS_BUILTIN_VOLUME_KEY << "\" " << VALUE_ATTR << "=\"1\"/>\n"; +#endif // ENABLE_RELOAD_FROM_DISK_REWORK + } + + // stores volume's config data + for (const std::string& key : volume->config.keys()) { + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << key << "\" " << VALUE_ATTR << "=\"" << volume->config.opt_serialize(key) << "\"/>\n"; + } + + // stores mesh's statistics + const RepairedMeshErrors& stats = volume->mesh().stats().repaired_errors; + stream << " <" << MESH_TAG << " "; + stream << MESH_STAT_EDGES_FIXED << "=\"" << stats.edges_fixed << "\" "; + stream << MESH_STAT_DEGENERATED_FACETS << "=\"" << stats.degenerate_facets << "\" "; + stream << MESH_STAT_FACETS_REMOVED << "=\"" << stats.facets_removed << "\" "; + stream << MESH_STAT_FACETS_RESERVED << "=\"" << stats.facets_reversed << "\" "; + stream << MESH_STAT_BACKWARDS_EDGES << "=\"" << stats.backwards_edges << "\"/>\n"; + + stream << " \n"; + } + } + } + + stream << " \n"; + } + } + + stream << "\n"; + + std::string out = stream.str(); + + if (!mz_zip_writer_add_mem(&archive, MODEL_CONFIG_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add model config file to archive"); + return false; + } + + return true; + } + +bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config) +{ + std::string out = ""; + + if (!model.custom_gcode_per_print_z.gcodes.empty()) { + pt::ptree tree; + pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", ""); + + for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) { + pt::ptree& code_tree = main_tree.add("code", ""); + + // store data of custom_gcode_per_print_z + code_tree.put(".print_z" , code.print_z ); + code_tree.put(".type" , static_cast(code.type)); + code_tree.put(".extruder" , code.extruder ); + code_tree.put(".color" , code.color ); + code_tree.put(".extra" , code.extra ); + + // add gcode field data for the old version of the PrusaSlicer + std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") : + code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") : + code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") : + code.type == CustomGCode::ToolChange ? "tool_change" : code.extra; + code_tree.put(".gcode" , gcode ); + } + + pt::ptree& mode_tree = main_tree.add("mode", ""); + // store mode of a custom_gcode_per_print_z + mode_tree.put(".value", model.custom_gcode_per_print_z.mode == CustomGCode::Mode::SingleExtruder ? CustomGCode::SingleExtruderMode : + model.custom_gcode_per_print_z.mode == CustomGCode::Mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode : + CustomGCode::MultiExtruderMode); + + if (!tree.empty()) { + std::ostringstream oss; + boost::property_tree::write_xml(oss, tree); + out = oss.str(); + + // Post processing("beautification") of the output string + boost::replace_all(out, "><", ">\n<"); + } + } + + if (!out.empty()) { + if (!mz_zip_writer_add_mem(&archive, CUSTOM_GCODE_PER_PRINT_Z_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) { + add_error("Unable to add custom Gcodes per print_z file to archive"); + return false; + } + } + + return true; +} + +// Perform conversions based on the config values available. +//FIXME provide a version of PrusaSlicer that stored the project file (3MF). +static void handle_legacy_project_loaded(unsigned int version_project_file, DynamicPrintConfig& config) +{ + if (! config.has("brim_separation")) { + if (auto *opt_elephant_foot = config.option("elefant_foot_compensation", false); opt_elephant_foot) { + // Conversion from older PrusaSlicer which applied brim separation equal to elephant foot compensation. + auto *opt_brim_separation = config.option("brim_separation", true); + opt_brim_separation->value = opt_elephant_foot->value; + } + } +} + +bool is_project_3mf(const std::string& filename) +{ + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + + if (!open_zip_reader(&archive, filename)) + return false; + + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + + // loop the entries to search for config + mz_zip_archive_file_stat stat; + bool config_found = false; + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + std::replace(name.begin(), name.end(), '\\', '/'); + + if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { + config_found = true; + break; + } + } + } + + close_zip_reader(&archive); + + return config_found; +} + +bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) +{ + if (path == nullptr || model == nullptr) + return false; + + // All import should use "C" locales for number formatting. + CNumericLocalesSetter locales_setter; + _3MF_Importer importer; + bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version); + importer.log_errors(); + handle_legacy_project_loaded(importer.version(), config); + return res; +} + +bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) +{ + // All export should use "C" locales for number formatting. + CNumericLocalesSetter locales_setter; + + if (path == nullptr || model == nullptr) + return false; + + _3MF_Exporter exporter; + bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data, zip64); + if (!res) + exporter.log_errors(); + + return res; +} +} // namespace Slic3r From f8ec5fc9e78c851952ef4c9bfa479afdd4841e73 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 23 May 2022 10:55:23 +0200 Subject: [PATCH 033/169] Revert CMAKE_FIND_PACKAGE_PREFER_CONFIG as its from cmake > 3.13 Do the overriding in the appropriate find modules in cmake/modules --- CMakeLists.txt | 21 +---- cmake/modules/FindCURL.cmake | 155 ++++++++++++++++++++--------------- 2 files changed, 89 insertions(+), 87 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 002cd3456..4267de2fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,14 +43,6 @@ set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux") set(IS_CROSS_COMPILE FALSE) -if (SLIC3R_STATIC) - # Prefer config scripts over find modules. This is helpful when building with - # the static dependencies. Many libraries have their own export scripts - # while having a Find module in standard cmake installation. - # (e.g. CURL) - set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) -endif () - if (APPLE) set(CMAKE_FIND_FRAMEWORK LAST) set(CMAKE_FIND_APPBUNDLE LAST) @@ -458,19 +450,10 @@ if (NOT EIGEN3_FOUND) endif () include_directories(BEFORE SYSTEM ${EIGEN3_INCLUDE_DIR}) -# Find expat or use bundled version -# Always use the system libexpat on Linux. - +# Find expat. We have our overriden FindEXPAT which exports libexpat target +# no matter what. find_package(EXPAT REQUIRED) -add_library(libexpat INTERFACE) - -if (TARGET EXPAT::EXPAT ) - target_link_libraries(libexpat INTERFACE EXPAT::EXPAT) -elseif(TARGET expat::expat) - target_link_libraries(libexpat INTERFACE expat::expat) -endif () - find_package(PNG REQUIRED) set(OpenGL_GL_PREFERENCE "LEGACY") diff --git a/cmake/modules/FindCURL.cmake b/cmake/modules/FindCURL.cmake index e0deafa45..ced591134 100644 --- a/cmake/modules/FindCURL.cmake +++ b/cmake/modules/FindCURL.cmake @@ -30,82 +30,101 @@ # ``CURL_VERSION_STRING`` # The version of curl found. -# Look for the header file. -find_path(CURL_INCLUDE_DIR NAMES curl/curl.h) -mark_as_advanced(CURL_INCLUDE_DIR) +# First, prefer config scripts +set(_q "") +if(CURL_FIND_QUIETLY) + set(_q QUIET) +endif() +find_package(CURL ${CURL_FIND_VERSION} CONFIG ${_q}) -if(NOT CURL_LIBRARY) - # Look for the library (sorted from most current/relevant entry to least). - find_library(CURL_LIBRARY_RELEASE NAMES - curl - # Windows MSVC prebuilts: - curllib - libcurl_imp - curllib_static - # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip): - libcurl - # Static library on Windows - libcurl_a - ) - mark_as_advanced(CURL_LIBRARY_RELEASE) - - find_library(CURL_LIBRARY_DEBUG NAMES - # Windows MSVC CMake builds in debug configuration on vcpkg: - libcurl-d_imp - libcurl-d - # Static library on Windows, compiled in debug mode - libcurl_a_debug - ) - mark_as_advanced(CURL_LIBRARY_DEBUG) - - include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations_SLIC3R.cmake) - select_library_configurations_SLIC3R(CURL) +if(NOT CURL_FIND_QUIETLY) + if (NOT CURL_FOUND) + message(STATUS "Falling back to MODULE search for CURL...") + else() + message(STATUS "CURL found in ${CURL_DIR}") + endif() endif() -if(CURL_INCLUDE_DIR) - foreach(_curl_version_header curlver.h curl.h) - if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}") - file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"") +if (NOT CURL_FOUND) - string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}") - unset(curl_version_str) - break() - endif() - endforeach() -endif() + # Look for the header file. + find_path(CURL_INCLUDE_DIR NAMES curl/curl.h) + mark_as_advanced(CURL_INCLUDE_DIR) -include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs_SLIC3R.cmake) -FIND_PACKAGE_HANDLE_STANDARD_ARGS_SLIC3R(CURL - REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR - VERSION_VAR CURL_VERSION_STRING) + if(NOT CURL_LIBRARY) + # Look for the library (sorted from most current/relevant entry to least). + find_library(CURL_LIBRARY_RELEASE NAMES + curl + # Windows MSVC prebuilts: + curllib + libcurl_imp + curllib_static + # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip): + libcurl + # Static library on Windows + libcurl_a + ) + mark_as_advanced(CURL_LIBRARY_RELEASE) -if(CURL_FOUND) - set(CURL_LIBRARIES ${CURL_LIBRARY}) - set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR}) + find_library(CURL_LIBRARY_DEBUG NAMES + # Windows MSVC CMake builds in debug configuration on vcpkg: + libcurl-d_imp + libcurl-d + # Static library on Windows, compiled in debug mode + libcurl_a_debug + ) + mark_as_advanced(CURL_LIBRARY_DEBUG) - if(NOT TARGET CURL::libcurl) - add_library(CURL::libcurl UNKNOWN IMPORTED) - set_target_properties(CURL::libcurl PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${CURL_INCLUDE_DIRS}") + include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations_SLIC3R.cmake) + select_library_configurations_SLIC3R(CURL) + endif() - if(EXISTS "${CURL_LIBRARY}") + if(CURL_INCLUDE_DIR) + foreach(_curl_version_header curlver.h curl.h) + if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}") + file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"") + + string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}") + unset(curl_version_str) + break() + endif() + endforeach() + endif() + + include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs_SLIC3R.cmake) + FIND_PACKAGE_HANDLE_STANDARD_ARGS_SLIC3R(CURL + REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR + VERSION_VAR CURL_VERSION_STRING) + + if(CURL_FOUND) + set(CURL_LIBRARIES ${CURL_LIBRARY}) + set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR}) + + if(NOT TARGET CURL::libcurl) + add_library(CURL::libcurl UNKNOWN IMPORTED) set_target_properties(CURL::libcurl PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION "${CURL_LIBRARY}") - endif() - if(CURL_LIBRARY_RELEASE) - set_property(TARGET CURL::libcurl APPEND PROPERTY - IMPORTED_CONFIGURATIONS RELEASE) - set_target_properties(CURL::libcurl PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION_RELEASE "${CURL_LIBRARY_RELEASE}") - endif() - if(CURL_LIBRARY_DEBUG) - set_property(TARGET CURL::libcurl APPEND PROPERTY - IMPORTED_CONFIGURATIONS DEBUG) - set_target_properties(CURL::libcurl PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION_DEBUG "${CURL_LIBRARY_DEBUG}") + INTERFACE_INCLUDE_DIRECTORIES "${CURL_INCLUDE_DIRS}") + + if(EXISTS "${CURL_LIBRARY}") + set_target_properties(CURL::libcurl PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${CURL_LIBRARY}") + endif() + if(CURL_LIBRARY_RELEASE) + set_property(TARGET CURL::libcurl APPEND PROPERTY + IMPORTED_CONFIGURATIONS RELEASE) + set_target_properties(CURL::libcurl PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION_RELEASE "${CURL_LIBRARY_RELEASE}") + endif() + if(CURL_LIBRARY_DEBUG) + set_property(TARGET CURL::libcurl APPEND PROPERTY + IMPORTED_CONFIGURATIONS DEBUG) + set_target_properties(CURL::libcurl PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION_DEBUG "${CURL_LIBRARY_DEBUG}") + endif() endif() endif() -endif() + +endif (NOT CURL_FOUND) From 6047eec609cf38b5c4f951ea11dc6e66b30c8359 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 24 May 2022 10:20:02 +0200 Subject: [PATCH 034/169] When, on Windows, the application tries to automatically switch to MESA OpenGL library and the system opengl32.dll is not unloaded, prompt the user with a dialog asking to rerun using the --sw-renderer option --- src/PrusaSlicer_app_msvc.cpp | 49 +++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/PrusaSlicer_app_msvc.cpp b/src/PrusaSlicer_app_msvc.cpp index 2ccf2f1ff..83f24c307 100644 --- a/src/PrusaSlicer_app_msvc.cpp +++ b/src/PrusaSlicer_app_msvc.cpp @@ -66,16 +66,25 @@ public: return this->success; } - void unload_opengl_dll() + bool unload_opengl_dll() { - if (this->hOpenGL) { - BOOL released = FreeLibrary(this->hOpenGL); - if (released) - printf("System OpenGL library released\n"); + if (this->hOpenGL != nullptr) { + if (::FreeLibrary(this->hOpenGL) != FALSE) { + if (::GetModuleHandle(L"opengl32.dll") == nullptr) { + printf("System OpenGL library successfully released\n"); + this->hOpenGL = nullptr; + return true; + } + else + printf("System OpenGL library released but not removed\n"); + } else printf("System OpenGL library NOT released\n"); - this->hOpenGL = nullptr; + + return false; } + + return true; } bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const @@ -270,20 +279,26 @@ int wmain(int argc, wchar_t **argv) // https://wiki.qt.io/Cross_compiling_Mesa_for_Windows // http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/ if (load_mesa) { - opengl_version_check.unload_opengl_dll(); - wchar_t path_to_mesa[MAX_PATH + 1] = { 0 }; - wcscpy(path_to_mesa, path_to_exe); - wcscat(path_to_mesa, L"mesa\\opengl32.dll"); - printf("Loading MESA OpenGL library: %S\n", path_to_mesa); - HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0); - if (hInstance_OpenGL == nullptr) { - printf("MESA OpenGL library was not loaded\n"); - } else - printf("MESA OpenGL library was loaded sucessfully\n"); + bool res = opengl_version_check.unload_opengl_dll(); + if (!res) { + MessageBox(nullptr, L"PrusaSlicer was unable to automatically switch to MESA OpenGL library\nPlease, try to run the application using the '--sw-renderer' option.\n", + L"PrusaSlicer Warning", MB_OK); + return -1; + } + else { + wchar_t path_to_mesa[MAX_PATH + 1] = { 0 }; + wcscpy(path_to_mesa, path_to_exe); + wcscat(path_to_mesa, L"mesa\\opengl32.dll"); + printf("Loading MESA OpenGL library: %S\n", path_to_mesa); + HINSTANCE hInstance_OpenGL = LoadLibraryExW(path_to_mesa, nullptr, 0); + if (hInstance_OpenGL == nullptr) + printf("MESA OpenGL library was not loaded\n"); + else + printf("MESA OpenGL library was loaded sucessfully\n"); + } } #endif /* SLIC3R_GUI */ - wchar_t path_to_slic3r[MAX_PATH + 1] = { 0 }; wcscpy(path_to_slic3r, path_to_exe); wcscat(path_to_slic3r, L"PrusaSlicer.dll"); From ed482316ee22dd3888a73548696f3af3ea51cda3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 25 May 2022 09:36:52 +0200 Subject: [PATCH 035/169] Revert of 39cefdad89f34741b4c6b17a4e42823a57a76c9c --- src/libslic3r/Technologies.hpp | 2 -- src/slic3r/GUI/ConfigWizard.cpp | 47 +++++++++--------------------- src/slic3r/GUI/GUI_App.cpp | 51 ++++++++++----------------------- src/slic3r/GUI/Preferences.cpp | 25 ++++++---------- 4 files changed, 38 insertions(+), 87 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 934d1b978..a89a7c8b0 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -81,8 +81,6 @@ #define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1) // Enable gizmo grabbers to share common models #define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1) -// Disable association to 3mf and stl files if the application is run on Windows 8 or later -#define ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER (1 && ENABLE_2_5_0_ALPHA1) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index b9dcc1a4f..cbee62013 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1934,10 +1934,7 @@ void ConfigWizard::priv::load_pages() index->add_page(page_update); index->add_page(page_reload_from_disk); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (page_files_association != nullptr) -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - index->add_page(page_files_association); + index->add_page(page_files_association); #endif // _WIN32 index->add_page(page_mode); @@ -2750,32 +2747,20 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (page_files_association != nullptr) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); - app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); -// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); + app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0"); + app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0"); +// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0"); - if (wxGetApp().is_editor()) { - if (page_files_association->associate_3mf()) - wxGetApp().associate_3mf_files(); - if (page_files_association->associate_stl()) - wxGetApp().associate_stl_files(); - } -// else { -// if (page_files_association->associate_gcode()) -// wxGetApp().associate_gcode_files(); -// } -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (wxGetApp().is_editor()) { + if (page_files_association->associate_3mf()) + wxGetApp().associate_3mf_files(); + if (page_files_association->associate_stl()) + wxGetApp().associate_stl_files(); } - else { - app_config->set("associate_3mf", "0"); - app_config->set("associate_stl", "0"); -// app_config->set("associate_gcode", "0"); - } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - +// else { +// if (page_files_association->associate_gcode()) +// wxGetApp().associate_gcode_files(); +// } #endif // _WIN32 page_mode->serialize_mode(app_config); @@ -2935,11 +2920,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent) p->add_page(p->page_update = new PageUpdate(this)); p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - p->add_page(p->page_files_association = new PageFilesAssociation(this)); + p->add_page(p->page_files_association = new PageFilesAssociation(this)); #endif // _WIN32 p->add_page(p->page_mode = new PageMode(this)); p->add_page(p->page_firmware = new PageFirmware(this)); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index af9b63acd..c871befbf 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1203,17 +1203,10 @@ bool GUI_App::on_init_inner() if (is_editor()) { #ifdef __WXMSW__ -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); #endif // __WXMSW__ preset_updater = new PresetUpdater(); @@ -1251,15 +1244,8 @@ bool GUI_App::on_init_inner() } else { #ifdef __WXMSW__ -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); #endif // __WXMSW__ } @@ -2432,23 +2418,16 @@ void GUI_App::open_preferences(const std::string& highlight_option /*= std::stri this->plater_->refresh_print(); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - if (is_editor()) { - if (app_config->get("associate_3mf") == "1") - associate_3mf_files(); - if (app_config->get("associate_stl") == "1") - associate_stl_files(); - } - else { - if (app_config->get("associate_gcode") == "1") - associate_gcode_files(); - } -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + if (is_editor()) { + if (app_config->get("associate_3mf") == "1") + associate_3mf_files(); + if (app_config->get("associate_stl") == "1") + associate_stl_files(); + } + else { + if (app_config->get("associate_gcode") == "1") + associate_gcode_files(); } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER #endif // _WIN32 if (mainframe->preferences_dialog->settings_layout_changed()) { diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 3da843f89..1124c4552 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -230,23 +230,16 @@ void PreferencesDialog::build() app_config->get("export_sources_full_pathnames") == "1"); #ifdef _WIN32 -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // file association is not possible anymore starting with Win 8 - if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) { -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - // Please keep in sync with ConfigWizard - append_bool_option(m_optgroup_general, "associate_3mf", - L("Associate .3mf files to PrusaSlicer"), - L("If enabled, sets PrusaSlicer as default application to open .3mf files."), - app_config->get("associate_3mf") == "1"); + // Please keep in sync with ConfigWizard + append_bool_option(m_optgroup_general, "associate_3mf", + L("Associate .3mf files to PrusaSlicer"), + L("If enabled, sets PrusaSlicer as default application to open .3mf files."), + app_config->get("associate_3mf") == "1"); - append_bool_option(m_optgroup_general, "associate_stl", - L("Associate .stl files to PrusaSlicer"), - L("If enabled, sets PrusaSlicer as default application to open .stl files."), - app_config->get("associate_stl") == "1"); -#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER - } -#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER + append_bool_option(m_optgroup_general, "associate_stl", + L("Associate .stl files to PrusaSlicer"), + L("If enabled, sets PrusaSlicer as default application to open .stl files."), + app_config->get("associate_stl") == "1"); #endif // _WIN32 m_optgroup_general->append_separator(); From fcd3966a5b2f3b6c2d314946db995c300af2b7c0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 25 May 2022 11:01:48 +0200 Subject: [PATCH 036/169] Fixed crash when opening the preference dialog in GCodeViewer --- src/slic3r/GUI/Preferences.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 1124c4552..a4317d8f4 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -79,9 +79,11 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str()); m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1"; - // update colors for color pickers - update_color(m_sys_colour, wxGetApp().get_label_clr_sys()); - update_color(m_mod_colour, wxGetApp().get_label_clr_modified()); + if (wxGetApp().is_editor()) { + // update colors for color pickers + update_color(m_sys_colour, wxGetApp().get_label_clr_sys()); + update_color(m_mod_colour, wxGetApp().get_label_clr_modified()); + } this->ShowModal(); } From 1b09628b0d224bacb94eb5ed3c9ebefbef9aa62b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 25 May 2022 13:14:33 +0200 Subject: [PATCH 037/169] #8332 - File association on Windows: reimplemented to allow setting PrusaSlicer as default application for .3mf and .stl files and GCodeViewer as default application for .gcode files --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GUI_App.cpp | 104 +------ src/slic3r/Utils/WinRegistry.cpp | 490 +++++++++++++++++++++++++++++++ src/slic3r/Utils/WinRegistry.hpp | 23 ++ 4 files changed, 519 insertions(+), 100 deletions(-) create mode 100644 src/slic3r/Utils/WinRegistry.cpp create mode 100644 src/slic3r/Utils/WinRegistry.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index ed994be18..09c2098d9 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -246,6 +246,8 @@ set(SLIC3R_GUI_SOURCES Utils/TCPConsole.hpp Utils/MKS.cpp Utils/MKS.hpp + Utils/WinRegistry.cpp + Utils/WinRegistry.hpp ) if (APPLE) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index c871befbf..b688e11f5 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -59,6 +59,7 @@ #include "../Utils/Process.hpp" #include "../Utils/MacDarkMode.hpp" #include "../Utils/AppUpdater.hpp" +#include "../Utils/WinRegistry.hpp" #include "slic3r/Config/Snapshot.hpp" #include "ConfigSnapshotDialog.hpp" #include "FirmwareDialog.hpp" @@ -3135,119 +3136,22 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* pa #ifdef __WXMSW__ -static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue) -{ - // see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association - wchar_t szValueCurrent[1000]; - DWORD dwType; - DWORD dwSize = sizeof(szValueCurrent); - - int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize); - - bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND; - - if ((iRC != ERROR_SUCCESS) && !bDidntExist) - // an error occurred - return false; - - if (!bDidntExist) { - if (dwType != REG_SZ) - // invalid type - return false; - - if (::wcscmp(szValueCurrent, pszValue) == 0) - // value already set - return false; - } - - DWORD dwDisposition; - HKEY hkey; - iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition); - bool ret = false; - if (iRC == ERROR_SUCCESS) { - iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t)); - if (iRC == ERROR_SUCCESS) - ret = true; - } - - RegCloseKey(hkey); - return ret; -} - void GUI_App::associate_3mf_files() { - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"Prusa.Slicer.1"; - std::wstring prog_desc = L"PrusaSlicer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.3mf"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true); } void GUI_App::associate_stl_files() { - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"Prusa.Slicer.1"; - std::wstring prog_desc = L"PrusaSlicer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.stl"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true); } void GUI_App::associate_gcode_files() { - wchar_t app_path[MAX_PATH]; - ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); - - std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\""; - std::wstring prog_id = L"PrusaSlicer.GCodeViewer.1"; - std::wstring prog_desc = L"PrusaSlicerGCodeViewer"; - std::wstring prog_command = prog_path + L" \"%1\""; - std::wstring reg_base = L"Software\\Classes"; - std::wstring reg_extension = reg_base + L"\\.gcode"; - std::wstring reg_prog_id = reg_base + L"\\" + prog_id; - std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command"; - - bool is_new = false; - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); - is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); - - if (is_new) - // notify Windows only when any of the values gets changed - ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); + associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true); } #endif // __WXMSW__ - void GUI_App::on_version_read(wxCommandEvent& evt) { app_config->set("version_online", into_u8(evt.GetString())); diff --git a/src/slic3r/Utils/WinRegistry.cpp b/src/slic3r/Utils/WinRegistry.cpp new file mode 100644 index 000000000..068a7fff1 --- /dev/null +++ b/src/slic3r/Utils/WinRegistry.cpp @@ -0,0 +1,490 @@ +#include "libslic3r/Technologies.hpp" +#include "WinRegistry.hpp" + +#ifdef _WIN32 +#include +#include +#include +#include + +namespace Slic3r { + +// Helper class which automatically closes the handle when +// going out of scope +class AutoHandle +{ + HANDLE m_handle{ nullptr }; + +public: + explicit AutoHandle(HANDLE handle) : m_handle(handle) {} + ~AutoHandle() { if (m_handle != nullptr) ::CloseHandle(m_handle); } + HANDLE get() { return m_handle; } +}; + +// Helper class which automatically closes the key when +// going out of scope +class AutoRegKey +{ + HKEY m_key{ nullptr }; + +public: + explicit AutoRegKey(HKEY key) : m_key(key) {} + ~AutoRegKey() { if (m_key != nullptr) ::RegCloseKey(m_key); } + HKEY get() { return m_key; } +}; + +// returns true if the given value is set/modified into Windows registry +static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue) +{ + // see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association + wchar_t szValueCurrent[1000]; + DWORD dwType; + DWORD dwSize = sizeof(szValueCurrent); + + LSTATUS iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize); + + const bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND; + + if (iRC != ERROR_SUCCESS && !bDidntExist) + // an error occurred + return false; + + if (!bDidntExist) { + if (dwType != REG_SZ) + // invalid type + return false; + + if (::wcscmp(szValueCurrent, pszValue) == 0) + // value already set + return false; + } + + DWORD dwDisposition; + HKEY hkey; + iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition); + bool ret = false; + if (iRC == ERROR_SUCCESS) { + iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t)); + if (iRC == ERROR_SUCCESS) + ret = true; + } + + RegCloseKey(hkey); + return ret; +} + +static std::wstring get_current_user_string_sid() +{ + HANDLE rawProcessToken; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, + &rawProcessToken)) + return L""; + + AutoHandle processToken(rawProcessToken); + + DWORD userSize = 0; + if (!(!::GetTokenInformation(processToken.get(), TokenUser, nullptr, 0, &userSize) && + GetLastError() == ERROR_INSUFFICIENT_BUFFER)) + return L""; + + std::vector userBytes(userSize); + if (!::GetTokenInformation(processToken.get(), TokenUser, userBytes.data(), userSize, &userSize)) + return L""; + + wchar_t* rawSid = nullptr; + if (!::ConvertSidToStringSidW(reinterpret_cast(userBytes.data())->User.Sid, &rawSid)) + return L""; + + return rawSid; +} + +/* + * Create the string which becomes the input to the UserChoice hash. + * + * @see generate_user_choice_hash() for parameters. + * + * @return The formatted string, empty string on failure. + * + * NOTE: This uses the format as of Windows 10 20H2 (latest as of this writing), + * used at least since 1803. + * There was at least one older version, not currently supported: On Win10 RTM + * (build 10240, aka 1507) the hash function is the same, but the timestamp and + * User Experience string aren't included; instead (for protocols) the string + * ends with the exe path. The changelog of SetUserFTA suggests the algorithm + * changed in 1703, so there may be two versions: before 1703, and 1703 to now. + */ +static std::wstring format_user_choice_string(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp) +{ + aTimestamp.wSecond = 0; + aTimestamp.wMilliseconds = 0; + + FILETIME fileTime = { 0 }; + if (!::SystemTimeToFileTime(&aTimestamp, &fileTime)) + return L""; + + // This string is built into Windows as part of the UserChoice hash algorithm. + // It might vary across Windows SKUs (e.g. Windows 10 vs. Windows Server), or + // across builds of the same SKU, but this is the only currently known + // version. There isn't any known way of deriving it, so we assume this + // constant value. If we are wrong, we will not be able to generate correct + // UserChoice hashes. + const wchar_t* userExperience = + L"User Choice set via Windows User Experience " + L"{D18B6DD5-6124-4341-9318-804003BAFA0B}"; + + const wchar_t* userChoiceFmt = + L"%s%s%s" + L"%08lx" + L"%08lx" + L"%s"; + + int userChoiceLen = _scwprintf(userChoiceFmt, aExt, aUserSid, aProgId, + fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience); + userChoiceLen += 1; // _scwprintf does not include the terminator + + std::wstring userChoice(userChoiceLen, L'\0'); + _snwprintf_s(userChoice.data(), userChoiceLen, _TRUNCATE, userChoiceFmt, aExt, + aUserSid, aProgId, fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience); + + ::CharLowerW(userChoice.data()); + return userChoice; +} + +// @return The MD5 hash of the input, nullptr on failure. +static std::vector cng_md5(const unsigned char* bytes, ULONG bytesLen) { + constexpr ULONG MD5_BYTES = 16; + constexpr ULONG MD5_DWORDS = MD5_BYTES / sizeof(DWORD); + std::vector hash; + + BCRYPT_ALG_HANDLE hAlg = nullptr; + if (NT_SUCCESS(::BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_MD5_ALGORITHM, nullptr, 0))) { + BCRYPT_HASH_HANDLE hHash = nullptr; + // As of Windows 7 the hash handle will manage its own object buffer when + // pbHashObject is nullptr and cbHashObject is 0. + if (NT_SUCCESS(::BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0))) { + // BCryptHashData promises not to modify pbInput. + if (NT_SUCCESS(::BCryptHashData(hHash, const_cast(bytes), bytesLen, 0))) { + hash.resize(MD5_DWORDS); + if (!NT_SUCCESS(::BCryptFinishHash(hHash, reinterpret_cast(hash.data()), + MD5_DWORDS * sizeof(DWORD), 0))) + hash.clear(); + } + ::BCryptDestroyHash(hHash); + } + ::BCryptCloseAlgorithmProvider(hAlg, 0); + } + + return hash; +} + +static inline DWORD word_swap(DWORD v) +{ + return (v >> 16) | (v << 16); +} + +// @return The input bytes encoded as base64, nullptr on failure. +static std::wstring crypto_api_base64_encode(const unsigned char* bytes, DWORD bytesLen) { + DWORD base64Len = 0; + if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &base64Len)) + return L""; + + std::wstring base64(base64Len, L'\0'); + if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, base64.data(), &base64Len)) + return L""; + + return base64; +} + +/* + * Generate the UserChoice Hash. + * + * This implementation is based on the references listed above. + * It is organized to show the logic as clearly as possible, but at some + * point the reasoning is just "this is how it works". + * + * @param inputString A null-terminated string to hash. + * + * @return The base64-encoded hash, or empty string on failure. + */ +static std::wstring hash_string(const wchar_t* inputString) +{ + auto inputBytes = reinterpret_cast(inputString); + int inputByteCount = (::lstrlenW(inputString) + 1) * sizeof(wchar_t); + + constexpr size_t DWORDS_PER_BLOCK = 2; + constexpr size_t BLOCK_SIZE = sizeof(DWORD) * DWORDS_PER_BLOCK; + // Incomplete blocks are ignored. + int blockCount = inputByteCount / BLOCK_SIZE; + + if (blockCount == 0) + return L""; + + // Compute an MD5 hash. md5[0] and md5[1] will be used as constant multipliers + // in the scramble below. + auto md5 = cng_md5(inputBytes, inputByteCount); + if (md5.empty()) + return L""; + + // The following loop effectively computes two checksums, scrambled like a + // hash after every DWORD is added. + + // Constant multipliers for the scramble, one set for each DWORD in a block. + const DWORD C0s[DWORDS_PER_BLOCK][5] = { + {md5[0] | 1, 0xCF98B111uL, 0x87085B9FuL, 0x12CEB96DuL, 0x257E1D83uL}, + {md5[1] | 1, 0xA27416F5uL, 0xD38396FFuL, 0x7C932B89uL, 0xBFA49F69uL} }; + const DWORD C1s[DWORDS_PER_BLOCK][5] = { + {md5[0] | 1, 0xEF0569FBuL, 0x689B6B9FuL, 0x79F8A395uL, 0xC3EFEA97uL}, + {md5[1] | 1, 0xC31713DBuL, 0xDDCD1F0FuL, 0x59C3AF2DuL, 0x35BD1EC9uL} }; + + // The checksums. + DWORD h0 = 0; + DWORD h1 = 0; + // Accumulated total of the checksum after each DWORD. + DWORD h0Acc = 0; + DWORD h1Acc = 0; + + for (int i = 0; i < blockCount; ++i) { + for (int j = 0; j < DWORDS_PER_BLOCK; ++j) { + const DWORD* C0 = C0s[j]; + const DWORD* C1 = C1s[j]; + + DWORD input; + memcpy(&input, &inputBytes[(i * DWORDS_PER_BLOCK + j) * sizeof(DWORD)], sizeof(DWORD)); + + h0 += input; + // Scramble 0 + h0 *= C0[0]; + h0 = word_swap(h0) * C0[1]; + h0 = word_swap(h0) * C0[2]; + h0 = word_swap(h0) * C0[3]; + h0 = word_swap(h0) * C0[4]; + h0Acc += h0; + + h1 += input; + // Scramble 1 + h1 = word_swap(h1) * C1[1] + h1 * C1[0]; + h1 = (h1 >> 16) * C1[2] + h1 * C1[3]; + h1 = word_swap(h1) * C1[4] + h1; + h1Acc += h1; + } + } + + DWORD hash[2] = { h0 ^ h1, h0Acc ^ h1Acc }; + return crypto_api_base64_encode(reinterpret_cast(hash), sizeof(hash)); +} + +static std::wstring generate_user_choice_hash(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp) +{ + const std::wstring userChoice = format_user_choice_string(aExt, aUserSid, aProgId, aTimestamp); + if (userChoice.empty()) + return L""; + + return hash_string(userChoice.c_str()); +} + +static bool add_milliseconds_to_system_time(SYSTEMTIME& aSystemTime, ULONGLONG aIncrementMS) +{ + FILETIME fileTime; + ULARGE_INTEGER fileTimeInt; + if (!::SystemTimeToFileTime(&aSystemTime, &fileTime)) + return false; + + fileTimeInt.LowPart = fileTime.dwLowDateTime; + fileTimeInt.HighPart = fileTime.dwHighDateTime; + + // FILETIME is in units of 100ns. + fileTimeInt.QuadPart += aIncrementMS * 1000 * 10; + + fileTime.dwLowDateTime = fileTimeInt.LowPart; + fileTime.dwHighDateTime = fileTimeInt.HighPart; + SYSTEMTIME tmpSystemTime; + if (!::FileTimeToSystemTime(&fileTime, &tmpSystemTime)) + return false; + + aSystemTime = tmpSystemTime; + return true; +} + +// Compare two SYSTEMTIMEs as FILETIME after clearing everything +// below minutes. +static bool check_equal_minutes(SYSTEMTIME aSystemTime1, SYSTEMTIME aSystemTime2) +{ + aSystemTime1.wSecond = 0; + aSystemTime1.wMilliseconds = 0; + + aSystemTime2.wSecond = 0; + aSystemTime2.wMilliseconds = 0; + + FILETIME fileTime1; + FILETIME fileTime2; + if (!::SystemTimeToFileTime(&aSystemTime1, &fileTime1) || !::SystemTimeToFileTime(&aSystemTime2, &fileTime2)) + return false; + + return (fileTime1.dwLowDateTime == fileTime2.dwLowDateTime) && (fileTime1.dwHighDateTime == fileTime2.dwHighDateTime); +} + +static std::wstring get_association_key_path(const wchar_t* aExt) +{ + const wchar_t* keyPathFmt; + if (aExt[0] == L'.') + keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\%s"; + else + keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\%s"; + + int keyPathLen = _scwprintf(keyPathFmt, aExt); + keyPathLen += 1; // _scwprintf does not include the terminator + + std::wstring keyPath(keyPathLen, '\0'); + _snwprintf_s(keyPath.data(), keyPathLen, _TRUNCATE, keyPathFmt, aExt); + return keyPath; +} + +/* + * Set an association with a UserChoice key + * + * Removes the old key, creates a new one with ProgID and Hash set to + * enable a new asociation. + * + * @param aExt File type or protocol to associate + * @param aProgID ProgID to use for the asociation + * + * @return true if successful, false on error. + */ +static bool set_user_choice(const wchar_t* aExt, const wchar_t* aProgID) { + + const std::wstring aSid = get_current_user_string_sid(); + if (aSid.empty()) + return false; + + SYSTEMTIME hashTimestamp; + ::GetSystemTime(&hashTimestamp); + std::wstring hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp); + if (hash.empty()) + return false; + + // The hash changes at the end of each minute, so check that the hash should + // be the same by the time we're done writing. + const ULONGLONG kWriteTimingThresholdMilliseconds = 100; + // Generating the hash could have taken some time, so start from now. + SYSTEMTIME writeEndTimestamp; + ::GetSystemTime(&writeEndTimestamp); + if (!add_milliseconds_to_system_time(writeEndTimestamp, +kWriteTimingThresholdMilliseconds)) + return false; + + if (!check_equal_minutes(hashTimestamp, writeEndTimestamp)) { + ::Sleep(kWriteTimingThresholdMilliseconds * 2); + + // For consistency, use the current time. + ::GetSystemTime(&hashTimestamp); + hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp); + if (hash.empty()) + return false; + } + + const std::wstring assocKeyPath = get_association_key_path(aExt); + if (assocKeyPath.empty()) + return false; + + LSTATUS ls; + HKEY rawAssocKey; + ls = ::RegOpenKeyExW(HKEY_CURRENT_USER, assocKeyPath.data(), 0, KEY_READ | KEY_WRITE, &rawAssocKey); + if (ls != ERROR_SUCCESS) + return false; + + AutoRegKey assocKey(rawAssocKey); + + HKEY currUserChoiceKey; + ls = ::RegOpenKeyExW(assocKey.get(), L"UserChoice", 0, KEY_READ, &currUserChoiceKey); + if (ls == ERROR_SUCCESS) { + ::RegCloseKey(currUserChoiceKey); + // When Windows creates this key, it is read-only (Deny Set Value), so we need + // to delete it first. + // We don't set any similar special permissions. + ls = ::RegDeleteKeyW(assocKey.get(), L"UserChoice"); + if (ls != ERROR_SUCCESS) + return false; + } + + HKEY rawUserChoiceKey; + ls = ::RegCreateKeyExW(assocKey.get(), L"UserChoice", 0, nullptr, + 0 /* options */, KEY_READ | KEY_WRITE, + 0 /* security attributes */, &rawUserChoiceKey, + nullptr); + if (ls != ERROR_SUCCESS) + return false; + + AutoRegKey userChoiceKey(rawUserChoiceKey); + DWORD progIdByteCount = (::lstrlenW(aProgID) + 1) * sizeof(wchar_t); + ls = ::RegSetValueExW(userChoiceKey.get(), L"ProgID", 0, REG_SZ, reinterpret_cast(aProgID), progIdByteCount); + if (ls != ERROR_SUCCESS) + return false; + + DWORD hashByteCount = (::lstrlenW(hash.data()) + 1) * sizeof(wchar_t); + ls = ::RegSetValueExW(userChoiceKey.get(), L"Hash", 0, REG_SZ, reinterpret_cast(hash.data()), hashByteCount); + if (ls != ERROR_SUCCESS) + return false; + + return true; +} + +static bool set_as_default_per_file_type(const std::wstring& extension, const std::wstring& prog_id) +{ + const std::wstring reg_extension = get_association_key_path(extension.c_str()); + if (reg_extension.empty()) + return false; + + bool needs_update = true; + bool modified = false; + HKEY rawAssocKey = nullptr; + LSTATUS res = ::RegOpenKeyExW(HKEY_CURRENT_USER, reg_extension.c_str(), 0, KEY_READ, &rawAssocKey); + AutoRegKey assoc_key(rawAssocKey); + if (res == ERROR_SUCCESS) { + DWORD data_size_bytes = 0; + res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, nullptr, &data_size_bytes); + if (res == ERROR_SUCCESS) { + // +1 in case dataSizeBytes was odd, +1 to ensure termination + DWORD data_size_chars = (data_size_bytes / sizeof(wchar_t)) + 2; + std::wstring curr_prog_id(data_size_chars, L'\0'); + res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, curr_prog_id.data(), &data_size_bytes); + if (res == ERROR_SUCCESS) { + const std::wstring::size_type pos = curr_prog_id.find_first_of(L'\0'); + if (pos != std::wstring::npos) + curr_prog_id = curr_prog_id.substr(0, pos); + needs_update = !boost::algorithm::iequals(curr_prog_id, prog_id); + } + } + } + + if (needs_update) + modified = set_user_choice(extension.c_str(), prog_id.c_str()); + + return modified; +} + +void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default) +{ + assert(!extension.empty() && extension.front() == L'.'); + + const std::wstring reg_extension = L"SOFTWARE\\Classes\\" + extension; + const std::wstring reg_prog_id = L"SOFTWARE\\Classes\\" + prog_id; + const std::wstring reg_prog_id_command = L"SOFTWARE\\Classes\\" + prog_id + +L"\\Shell\\Open\\Command"; + + wchar_t app_path[1040]; + ::GetModuleFileNameW(nullptr, app_path, sizeof(app_path)); + const std::wstring prog_command = L"\"" + std::wstring(app_path) + L"\"" + L" \"%1\""; + + bool modified = false; + modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str()); + modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str()); + modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str()); + if (set_as_default) + modified |= set_as_default_per_file_type(extension, prog_id); + + // notify Windows only when any of the values gets changed + if (modified) + ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr); +} + +} // namespace Slic3r + +#endif // _WIN32 diff --git a/src/slic3r/Utils/WinRegistry.hpp b/src/slic3r/Utils/WinRegistry.hpp new file mode 100644 index 000000000..bc0875d32 --- /dev/null +++ b/src/slic3r/Utils/WinRegistry.hpp @@ -0,0 +1,23 @@ +#ifndef slic3r_Utils_WinRegistry_hpp_ +#define slic3r_Utils_WinRegistry_hpp_ + +#ifdef _WIN32 + +#include + +namespace Slic3r { + +// Creates a Windows registry key for the files with the given 'extension' and associates them to the application 'prog_id'. +// If 'set_as_default' is true, the application 'prog_id' is set ad default application for the file type 'extension'. +// The file type registration implementation is based on code taken from: +// https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association +// The set as default implementation is based on code taken from: +// https://hg.mozilla.org/mozilla-central/rev/e928b3e95a6c3b7257d0ba475fc2303bfbad1874 +// https://hg.mozilla.org/releases/mozilla-release/diff/7e775ce432b599c6daf7ac379aa42f1e9b3b33ed/browser/components/shell/WindowsUserChoice.cpp +void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default); + +} // namespace Slic3r + +#endif // _WIN32 + +#endif // slic3r_Utils_WinRegistry_hpp_ From 4e3369bbfcdadd8c63f06f6e2d2646356fc5c3f4 Mon Sep 17 00:00:00 2001 From: MarkMan0 Date: Wed, 4 May 2022 13:19:47 +0200 Subject: [PATCH 038/169] Improve Proton X profiles, Add Proton XE-750 printer --- resources/profiles/INAT.idx | 3 +- resources/profiles/INAT.ini | 375 +++++++++++++++--- .../profiles/INAT/PROTON_XE750_thumbnail.png | Bin 0 -> 36819 bytes 3 files changed, 320 insertions(+), 58 deletions(-) create mode 100644 resources/profiles/INAT/PROTON_XE750_thumbnail.png diff --git a/resources/profiles/INAT.idx b/resources/profiles/INAT.idx index a756b34b5..6087bae62 100644 --- a/resources/profiles/INAT.idx +++ b/resources/profiles/INAT.idx @@ -1,4 +1,5 @@ -min_slic3r_version = 2.3.1-beta +min_slic3r_version = 2.4.1 +0.0.4 Improve Proton X profiles, Add Proton XE-750 printer 0.0.3 Set default filament profile. 0.0.2 Improved start gcode, changed filename format 0.0.1 Initial version diff --git a/resources/profiles/INAT.ini b/resources/profiles/INAT.ini index 3c1a753b5..0bba8c976 100644 --- a/resources/profiles/INAT.ini +++ b/resources/profiles/INAT.ini @@ -3,7 +3,7 @@ [vendor] # Vendor name will be shown by the Config Wizard. name = INAT -config_version = 0.0.3 +config_version = 0.0.4 config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/INAT/ ### @@ -24,9 +24,16 @@ technology = FFF family = Proton default_materials = PLA @PROTON_X +[printer_model:PROTON_XE750] +name = INAT Proton XE-750 +variants = 0.4 +technology = FFF +family = Proton +default_materials = PLA @PROTON_XE750 + ### -### QUALITY DEFINITIONS +### COMMON QUALITY DEFINITIONS ### [print:*common*] @@ -35,14 +42,15 @@ layer_height = 0.2 first_layer_height = 0.2 perimeters = 3 spiral_vase = 0 -top_solid_layers = 4 +top_solid_layers = 5 bottom_solid_layers = 3 -top_solid_min_thickness = 0.8 +top_solid_min_thickness = 1 bottom_solid_min_thickness = 0.6 extra_perimeters = 1 ensure_vertical_shell_thickness = 1 avoid_crossing_perimeters = 0 thin_walls = 0 +thick_bridges = 0 overhangs = 1 seam_position = aligned external_perimeters_first = 0 @@ -76,7 +84,9 @@ support_material_auto = 1 support_material_threshold = 0 support_material_enforce_layers = 0 raft_layers = 0 -support_material_contact_distance = 0.2 +support_material_style = grid +support_material_contact_distance = 0.25 +support_material_bottom_contact_distance = 0.3 support_material_pattern = rectilinear support_material_with_sheath = 0 support_material_spacing = 5 @@ -92,8 +102,8 @@ perimeter_speed = 60 small_perimeter_speed = 75% external_perimeter_speed = 50% infill_speed = 80 -solid_infill_speed = 100% -top_solid_infill_speed = 30 +solid_infill_speed = 80% +top_solid_infill_speed = 20 support_material_speed = 80 support_material_interface_speed = 100% bridge_speed = 60 @@ -126,7 +136,7 @@ infill_overlap = 25% bridge_flow_ratio = 1 slice_closing_radius = 0.049 resolution = 0 -xy_size_compensation = 0 +xy_size_compensation = -0.05 elefant_foot_compensation = 0.3 clip_multipart_objects = 0 #output @@ -138,47 +148,50 @@ gcode_label_objects = 0 output_filename_format = {input_filename_base}_{filament_type[0]}.gcode -[print:0.2mm Standard @PROTON_X] +[print:*common 0.2mm Standard @INAT*] inherits = *common* -[print:0.2mm Strong @PROTON_X] +[print:*common 0.2mm Strong @INAT*] inherits = *common* fill_density = 50% perimeters = 6 -[print:0.2mm Advanced Material @PROTON_X] +[print:*common 0.2mm Advanced Material @INAT*] inherits = *common* bottom_solid_layers = 5 top_solid_layers = 6 skirts = 0 -brim_width = 30 +brim_width = 20 infill_speed = 60 support_material_speed = 60 travel_speed = 100 first_layer_speed = 20 elefant_foot_compensation = 0 -[print:0.12mm Fine @PROTON_X] +[print:*common 0.12mm Fine @INAT*] inherits = *common* +layer_height = 0.12 bottom_solid_layers = 7 top_solid_layers = 7 infill_every_layers = 2 perimeter_speed = 50 infill_speed = 50 -[print:0.32mm Draft @PROTON_X] +[print:*common 0.32mm Draft @INAT*] inherits = *common* +layer_height = 0.32 perimeter_speed = 80 external_perimeter_speed = 75% infill_speed = 100 top_solid_infill_speed = 60 fill_density = 15% +support_material_style = snug ### -### PRINTER DEFINITIONS +### COMMON PRINTER DEFINITIONS ### -[printer:*common*] +[printer:*proton_x_common*] printer_vendor = INAT s.r.o. default_filament_profile = "PLA @PROTON_X" #general @@ -206,14 +219,14 @@ machine_max_feedrate_z = 10,10 machine_max_feedrate_e = 100,100 machine_max_acceleration_x = 500,500 machine_max_acceleration_y = 500,500 -machine_max_acceleration_z = 100,100 -machine_max_acceleration_e = 2000,2000 +machine_max_acceleration_z = 200,200 +machine_max_acceleration_e = 8000,8000 machine_max_acceleration_extruding = 1000,1000 -machine_max_acceleration_retracting = 1500,1500 +machine_max_acceleration_retracting = 8000,8000 machine_max_jerk_x = 8,8 machine_max_jerk_y = 8,8 -machine_max_jerk_z = 1,1 -machine_max_jerk_e = 2.5,2.5 +machine_max_jerk_z = 3,3 +machine_max_jerk_e = 10,10 machine_min_extruding_rate = 5 #extruder 1 nozzle_diameter = 0.4 @@ -233,24 +246,65 @@ wipe = 1 retract_before_wipe = 100% -[printer:Proton X Rail] -inherits = *common* -printer_model = PROTON_X_RAIL -printer_variant = 0.4 -default_print_profile = 0.2mm Standard @PROTON_X -gcode_flavor = marlin -machine_max_acceleration_y = 800,800 - -[printer:Proton X Rod] -inherits = *common* -printer_model = PROTON_X_ROD -printer_variant = 0.4 -default_print_profile = 0.2mm Standard @PROTON_X +[printer:*proton_xe750_common*] +printer_vendor = INAT s.r.o. +default_filament_profile = "PLA @PROTON_XE750" +#general +printer_technology = FFF +bed_shape = 0x0,600x0,600x500,0x500 +max_print_height = 750 +z_offset = 0 +extruders_count = 2 gcode_flavor = marlin +silent_mode = 0 +remaining_times = 1 +use_relative_e_distances = 0 +use_firmware_retraction = 0 +use_volumetric_e = 0 +variable_layer_height = 1 +#gcodes +start_gcode = G28 ;Home\nG0 Z10 F1000\nG29\nG0 X0 Y0 Z30 F6000\nM84 E\nM0\nG1 Z15.0 F6000 ;Move the platform down 15mm\n +end_gcode = M400\nM104 S0\nM140 S0\nM107\n;Retract the filament\nG92 E1\nG1 E-1 F300\nG28 X R5\nG0 Y300 F2000\nM84\nG4 S180\nM81 S30\n +color_change_gcode = M600 +#limits +machine_limits_usage = emit_to_gcode +machine_max_feedrate_x = 200,200 +machine_max_feedrate_y = 200,200 +machine_max_feedrate_z = 10,10 +machine_max_feedrate_e = 100,100 +machine_max_acceleration_x = 1500,1500 +machine_max_acceleration_y = 1500,1500 +machine_max_acceleration_z = 500,500 +machine_max_acceleration_e = 20000,20000 +machine_max_acceleration_extruding = 2000,2000 +machine_max_acceleration_retracting = 8000,8000 +machine_max_jerk_x = 12,12 +machine_max_jerk_y = 12,12 +machine_max_jerk_z = 3,3 +machine_max_jerk_e = 20,20 +machine_min_extruding_rate = 5 +#extruder 1 +nozzle_diameter = 0.4,0.4 +min_layer_height = 0.05,0.05 +max_layer_height = 0.33,0.33 +extruder_offset = 0x0,0x0 +retract_length = 1.5,1.5 +retract_lift = 0.6,0.6 +retract_lift_above = 0,0 +retract_lift_below = 0,0 +retract_speed = 45,45 +deretract_speed = 0,0 +retract_restart_extra = 0,0 +retract_before_travel = 2,2 +retract_layer_change = 0,0 +wipe = 1,1 +retract_before_wipe = 100%,100% +retract_length_toolchange = 37,37 +extruder_colour = #33CC33;#3399FF ### -### MATERIAL DEFINITIONS +### COMMON MATERIAL DEFINITIONS ### [filament:*common*] @@ -272,7 +326,7 @@ min_print_speed = 10 filament_soluble = 0 -[filament:PLA @PROTON_X] +[filament:*common PLA @INAT*] inherits = *common* temperature = 210 bed_temperature = 60 @@ -281,11 +335,12 @@ first_layer_bed_temperature = 60 filament_type = PLA filament_cost = 20 filament_density = 1.25 +fan_always_on = 1 min_fan_speed = 50 max_fan_speed = 100 -[filament:PETG @PROTON_X] +[filament:*common PETG @INAT*] inherits = *common* temperature = 240 bed_temperature = 80 @@ -294,10 +349,11 @@ first_layer_bed_temperature = 80 filament_type = PETG filament_cost = 25 filament_density = 1.27 -min_fan_speed = 0 +fan_always_on = 1 +min_fan_speed = 25 max_fan_speed = 50 -[filament:ABS @PROTON_X] +[filament:*common ABS @INAT*] inherits = *common* temperature = 235 bed_temperature = 100 @@ -309,7 +365,7 @@ filament_density = 1.01 cooling = 0 bridge_fan_speed = 0 -[filament:ASA @PROTON_X] +[filament:*common ASA @INAT*] inherits = *common* temperature = 240 bed_temperature = 110 @@ -320,7 +376,7 @@ filament_cost = 22 filament_density = 1.07 cooling = 0 -[filament:TPE @PROTON_X] +[filament:*common TPE @INAT*] inherits = *common* temperature = 220 bed_temperature = 40 @@ -334,7 +390,7 @@ max_fan_speed = 50 filament_retract_length = 0.8 filament_retract_speed = 25 -[filament:HIPS @PROTON_X] +[filament:*common HIPS @INAT*] inherits = *common* temperature = 245 bed_temperature = 100 @@ -347,7 +403,7 @@ min_fan_speed = 0 max_fan_speed = 50 filament_soluble = 1 -[filament:Nylon @PROTON_X] +[filament:*common Nylon @INAT*] inherits = *common* temperature = 235 bed_temperature = 130 @@ -359,19 +415,19 @@ filament_density = 1.01 cooling = 0 bridge_fan_speed = 0 -[filament:PC @PROTON_X] +[filament:*common PC @INAT*] inherits = *common* temperature = 270 -bed_temperature = 130 +bed_temperature = 115 first_layer_temperature = 270 -first_layer_bed_temperature = 130 +first_layer_bed_temperature = 115 filament_type = PC filament_cost = 65 filament_density = 1.19 cooling = 0 bridge_fan_speed = 0 -[filament:CPE @PROTON_X] +[filament:*common CPE @INAT*] inherits = *common* temperature = 280 bed_temperature = 90 @@ -383,7 +439,7 @@ filament_density = 1.27 cooling = 0 bridge_fan_speed = 0 -[filament:PEEK @PROTON_X] +[filament:*common PEEK @INAT*] inherits = *common* temperature = 440 bed_temperature = 150 @@ -395,7 +451,7 @@ filament_density = 1.3 cooling = 0 bridge_fan_speed = 0 -[filament:PEI @PROTON_X] +[filament:*common PEI @INAT*] inherits = *common* temperature = 400 bed_temperature = 150 @@ -407,7 +463,7 @@ filament_density = 1.27 cooling = 0 bridge_fan_speed = 0 -[filament:Polymaker PolyMide CoPA @PROTON_X] +[filament:*common Polymaker PolyMide CoPA @INAT*] inherits = *common* filament_vendor = Polymaker temperature = 265 @@ -419,7 +475,7 @@ filament_cost = 93 filament_density = 1.12 cooling = 0 -[filament:Polymaker PolyMide PA6-CF @PROTON_X] +[filament:*common Polymaker PolyMide PA6-CF @INAT*] inherits = *common* filament_vendor = Polymaker temperature = 300 @@ -431,7 +487,7 @@ filament_cost = 95 filament_density = 1.17 cooling = 0 -[filament:Polymaker PolyMide PA6-GF @PROTON_X] +[filament:*common Polymaker PolyMide PA6-GF @INAT*] inherits = *common* filament_vendor = Polymaker temperature = 300 @@ -443,20 +499,21 @@ filament_cost = 95 filament_density = 1.2 cooling = 0 -[filament:Devil Design PETG @PROTON_X] +[filament:*common Devil Design PETG @INAT*] inherits = *common* filament_vendor = Devil Design -temperature = 250 +temperature = 245 bed_temperature = 80 -first_layer_temperature = 250 +first_layer_temperature = 245 first_layer_bed_temperature = 80 filament_type = PETG filament_cost = 22 filament_density = 1.23 -min_fan_speed = 0 +fan_always_on = 1 +min_fan_speed = 25 max_fan_speed = 50 -[filament:Filament PM PETG FRJet @PROTON_X] +[filament:*common Filament PM PETG FRJet @INAT*] inherits = *common* filament_vendor = Filament PM temperature = 250 @@ -467,3 +524,207 @@ filament_type = PETG filament_cost = 45.5 filament_density = 1.27 cooling = 0 + + +###### +###### PROTON X PRINTERS +###### + +[printer:Proton X Rail] +inherits = *proton_x_common* +printer_model = PROTON_X_RAIL +printer_variant = 0.4 +default_print_profile = 0.2mm Standard @PROTON_X +gcode_flavor = marlin +machine_max_acceleration_x = 800,800 +machine_max_acceleration_y = 800,800 +machine_max_jerk_x = 10,10 +machine_max_jerk_y = 10,10 + +[printer:Proton X Rod] +inherits = *proton_x_common* +printer_model = PROTON_X_ROD +printer_variant = 0.4 +default_print_profile = 0.2mm Standard @PROTON_X +gcode_flavor = marlin + +[print:0.2mm Standard @PROTON_X] +inherits = *common 0.2mm Standard @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[print:0.2mm Strong @PROTON_X] +inherits = *common 0.2mm Strong @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[print:0.2mm Advanced Material @PROTON_X] +inherits = *common 0.2mm Advanced Material @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[print:0.12mm Fine @PROTON_X] +inherits = *common 0.12mm Fine @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[print:0.32mm Draft @PROTON_X] +inherits = *common 0.32mm Draft @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + + + +[filament:PLA @PROTON_X] +inherits =*common PLA @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:PETG @PROTON_X] +inherits =*common PETG @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:ABS @PROTON_X] +inherits =*common ABS @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:ASA @PROTON_X] +inherits =*common ASA @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:TPE @PROTON_X] +inherits =*common TPE @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:HIPS @PROTON_X] +inherits =*common HIPS @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:Nylon @PROTON_X] +inherits =*common Nylon @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:PC @PROTON_X] +inherits =*common PC @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:CPE @PROTON_X] +inherits =*common CPE @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:PEEK @PROTON_X] +inherits =*common PEEK @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:PEI @PROTON_X] +inherits =*common PEI @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:Polymaker PolyMide CoPA @PROTON_X] +inherits =*common Polymaker PolyMide CoPA @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:Polymaker PolyMide PA6-CF @PROTON_X] +inherits =*common Polymaker PolyMide PA6-CF @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:Polymaker PolyMide PA6-GF @PROTON_X] +inherits =*common Polymaker PolyMide PA6-GF @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:Devil Design PETG @PROTON_X] +inherits =*common Devil Design PETG @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + +[filament:Filament PM PETG FRJet @PROTON_X] +inherits =*common Filament PM PETG FRJet @INAT* +compatible_printers = "Proton X Rail";"Proton X Rod" + + + + +######### ######### +######### Proton XE 750 ######### +######### ######### + +[printer:Proton XE-750] +inherits = *proton_xe750_common* +printer_model = PROTON_XE750 +printer_variant = 0.4 +default_print_profile = 0.2mm Standard @PROTON_XE750 +gcode_flavor = marlin + + +[print:0.2mm Standard @PROTON_XE750] +inherits = *common 0.2mm Standard @INAT* +compatible_printers = "Proton XE-750" + +[print:0.2mm Strong @PROTON_XE750] +inherits = *common 0.2mm Strong @INAT* +compatible_printers = "Proton XE-750" + +[print:0.2mm Advanced Material @PROTON_XE750] +inherits = *common 0.2mm Advanced Material @INAT* +compatible_printers = "Proton XE-750" + +[print:0.12mm Fine @PROTON_XE750] +inherits = *common 0.12mm Fine @INAT* +compatible_printers = "Proton XE-750" + +[print:0.32mm Draft @PROTON_XE750] +inherits = *common 0.32mm Draft @INAT* +compatible_printers = "Proton XE-750" + + + + + + +[filament:*start_end_gcode @PROTON_XE750*] +start_filament_gcode = "; Filament start gcode BEGIN\nM104 S[temperature[current_extruder]]\nG4 S20\n; Filament start gcode END\n" +end_filament_gcode = "; Filament end gcode BEGIN\nG0 X-5 Y250 F10000\nM104 S{temperature[current_extruder] - 50}\n; Filament end gcode END\n" +compatible_printers = "Proton XE-750" + + + +[filament:PLA @PROTON_XE750] +inherits =*common PLA @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:PETG @PROTON_XE750] +inherits =*common PETG @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:ABS @PROTON_XE750] +inherits =*common ABS @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:ASA @PROTON_XE750] +inherits =*common ASA @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:TPE @PROTON_XE750] +inherits =*common TPE @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:HIPS @PROTON_XE750] +inherits =*common HIPS @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:Nylon @PROTON_XE750] +inherits =*common Nylon @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:PC @PROTON_XE750] +inherits =*common PC @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:CPE @PROTON_XE750] +inherits =*common CPE @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:PEEK @PROTON_XE750] +inherits =*common PEEK @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:PEI @PROTON_XE750] +inherits =*common PEI @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:Polymaker PolyMide CoPA @PROTON_XE750] +inherits =*common Polymaker PolyMide CoPA @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:Polymaker PolyMide PA6-CF @PROTON_XE750] +inherits =*common Polymaker PolyMide PA6-CF @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:Polymaker PolyMide PA6-GF @PROTON_XE750] +inherits =*common Polymaker PolyMide PA6-GF @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:Devil Design PETG @PROTON_XE750] +inherits =*common Devil Design PETG @INAT*; *start_end_gcode @PROTON_XE750* + +[filament:Filament PM PETG FRJet @PROTON_XE750] +inherits =*common Filament PM PETG FRJet @INAT*; *start_end_gcode @PROTON_XE750* diff --git a/resources/profiles/INAT/PROTON_XE750_thumbnail.png b/resources/profiles/INAT/PROTON_XE750_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..114247ade2a5a7628fc1ffda97ea9a2bb4510cb7 GIT binary patch literal 36819 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 literal 0 HcmV?d00001 From a115702289f399f14bd914dc98ac4795e3cce39c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 27 May 2022 13:22:08 +0200 Subject: [PATCH 039/169] Ignore CLion IDE files in all subdirectories. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c4df3f3f8..704289a22 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,4 @@ local-lib build-linux/* deps/build-linux/* **/.DS_Store -/.idea/ +**/.idea/ From 98928935877b3ec25a62c8fec1fc28642fff6b23 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 May 2022 15:33:03 +0200 Subject: [PATCH 040/169] Add UIThreadWorker for debugging and profiling purposes --- src/slic3r/CMakeLists.txt | 1 + src/slic3r/GUI/Jobs/PlaterWorker.hpp | 6 ++ src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 114 ++++++++++++++++++++++++ src/slic3r/GUI/Plater.cpp | 3 + tests/slic3rutils/slic3r_jobs_tests.cpp | 40 ++++++--- 5 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 src/slic3r/GUI/Jobs/UIThreadWorker.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 09c2098d9..78e73ba9a 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -172,6 +172,7 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/Worker.hpp GUI/Jobs/BoostThreadWorker.hpp GUI/Jobs/BoostThreadWorker.cpp + GUI/Jobs/UIThreadWorker.hpp GUI/Jobs/BusyCursorJob.hpp GUI/Jobs/PlaterWorker.hpp GUI/Jobs/ArrangeJob.hpp diff --git a/src/slic3r/GUI/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp index 573590272..58bd1ec32 100644 --- a/src/slic3r/GUI/Jobs/PlaterWorker.hpp +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -40,6 +40,12 @@ class PlaterWorker: public Worker { { wxWakeUpIdle(); ctl.update_status(st, msg); + + // If the worker is not using additional threads, the UI + // is refreshed with this call. If the worker is running + // in it's own thread, the yield should not have any + // visible effects. + wxYieldIfNeeded(); } bool was_canceled() const override { return ctl.was_canceled(); } diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp new file mode 100644 index 000000000..aa946036e --- /dev/null +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -0,0 +1,114 @@ +#ifndef UITHREADWORKER_HPP +#define UITHREADWORKER_HPP + +#include +#include + +#include "Worker.hpp" +#include "ProgressIndicator.hpp" + +namespace Slic3r { namespace GUI { + +// Implementation of a worker which does not create any additional threads. +class UIThreadWorker : public Worker, private Job::Ctl { + std::queue, std::deque>> m_jobqueue; + std::shared_ptr m_progress; + bool m_running = false; + bool m_canceled = false; + + void process_front() + { + std::unique_ptr job; + + if (!m_jobqueue.empty()) { + job = std::move(m_jobqueue.front()); + m_jobqueue.pop(); + } + + if (job) { + std::exception_ptr eptr; + m_running = true; + + try { + job->process(*this); + } catch (...) { + eptr= std::current_exception(); + } + + m_running = false; + + job->finalize(m_canceled, eptr); + + m_canceled = false; + } + } + +protected: + // Implement Job::Ctl interface: + + void update_status(int st, const std::string &msg = "") override + { + if (m_progress) { + m_progress->set_progress(st); + m_progress->set_status_text(msg.c_str()); + } + } + + bool was_canceled() const override { return m_canceled; } + + std::future call_on_main_thread(std::function fn) override + { + return std::async(std::launch::deferred, [fn]{ fn(); }); + } + +public: + explicit UIThreadWorker(std::shared_ptr pri, + const std::string & /*name*/ = "") + : m_progress{pri} + {} + + UIThreadWorker() = default; + + bool push(std::unique_ptr job) override + { + m_canceled = false; + m_jobqueue.push(std::move(job)); + + return bool(job); + } + + bool is_idle() const override { return !m_running; } + + void cancel() override { m_canceled = true; } + + void cancel_all() override + { + m_canceled = true; + process_front(); + while (!m_jobqueue.empty()) m_jobqueue.pop(); + } + + void process_events() override { + while (!m_jobqueue.empty()) + process_front(); + } + + bool wait_for_current_job(unsigned /*timeout_ms*/ = 0) override { + process_front(); + + return true; + } + + bool wait_for_idle(unsigned /*timeout_ms*/ = 0) override { + process_events(); + + return true; + } + + ProgressIndicator * get_pri() { return m_progress.get(); } + const ProgressIndicator * get_pri() const { return m_progress.get(); } +}; + +}} // namespace Slic3r::GUI + +#endif // UITHREADWORKER_HPP diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0456936e4..30c30d2ec 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1648,6 +1648,9 @@ struct Plater::priv // objects would be frozen for the user. In case of arrange, an animation // could be shown, or with the optimize orientations, partial results // could be displayed. + // + // UIThreadWorker can be used as a replacement for BoostThreadWorker if + // no additional worker threads are desired (useful for debugging or profiling) PlaterWorker m_worker; SLAImportDialog * m_sla_import_dlg; diff --git a/tests/slic3rutils/slic3r_jobs_tests.cpp b/tests/slic3rutils/slic3r_jobs_tests.cpp index d31b07349..d4b912b84 100644 --- a/tests/slic3rutils/slic3r_jobs_tests.cpp +++ b/tests/slic3rutils/slic3r_jobs_tests.cpp @@ -4,10 +4,9 @@ #include #include "slic3r/GUI/Jobs/BoostThreadWorker.hpp" +#include "slic3r/GUI/Jobs/UIThreadWorker.hpp" #include "slic3r/GUI/Jobs/ProgressIndicator.hpp" -//#include - struct Progress: Slic3r::ProgressIndicator { int range = 100; int pr = 0; @@ -19,17 +18,30 @@ struct Progress: Slic3r::ProgressIndicator { int get_range() const override { return range; } }; -TEST_CASE("nullptr job should be ignored", "[Jobs]") { - Slic3r::GUI::BoostThreadWorker worker{std::make_unique()}; +using TestClasses = std::tuple< Slic3r::GUI::UIThreadWorker, Slic3r::GUI::BoostThreadWorker >; + +TEMPLATE_LIST_TEST_CASE("Empty worker should not do anything", "[Jobs]", TestClasses) { + TestType worker{std::make_unique()}; + + REQUIRE(worker.is_idle()); + + worker.wait_for_current_job(); + worker.process_events(); + + REQUIRE(worker.is_idle()); +} + +TEMPLATE_LIST_TEST_CASE("nullptr job should be ignored", "[Jobs]", TestClasses) { + TestType worker{std::make_unique()}; worker.push(nullptr); REQUIRE(worker.is_idle()); } -TEST_CASE("State should not be idle while running a job", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("State should not be idle while running a job", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; - BoostThreadWorker worker{std::make_unique(), "worker_thread"}; + TestType worker{std::make_unique(), "worker_thread"}; queue_job(worker, [&worker](Job::Ctl &ctl) { ctl.call_on_main_thread([&worker] { @@ -42,11 +54,11 @@ TEST_CASE("State should not be idle while running a job", "[Jobs]") { REQUIRE(worker.is_idle()); } -TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; queue_job(worker, [](Job::Ctl &ctl){ for (int s = 0; s <= 100; ++s) { @@ -60,12 +72,12 @@ TEST_CASE("Status messages should be received by the main thread during job exec REQUIRE(pri->statustxt == "Running"); } -TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; queue_job( worker, @@ -88,12 +100,12 @@ TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") { REQUIRE(pri->pr != 100); } -TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; std::array jobres = {false, false, false, false}; @@ -125,12 +137,12 @@ TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") { REQUIRE(jobres[3] == false); } -TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]") { +TEMPLATE_LIST_TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]", TestClasses) { using namespace Slic3r; using namespace Slic3r::GUI; auto pri = std::make_shared(); - BoostThreadWorker worker{pri}; + TestType worker{pri}; queue_job( worker, [](Job::Ctl &) { throw std::runtime_error("test"); }, From 6c284882ba2e85bf397d4a5ab68a8e560621a9d3 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 May 2022 15:54:26 +0200 Subject: [PATCH 041/169] Fix cancellation from UI for UIThreadWorker --- src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp index aa946036e..2477489f7 100644 --- a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -65,7 +65,10 @@ public: explicit UIThreadWorker(std::shared_ptr pri, const std::string & /*name*/ = "") : m_progress{pri} - {} + { + if (m_progress) + m_progress->set_cancel_callback([this](){ cancel(); }); + } UIThreadWorker() = default; From b00c5504639bf8dae84f24e41438a3a964b6e466 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 27 May 2022 15:55:25 +0200 Subject: [PATCH 042/169] Do not show legend and bottom slider when loading an invalid gcode file into GCodeViewer --- src/slic3r/GUI/GCodeViewer.cpp | 12 +++++++----- src/slic3r/GUI/GUI_Preview.cpp | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 2134a795f..1e28d1287 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -991,11 +991,13 @@ void GCodeViewer::render() render_toolpaths(); render_shells(); float legend_height = 0.0f; - render_legend(legend_height); - if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { - m_sequential_view.marker.set_world_position(m_sequential_view.current_position); - m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset); - m_sequential_view.render(legend_height); + if (!m_layers.empty()) { + render_legend(legend_height); + if (m_sequential_view.current.last != m_sequential_view.endpoints.last) { + m_sequential_view.marker.set_world_position(m_sequential_view.current_position); + m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset); + m_sequential_view.render(legend_height); + } } #if ENABLE_GCODE_VIEWER_STATISTICS render_statistics(); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 9f62fa0b8..6fff561ce 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -954,7 +954,7 @@ void Preview::load_print_as_fff(bool keep_z_range) } GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type(); - bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); + bool gcode_preview_data_valid = !m_gcode_result->moves.empty() && !m_canvas->get_gcode_layers_zs().empty(); // Collect colors per extruder. std::vector colors; From 6ab8e3a1388cd5e17d7c0a20b6a148918071d772 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 May 2022 16:09:32 +0200 Subject: [PATCH 043/169] Fix id_idle() for UIThreadWorker --- src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp index 2477489f7..b8ac914fd 100644 --- a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -67,7 +67,7 @@ public: : m_progress{pri} { if (m_progress) - m_progress->set_cancel_callback([this](){ cancel(); }); + m_progress->set_cancel_callback([this]() { cancel(); }); } UIThreadWorker() = default; @@ -75,12 +75,14 @@ public: bool push(std::unique_ptr job) override { m_canceled = false; - m_jobqueue.push(std::move(job)); + + if (job) + m_jobqueue.push(std::move(job)); return bool(job); } - bool is_idle() const override { return !m_running; } + bool is_idle() const override { return !m_running && m_jobqueue.empty(); } void cancel() override { m_canceled = true; } @@ -88,7 +90,9 @@ public: { m_canceled = true; process_front(); - while (!m_jobqueue.empty()) m_jobqueue.pop(); + + while (!m_jobqueue.empty()) + m_jobqueue.pop(); } void process_events() override { From c5e3a565117ef714485b132f990b15a251b01e99 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 30 May 2022 10:01:47 +0200 Subject: [PATCH 044/169] Further fix to is_idle() and rethrow unhandled exception after finalize In UIThreadWorker --- src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp index b8ac914fd..610d205cf 100644 --- a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -35,10 +35,14 @@ class UIThreadWorker : public Worker, private Job::Ctl { eptr= std::current_exception(); } - m_running = false; - job->finalize(m_canceled, eptr); + // Unhandled exceptions are rethrown without mercy. + if (eptr) + std::rethrow_exception(eptr); + + m_running = false; + m_canceled = false; } } From 4326e083eb127663016a6b8a4d65121dccda94db Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 30 May 2022 11:15:23 +0200 Subject: [PATCH 045/169] Fix sla rotation gizmo menu not being remembered --- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index b4b9e0777..640199019 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -758,7 +758,9 @@ GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_fil GLGizmoRotate(parent, GLGizmoRotate::X), GLGizmoRotate(parent, GLGizmoRotate::Y), GLGizmoRotate(parent, GLGizmoRotate::Z) }) -{} +{ + load_rotoptimize_state(); +} bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) { From 78124689c5aa3f885f3905d9bfbc0bbae55c356e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 30 May 2022 11:17:34 +0200 Subject: [PATCH 046/169] Fix excessive uptates to UI in sla rotation optimization --- src/libslic3r/SLA/Rotfinder.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 847e638e6..16cd28130 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -288,7 +288,7 @@ template struct RotfinderBoilerplate { static constexpr unsigned MAX_TRIES = MAX_ITER; - int status = 0; + int status = 0, prev_status = 0; TriangleMesh mesh; unsigned max_tries; const RotOptimizeParams ¶ms; @@ -314,13 +314,20 @@ struct RotfinderBoilerplate { RotfinderBoilerplate(const ModelObject &mo, const RotOptimizeParams &p) : mesh{get_mesh_to_rotate(mo)} - , params{p} , max_tries(p.accuracy() * MAX_TRIES) - { + , params{p} + {} + void statusfn() { + int s = status * 100 / max_tries; + if (s != prev_status) { + params.statuscb()(s); + prev_status = s; + } + + ++status; } - void statusfn() { params.statuscb()(++status * 100.0 / max_tries); } bool stopcond() { return ! params.statuscb()(-1); } }; From e8753ee8cd7bb4ffab9a0681bc904ca90e5ff60f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 12 Jan 2022 11:29:38 +0100 Subject: [PATCH 047/169] Tech ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE - 1st installment - Copies custom bed texture and model files to 'data_dir()\printer' folder, if needed, and updates the printer config accordingly Fixed conflicts after rebase with master --- src/libslic3r/PresetBundle.cpp | 35 ++++++++++++++++++++++++++++++++- src/libslic3r/PresetBundle.hpp | 6 ++++++ src/libslic3r/Technologies.hpp | 3 ++- src/slic3r/GUI/ConfigWizard.cpp | 4 ++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 85bcd69ba..e744d72ca 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1,7 +1,7 @@ #include -#include "PresetBundle.hpp" #include "libslic3r.h" +#include "PresetBundle.hpp" #include "Utils.hpp" #include "Model.hpp" #include "format.hpp" @@ -453,6 +453,11 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset:: presets.get_edited_preset().config.apply_only(presets.get_selected_preset().config, unselected_options); } +#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + if (type == Preset::TYPE_PRINTER) + copy_bed_model_and_texture_if_needed(presets.get_edited_preset().config); +#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini presets.save_current_preset(new_name); // Mark the print & filament enabled if they are compatible with the currently selected preset. @@ -1860,4 +1865,32 @@ void PresetBundle::set_default_suppressed(bool default_suppressed) printers.set_default_suppressed(default_suppressed); } +#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE +void copy_bed_model_and_texture_if_needed(DynamicPrintConfig& config) +{ + const boost::filesystem::path user_dir = boost::filesystem::absolute(boost::filesystem::path(data_dir()) / "printer").make_preferred(); + const boost::filesystem::path res_dir = boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); + + auto do_copy = [&user_dir, &res_dir](ConfigOptionString* cfg, const std::string& type) { + if (cfg == nullptr || cfg->value.empty()) + return; + + const boost::filesystem::path src_dir = boost::filesystem::absolute(boost::filesystem::path(cfg->value)).make_preferred().parent_path(); + if (src_dir != user_dir && src_dir.parent_path() != res_dir) { + const std::string dst_value = (user_dir / boost::filesystem::path(cfg->value).filename()).string(); + std::string error; + if (copy_file_inner(cfg->value, dst_value, error) == SUCCESS) + cfg->value = dst_value; + else { + BOOST_LOG_TRIVIAL(error) << "Copying from " << cfg->value << " to " << dst_value << " failed. Unable to set custom bed " << type << ". [" << error << "]"; + cfg->value = ""; + } + } + }; + + do_copy(config.option("bed_custom_texture"), "texture"); + do_copy(config.option("bed_custom_model"), "model"); +} +#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + } // namespace Slic3r diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 2a5ce6839..b36f6ed6e 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -178,6 +178,12 @@ private: ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute) +#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE +// Copies bed texture and model files to 'data_dir()\printer' folder, if needed +// and updates the config accordingly +extern void copy_bed_model_and_texture_if_needed(DynamicPrintConfig& config); +#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + } // namespace Slic3r #endif /* slic3r_PresetBundle_hpp_ */ diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a89a7c8b0..9f41196a1 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -81,6 +81,7 @@ #define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1) // Enable gizmo grabbers to share common models #define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1) - +// Enable copy of custom bed model and texture +#define ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE (1 && ENABLE_2_5_0_ALPHA1) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index cbee62013..080de997e 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -2780,6 +2780,10 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese page_diams->apply_custom_config(*custom_config); page_temps->apply_custom_config(*custom_config); +#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + copy_bed_model_and_texture_if_needed(*custom_config); +#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE + const std::string profile_name = page_custom->profile_name(); preset_bundle->load_config_from_wizard(profile_name, *custom_config); } From 4dee789e9e8aa55e3d9e9c03619767b500b74d18 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 31 May 2022 08:39:15 +0200 Subject: [PATCH 048/169] Follow-up of b00c5504639bf8dae84f24e41438a3a964b6e466 - More robust fix for: Do not show legend and bottom slider when loading an invalid gcode file into GCodeViewer --- src/slic3r/GUI/GUI_Preview.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 6fff561ce..88bd42a66 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -954,7 +954,7 @@ void Preview::load_print_as_fff(bool keep_z_range) } GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type(); - bool gcode_preview_data_valid = !m_gcode_result->moves.empty() && !m_canvas->get_gcode_layers_zs().empty(); + bool gcode_preview_data_valid = !m_gcode_result->moves.empty(); // Collect colors per extruder. std::vector colors; @@ -983,10 +983,11 @@ void Preview::load_print_as_fff(bool keep_z_range) if (gcode_preview_data_valid) { // Load the real G-code preview. m_canvas->load_gcode_preview(*m_gcode_result, colors); - m_left_sizer->Show(m_bottom_toolbar_panel); m_left_sizer->Layout(); Refresh(); zs = m_canvas->get_gcode_layers_zs(); + if (!zs.empty()) + m_left_sizer->Show(m_bottom_toolbar_panel); m_loaded = true; } else if (wxGetApp().is_editor()) { From fe9ad66e84c9ed96492911b44bedf1295253c88e Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 1 Jun 2022 11:56:42 +0200 Subject: [PATCH 049/169] Disable ENABLE_OBJECT_MANIPULATOR_FOCUS --- src/libslic3r/Technologies.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 9f41196a1..16b2fda0e 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -50,7 +50,7 @@ // Enable showing time estimate for travel moves in legend #define ENABLE_TRAVEL_TIME (1 && ENABLE_2_5_0_ALPHA1) // Enable not killing focus in object manipulator fields when hovering over 3D scene -#define ENABLE_OBJECT_MANIPULATOR_FOCUS (1 && ENABLE_2_5_0_ALPHA1) +#define ENABLE_OBJECT_MANIPULATOR_FOCUS (0 && ENABLE_2_5_0_ALPHA1) // Enable removal of wipe tower magic object_id equal to 1000 #define ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL (1 && ENABLE_2_5_0_ALPHA1) // Enable removal of legacy OpenGL calls From cf16dafad943b6869cc93ffa4b8c7440b6b21a09 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 1 Jun 2022 15:39:07 +0200 Subject: [PATCH 050/169] Fix PlaterWorker not calling yield from main thread Also fix UIThreadWorker not setting busy cursor --- src/slic3r/GUI/Jobs/PlaterWorker.hpp | 12 +++++++----- src/slic3r/GUI/Jobs/UIThreadWorker.hpp | 10 +++++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp index 58bd1ec32..0fef3655e 100644 --- a/src/slic3r/GUI/Jobs/PlaterWorker.hpp +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -38,22 +38,24 @@ class PlaterWorker: public Worker { void update_status(int st, const std::string &msg = "") override { - wxWakeUpIdle(); ctl.update_status(st, msg); // If the worker is not using additional threads, the UI // is refreshed with this call. If the worker is running - // in it's own thread, the yield should not have any - // visible effects. - wxYieldIfNeeded(); + // in it's own thread, this will be one additional + // evaluation of the event loop which should have no visible + // effects. + call_on_main_thread([] { wxYieldIfNeeded(); }); } bool was_canceled() const override { return ctl.was_canceled(); } std::future call_on_main_thread(std::function fn) override { + auto ftr = ctl.call_on_main_thread(std::move(fn)); wxWakeUpIdle(); - return ctl.call_on_main_thread(std::move(fn)); + + return ftr; } } wctl{c}; diff --git a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp index 610d205cf..91213c239 100644 --- a/src/slic3r/GUI/Jobs/UIThreadWorker.hpp +++ b/src/slic3r/GUI/Jobs/UIThreadWorker.hpp @@ -62,7 +62,15 @@ protected: std::future call_on_main_thread(std::function fn) override { - return std::async(std::launch::deferred, [fn]{ fn(); }); + std::future ftr = std::async(std::launch::deferred, [fn]{ fn(); }); + + // So, it seems that the destructor of std::future will not call the + // packaged function. The future needs to be accessed at least ones + // or waited upon. Calling wait() instead of get() will keep the + // returned future's state valid. + ftr.wait(); + + return ftr; } public: From ae377bd28cb7ff0fd267de106dc2a0516a090c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 23 May 2022 12:41:23 +0200 Subject: [PATCH 051/169] Fixed assert in Lightning infill (merging empty BoundingBoxes). --- src/libslic3r/Fill/Lightning/Generator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index e226fbbab..5e2838e46 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -125,7 +125,8 @@ void Generator::generateTrees(const PrintObject &print_object, const std::functi if (const BoundingBox &outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined) below_outlines_bbox.merge(outlines_locator_bbox); - below_outlines_bbox.merge(get_extents(current_lightning_layer.tree_roots).inflated(SCALED_EPSILON)); + if (!current_lightning_layer.tree_roots.empty()) + below_outlines_bbox.merge(get_extents(current_lightning_layer.tree_roots).inflated(SCALED_EPSILON)); outlines_locator.set_bbox(below_outlines_bbox); outlines_locator.create(below_outlines, locator_cell_size); From e967d10788191f57058122f80eac64d6d234ebbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 24 May 2022 14:07:37 +0200 Subject: [PATCH 052/169] Added anchors for the Lightning infill to better connect the infill and perimeters. --- src/libslic3r/Fill/FillLightning.cpp | 23 +++++++++++-------- src/libslic3r/Fill/FillLightning.hpp | 9 ++++++-- .../Fill/Lightning/DistanceField.hpp | 2 +- src/libslic3r/Fill/Lightning/Generator.cpp | 9 ++------ src/libslic3r/Fill/Lightning/Layer.cpp | 5 ++-- src/libslic3r/Fill/Lightning/Layer.hpp | 2 +- src/libslic3r/Fill/Lightning/TreeNode.cpp | 10 ++++---- src/libslic3r/Fill/Lightning/TreeNode.hpp | 10 ++++---- 8 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/Fill/FillLightning.cpp b/src/libslic3r/Fill/FillLightning.cpp index 2ba6fe017..dd2189e6b 100644 --- a/src/libslic3r/Fill/FillLightning.cpp +++ b/src/libslic3r/Fill/FillLightning.cpp @@ -1,20 +1,25 @@ #include "../Print.hpp" +#include "../ShortestPath.hpp" #include "FillLightning.hpp" #include "Lightning/Generator.hpp" -#include "../Surface.hpp" - -#include -#include -#include -#include namespace Slic3r::FillLightning { -Polylines Filler::fill_surface(const Surface *surface, const FillParams ¶ms) +void Filler::_fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon expolygon, + Polylines &polylines_out) { - const Layer &layer = generator->getTreesForLayer(this->layer_id); - return layer.convertToLines(to_polygons(surface->expolygon), generator->infilll_extrusion_width()); + const Layer &layer = generator->getTreesForLayer(this->layer_id); + Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled(0.5 * this->spacing - this->overlap)); + + if (params.dont_connect() || fill_lines.size() <= 1) { + append(polylines_out, chain_polylines(std::move(fill_lines))); + } else + connect_infill(std::move(fill_lines), expolygon, polylines_out, this->spacing, params); } void GeneratorDeleter::operator()(Generator *p) { diff --git a/src/libslic3r/Fill/FillLightning.hpp b/src/libslic3r/Fill/FillLightning.hpp index 941392103..6e672783a 100644 --- a/src/libslic3r/Fill/FillLightning.hpp +++ b/src/libslic3r/Fill/FillLightning.hpp @@ -24,8 +24,13 @@ public: Generator *generator { nullptr }; protected: Fill* clone() const override { return new Filler(*this); } - // Perform the fill. - Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + + void _fill_surface_single(const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon expolygon, + Polylines &polylines_out) override; + // Let the G-code export reoder the infill lines. bool no_sort() const override { return false; } }; diff --git a/src/libslic3r/Fill/Lightning/DistanceField.hpp b/src/libslic3r/Fill/Lightning/DistanceField.hpp index d4a142c05..1a47ee6ca 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.hpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.hpp @@ -176,7 +176,7 @@ protected: const Point offset_loc = loc - m_grid_range.min; const size_t flat_idx = m_grid_size.x() * offset_loc.y() + offset_loc.x(); assert(offset_loc.x() >= 0 && offset_loc.y() >= 0); - assert(flat_idx < m_grid_size.y() * m_grid_size.x()); + assert(flat_idx < size_t(m_grid_size.y() * m_grid_size.x())); return flat_idx; } }; diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index 5e2838e46..4aba7202d 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -7,7 +7,6 @@ #include "../../ClipperUtils.hpp" #include "../../Layer.hpp" #include "../../Print.hpp" -#include "../../Surface.hpp" /* Possible future tasks/optimizations,etc.: * - Improve connecting heuristic to favor connecting to shorter trees @@ -54,8 +53,6 @@ Generator::Generator(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { m_overhang_per_layer.resize(print_object.layers().size()); - // FIXME: It can be adjusted to improve bonding between infill and perimeters. - const float infill_wall_offset = 0;// m_infill_extrusion_width; Polygons infill_area_above; //Iterate from top to bottom, to subtract the overhang areas above from the overhang areas on the layer below, to get only overhang in the top layer where it is overhanging. @@ -65,7 +62,7 @@ void Generator::generateInitialInternalOverhangs(const PrintObject &print_object for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions()) for (const Surface& surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) - append(infill_area_here, infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset)); + infill_area_here.emplace_back(surface.expolygon); //Remove the part of the infill area that is already supported by the walls. Polygons overhang = diff(offset(infill_area_here, -float(m_wall_supporting_radius)), infill_area_above); @@ -84,8 +81,6 @@ const Layer& Generator::getTreesForLayer(const size_t& layer_id) const void Generator::generateTrees(const PrintObject &print_object, const std::function &throw_on_cancel_callback) { m_lightning_layers.resize(print_object.layers().size()); - // FIXME: It can be adjusted to improve bonding between infill and perimeters. - const coord_t infill_wall_offset = 0;// m_infill_extrusion_width; std::vector infill_outlines(print_object.layers().size(), Polygons()); @@ -95,7 +90,7 @@ void Generator::generateTrees(const PrintObject &print_object, const std::functi for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions()) for (const Surface &surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) - append(infill_outlines[layer_id], infill_wall_offset == 0 ? surface.expolygon : offset(surface.expolygon, infill_wall_offset)); + infill_outlines[layer_id].emplace_back(surface.expolygon); } // For various operations its beneficial to quickly locate nearby features on the polygon: diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index 0bd2a65c4..354623e51 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -433,15 +433,14 @@ static unsigned int moveInside(const Polygons& polygons, Point& from, int distan } #endif -// Returns 'added someting'. -Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const +Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_overlap) const { if (tree_roots.empty()) return {}; Polylines result_lines; for (const auto &tree : tree_roots) - tree->convertToPolylines(result_lines, line_width); + tree->convertToPolylines(result_lines, line_overlap); return intersection_pl(result_lines, limit_to_outline); } diff --git a/src/libslic3r/Fill/Lightning/Layer.hpp b/src/libslic3r/Fill/Lightning/Layer.hpp index 87431fb1c..e8c0a38b4 100644 --- a/src/libslic3r/Fill/Lightning/Layer.hpp +++ b/src/libslic3r/Fill/Lightning/Layer.hpp @@ -80,7 +80,7 @@ public: coord_t wall_supporting_radius ); - Polylines convertToLines(const Polygons& limit_to_outline, coord_t line_width) const; + Polylines convertToLines(const Polygons& limit_to_outline, coord_t line_overlap) const; coord_t getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location); diff --git a/src/libslic3r/Fill/Lightning/TreeNode.cpp b/src/libslic3r/Fill/Lightning/TreeNode.cpp index 9ef509611..982d47b10 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.cpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.cpp @@ -347,12 +347,12 @@ coord_t Node::prune(const coord_t& pruning_distance) return max_distance_pruned; } -void Node::convertToPolylines(Polylines &output, const coord_t line_width) const +void Node::convertToPolylines(Polylines &output, const coord_t line_overlap) const { Polylines result; result.emplace_back(); convertToPolylines(0, result); - removeJunctionOverlap(result, line_width); + removeJunctionOverlap(result, line_overlap); append(output, std::move(result)); } @@ -376,10 +376,10 @@ void Node::convertToPolylines(size_t long_line_idx, Polylines &output) const } } -void Node::removeJunctionOverlap(Polylines &result_lines, const coord_t line_width) const +void Node::removeJunctionOverlap(Polylines &result_lines, const coord_t line_overlap) const { - const coord_t reduction = line_width / 2; // TODO make configurable? - size_t res_line_idx = 0; + const coord_t reduction = line_overlap; + size_t res_line_idx = 0; while (res_line_idx < result_lines.size()) { Polyline &polyline = result_lines[res_line_idx]; if (polyline.size() <= 1) { diff --git a/src/libslic3r/Fill/Lightning/TreeNode.hpp b/src/libslic3r/Fill/Lightning/TreeNode.hpp index 81c63f7f6..8791b4331 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.hpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.hpp @@ -46,7 +46,7 @@ public: { struct EnableMakeShared : public Node { - EnableMakeShared(Arg&&...arg) : Node(std::forward(arg)...) {} + explicit EnableMakeShared(Arg&&...arg) : Node(std::forward(arg)...) {} }; return std::make_shared(std::forward(arg)...); } @@ -179,16 +179,16 @@ public: */ bool hasOffspring(const NodeSPtr& to_be_checked) const; -protected: Node() = delete; // Don't allow empty contruction +protected: /*! * Construct a new node, either for insertion in a tree or as root. * \param p The physical location in the 2D layer that this node represents. * Connecting other nodes to this node indicates that a line segment should * be drawn between those two physical positions. */ - Node(const Point& p, const std::optional& last_grounding_location = std::nullopt); + explicit Node(const Point& p, const std::optional& last_grounding_location = std::nullopt); /*! * Copy this node and its entire sub-tree. @@ -239,7 +239,7 @@ public: * * \param output all branches in this tree connected into polylines */ - void convertToPolylines(Polylines &output, coord_t line_width) const; + void convertToPolylines(Polylines &output, coord_t line_overlap) const; /*! If this was ever a direct child of the root, it'll have a previous grounding location. * @@ -260,7 +260,7 @@ protected: */ void convertToPolylines(size_t long_line_idx, Polylines &output) const; - void removeJunctionOverlap(Polylines &polylines, coord_t line_width) const; + void removeJunctionOverlap(Polylines &polylines, coord_t line_overlap) const; bool m_is_root; Point m_p; From a47446574eb3f831907248dc841eb6f7684bbdb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 30 May 2022 15:13:42 +0200 Subject: [PATCH 053/169] Sets locales before any thread start participating in the GCode processing pipeline. Locales should be set once per any participating threads in tbb::parallel_pipeline. It should fix the issue with appearing comma instead of the decimal point in generated Gcode. --- src/libslic3r/GCode.cpp | 36 ++++++++++++++++++++++++++++++++++ src/libslic3r/LocalesUtils.hpp | 19 ++++++++++++++++++ src/libslic3r/Thread.cpp | 18 +++-------------- 3 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index aef83f21f..1f7eaba24 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -35,6 +35,8 @@ #include "SVG.hpp" #include +#include +#include // Intel redesigned some TBB interface considerably when merging TBB with their oneAPI set of libraries, see GH #7332. // We are using quite an old TBB 2017 U7. Before we update our build servers, let's use the old API, which is deprecated in up to date TBB. @@ -1488,6 +1490,32 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato print.throw_if_canceled(); } +// For unknown reasons and in sporadic cases when GCode export is processing, some participating thread +// in tbb::parallel_pipeline has not set locales to "C", probably because this thread is newly spawned. +// So in this class method on_scheduler_entry is called for every thread before it starts participating +// in tbb::parallel_pipeline to ensure that locales are set correctly + +// For tbb::parallel_pipeline, it seems that on_scheduler_entry is called for every layer and every filter. +// We ensure using thread-local storage that locales will be set to "C" just once for any participating thread. +class TBBLocalesSetter : public tbb::task_scheduler_observer +{ +public: + TBBLocalesSetter() { this->observe(true); } + ~TBBLocalesSetter() override = default; + + void on_scheduler_entry(bool is_worker) override + { + if (bool &is_locales_sets = m_is_locales_sets.local(); !is_locales_sets) { + // Set locales of the worker thread to "C". + set_c_locales(); + is_locales_sets = true; + } + } + +private: + tbb::enumerable_thread_specific, tbb::ets_key_usage_type::ets_key_per_instance> m_is_locales_sets; +}; + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. @@ -1531,6 +1559,10 @@ void GCode::process_layers( [&output_stream](std::string s) { output_stream.write(s); } ); + // It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline. + // Handler is unregistered when the destructor is called. + TBBLocalesSetter locales_setter; + // The pipeline elements are joined using const references, thus no copying is performed. output_stream.find_replace_supress(); if (m_spiral_vase && m_find_replace) @@ -1584,6 +1616,10 @@ void GCode::process_layers( [&output_stream](std::string s) { output_stream.write(s); } ); + // It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline. + // Handler is unregistered when the destructor is called. + TBBLocalesSetter locales_setter; + // The pipeline elements are joined using const references, thus no copying is performed. output_stream.find_replace_supress(); if (m_spiral_vase && m_find_replace) diff --git a/src/libslic3r/LocalesUtils.hpp b/src/libslic3r/LocalesUtils.hpp index f63c3572f..ce8030ceb 100644 --- a/src/libslic3r/LocalesUtils.hpp +++ b/src/libslic3r/LocalesUtils.hpp @@ -43,6 +43,25 @@ std::string float_to_string_decimal_point(double value, int precision = -1); //std::string float_to_string_decimal_point(float value, int precision = -1); double string_to_double_decimal_point(const std::string_view str, size_t* pos = nullptr); +// Set locales to "C". +inline void set_c_locales() +{ +#ifdef _WIN32 + _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); + std::setlocale(LC_ALL, "C"); +#else + // We are leaking some memory here, because the newlocale() produced memory will never be released. + // This is not a problem though, as there will be a maximum one worker thread created per physical thread. + uselocale(newlocale( +#ifdef __APPLE__ + LC_ALL_MASK +#else // some Unix / Linux / BSD + LC_ALL +#endif + , "C", nullptr)); +#endif +} + } // namespace Slic3r #endif // slic3r_LocalesUtils_hpp_ diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 4e7bd073a..c099f8de6 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -15,6 +15,7 @@ #include "Thread.hpp" #include "Utils.hpp" +#include "LocalesUtils.hpp" namespace Slic3r { @@ -234,21 +235,8 @@ void name_tbb_thread_pool_threads_set_locale() std::ostringstream name; name << "slic3r_tbb_" << range.begin(); set_current_thread_name(name.str().c_str()); - // Set locales of the worker thread to "C". -#ifdef _WIN32 - _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); - std::setlocale(LC_ALL, "C"); -#else - // We are leaking some memory here, because the newlocale() produced memory will never be released. - // This is not a problem though, as there will be a maximum one worker thread created per physical thread. - uselocale(newlocale( -#ifdef __APPLE__ - LC_ALL_MASK -#else // some Unix / Linux / BSD - LC_ALL -#endif - , "C", nullptr)); -#endif + // Set locales of the worker thread to "C". + set_c_locales(); } }); } From 11f6c67e7c3f6c346b8720afe5087fd5ded1914c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 9 May 2022 11:42:14 +0200 Subject: [PATCH 054/169] Added detection for corrupted PrusaSlicer.ini and fixed showing instructions on how to recover from it (#8217). Previously when PrusaSlicer.ini was just partly corrupted, it could happen that PrusaSlicer.ini wasn't detected as corrupted, and it could cause that instruction on how to recover from this state wasn't shown, and PrusaSlicer crashed because wrong data from PrusaSlicer.ini was read. --- src/libslic3r/AppConfig.cpp | 49 +++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 928febffd..82c0a72a6 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -230,8 +230,13 @@ static std::string appconfig_md5_hash_line(const std::string_view data) return "# MD5 checksum " + md5_digest_str + "\n"; }; +struct ConfigFileInfo { + bool correct_checksum {false}; + bool contains_null {false}; +}; + // Assume that the last line with the comment inside the config file contains a checksum and that the user didn't modify the config file. -static bool verify_config_file_checksum(boost::nowide::ifstream &ifs) +static ConfigFileInfo check_config_file_and_verify_checksum(boost::nowide::ifstream &ifs) { auto read_whole_config_file = [&ifs]() -> std::string { std::stringstream ss; @@ -240,7 +245,8 @@ static bool verify_config_file_checksum(boost::nowide::ifstream &ifs) }; ifs.seekg(0, boost::nowide::ifstream::beg); - std::string whole_config = read_whole_config_file(); + const std::string whole_config = read_whole_config_file(); + const bool contains_null = whole_config.find_first_of('\0') != std::string::npos; // The checksum should be on the last line in the config file. if (size_t last_comment_pos = whole_config.find_last_of('#'); last_comment_pos != std::string::npos) { @@ -249,9 +255,9 @@ static bool verify_config_file_checksum(boost::nowide::ifstream &ifs) // When the checksum isn't found, the checksum was not saved correctly, it was removed or it is an older config file without the checksum. // If the checksum is incorrect, then the file was either not saved correctly or modified. if (std::string_view(whole_config.c_str() + last_comment_pos, whole_config.size() - last_comment_pos) == appconfig_md5_hash_line({ whole_config.data(), last_comment_pos })) - return true; + return {true, contains_null}; } - return false; + return {false, contains_null}; } #endif @@ -269,14 +275,25 @@ std::string AppConfig::load(const std::string &path) ifs.open(path); #ifdef WIN32 // Verify the checksum of the config file without taking just for debugging purpose. - if (!verify_config_file_checksum(ifs)) - BOOST_LOG_TRIVIAL(info) << "The configuration file " << path << - " has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit."; + const ConfigFileInfo config_file_info = check_config_file_and_verify_checksum(ifs); + if (!config_file_info.correct_checksum) + BOOST_LOG_TRIVIAL(info) + << "The configuration file " << path + << " has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit."; + + if (!config_file_info.correct_checksum && config_file_info.contains_null) { + BOOST_LOG_TRIVIAL(info) << "The configuration file " + path + " is corrupted, because it is contains null characters."; + throw Slic3r::CriticalException("The configuration file contains null characters."); + } ifs.seekg(0, boost::nowide::ifstream::beg); #endif - pt::read_ini(ifs, tree); - } catch (pt::ptree_error& ex) { + try { + pt::read_ini(ifs, tree); + } catch (pt::ptree_error &ex) { + throw Slic3r::CriticalException(ex.what()); + } + } catch (Slic3r::CriticalException &ex) { #ifdef WIN32 // The configuration file is corrupted, try replacing it with the backup configuration. ifs.close(); @@ -284,29 +301,29 @@ std::string AppConfig::load(const std::string &path) if (boost::filesystem::exists(backup_path)) { // Compute checksum of the configuration backup file and try to load configuration from it when the checksum is correct. boost::nowide::ifstream backup_ifs(backup_path); - if (!verify_config_file_checksum(backup_ifs)) { - BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", path, backup_path); + if (const ConfigFileInfo config_file_info = check_config_file_and_verify_checksum(backup_ifs); !config_file_info.correct_checksum || config_file_info.contains_null) { + BOOST_LOG_TRIVIAL(error) << format(R"(Both "%1%" and "%2%" are corrupted. It isn't possible to restore configuration from the backup.)", path, backup_path); backup_ifs.close(); boost::filesystem::remove(backup_path); } else if (std::string error_message; copy_file(backup_path, path, error_message, false) != SUCCESS) { - BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", path, backup_path, error_message); + BOOST_LOG_TRIVIAL(error) << format(R"(Configuration file "%1%" is corrupted. Failed to restore from backup "%2%": %3%)", path, backup_path, error_message); backup_ifs.close(); boost::filesystem::remove(backup_path); } else { - BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", path, backup_path); + BOOST_LOG_TRIVIAL(info) << format(R"(Configuration file "%1%" was corrupted. It has been successfully restored from the backup "%2%".)", path, backup_path); // Try parse configuration file after restore from backup. try { ifs.open(path); pt::read_ini(ifs, tree); recovered = true; } catch (pt::ptree_error& ex) { - BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", path, ex.what()); + BOOST_LOG_TRIVIAL(info) << format(R"(Failed to parse configuration file "%1%" after it has been restored from backup: %2%)", path, ex.what()); } } } else #endif // WIN32 - BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", path, ex.what()); - if (! recovered) { + BOOST_LOG_TRIVIAL(info) << format(R"(Failed to parse configuration file "%1%": %2%)", path, ex.what()); + if (!recovered) { // Report the initial error of parsing PrusaSlicer.ini. // Error while parsing config file. We'll customize the error message and rethrow to be displayed. // ! But to avoid the use of _utf8 (related to use of wxWidgets) From ebe411aefb9fbc2d72cf23d5e191bcfc2d4fd452 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 8 Oct 2021 14:32:02 +0200 Subject: [PATCH 055/169] Tech ENABLE_WORLD_COORDINATE - 1st installment 1) Added combo to select world/local coordinate to part manipulator in sidebar 2) Gizmo move oriented in dependence of the selected coordinate system 3) Sidebar hints for position oriented in dependence of the selected coordinate system Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 61 +++++++++++++++-- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 4 ++ src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 83 ++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 26 ++++--- src/slic3r/GUI/Selection.cpp | 18 +++++ 6 files changed, 170 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 16b2fda0e..302c62879 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -69,6 +69,8 @@ #define ENABLE_SHOW_TOOLPATHS_COG (1 && ENABLE_2_5_0_ALPHA1) // Enable recalculating toolpaths when switching to/from volumetric rate visualization #define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1) +// Enable editing volumes transformation in world coordinates and instances in local coordinates +#define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 6ab87150b..1cc35be7f 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -444,8 +444,13 @@ void ObjectManipulation::Show(const bool show) if (show) { // Show the "World Coordinates" / "Local Coordintes" Combo in Advanced / Expert mode only. - bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple; - m_word_local_combo->Show(show_world_local_combo); +#if ENABLE_WORLD_COORDINATE + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()); +#else + bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple; +#endif // ENABLE_WORLD_COORDINATE + m_word_local_combo->Show(show_world_local_combo); m_empty_str->Show(!show_world_local_combo); } } @@ -529,7 +534,9 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (selection.is_single_full_instance()) { // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#if !ENABLE_WORLD_COORDINATE m_new_position = volume->get_instance_offset(); +#endif // !ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. if (m_world_coordinates && ! m_uniform_scale && @@ -540,14 +547,20 @@ void ObjectManipulation::update_settings_value(const Selection& selection) } if (m_world_coordinates) { - m_new_rotate_label_string = L("Rotate"); +#if ENABLE_WORLD_COORDINATE + m_new_position = volume->get_instance_offset(); +#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.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.; + m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; } else { - m_new_rotation = volume->get_instance_rotation() * (180. / M_PI); - m_new_size = volume->get_instance_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size()); +#if ENABLE_WORLD_COORDINATE + m_new_position = Vec3d::Zero(); +#endif // ENABLE_WORLD_COORDINATE + 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()); m_new_scale = volume->get_instance_scaling_factor() * 100.; } @@ -566,10 +579,29 @@ void ObjectManipulation::update_settings_value(const Selection& selection) else if (selection.is_single_modifier() || selection.is_single_volume()) { // the selection contains a single volume const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_WORLD_COORDINATE + if (m_world_coordinates) { + const Geometry::Transformation trafo(volume->world_matrix()); + + const Vec3d& offset = trafo.get_offset(); + const Vec3d& rotation = trafo.get_rotation(); + const Vec3d& scaling_factor = trafo.get_scaling_factor(); + const Vec3d& m = trafo.get_mirror(); + + m_new_position = offset; + m_new_rotation = rotation * (180.0 / M_PI); + m_new_scale = scaling_factor * 100.0; + m_new_size = volume->bounding_box().size().cwiseProduct(scaling_factor); + } + else { +#endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); m_new_scale = volume->get_volume_scaling_factor() * 100.; - m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size())); + m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size())); +#if ENABLE_WORLD_COORDINATE + } +#endif // ENABLE_WORLD_COORDINATE m_new_enabled = true; } else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { @@ -815,7 +847,11 @@ void ObjectManipulation::change_position_value(int axis, double value) auto canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); selection.setup_cache(); +#if ENABLE_WORLD_COORDINATE + selection.translate(position - m_cache.position, !m_world_coordinates); +#else selection.translate(position - m_cache.position, selection.requires_local_axes()); +#endif // ENABLE_WORLD_COORDINATE canvas->do_move(L("Set Position")); m_cache.position = position; @@ -997,6 +1033,17 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) m_uniform_scale = new_value; } +#if ENABLE_WORLD_COORDINATE +void ObjectManipulation::set_world_coordinates(const bool world_coordinates) +{ + m_world_coordinates = world_coordinates; + this->UpdateAndShow(true); + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + canvas->set_as_dirty(); + canvas->request_extra_frame(); +} +#endif // ENABLE_WORLD_COORDINATE + void ObjectManipulation::msw_rescale() { const int em = wxGetApp().em_unit(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index a15c72fb8..6d0383038 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -183,7 +183,11 @@ public: void set_uniform_scaling(const bool uniform_scale); bool get_uniform_scaling() const { return m_uniform_scale; } // Does the object manipulation panel work in World or Local coordinates? +#if ENABLE_WORLD_COORDINATE + void set_world_coordinates(const bool world_coordinates); +#else void set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); } +#endif // ENABLE_WORLD_COORDINATE bool get_world_coordinates() const { return m_world_coordinates; } void reset_cache() { m_cache.reset(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 9dfb8bc9e..22d777d2c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -2,6 +2,9 @@ #include "GLGizmoMove.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" +#if ENABLE_WORLD_COORDINATE +#include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_GL_SHADERS_ATTRIBUTES #include "slic3r/GUI/Plater.hpp" #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -22,15 +25,15 @@ GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filenam std::string GLGizmoMove3D::get_tooltip() const { const Selection& selection = m_parent.get_selection(); - bool show_position = selection.is_single_full_instance(); + const bool show_position = selection.is_single_full_instance(); const Vec3d& position = selection.get_bounding_box().center(); if (m_hover_id == 0 || m_grabbers[0].dragging) - return "X: " + format(show_position ? position(0) : m_displacement(0), 2); + return "X: " + format(show_position ? position.x() : m_displacement.x(), 2); else if (m_hover_id == 1 || m_grabbers[1].dragging) - return "Y: " + format(show_position ? position(1) : m_displacement(1), 2); + return "Y: " + format(show_position ? position.y() : m_displacement.y(), 2); else if (m_hover_id == 2 || m_grabbers[2].dragging) - return "Z: " + format(show_position ? position(2) : m_displacement(2), 2); + return "Z: " + format(show_position ? position.z() : m_displacement.z(), 2); else return ""; } @@ -80,10 +83,18 @@ void GLGizmoMove3D::on_start_dragging() m_displacement = Vec3d::Zero(); const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); +#if ENABLE_WORLD_COORDINATE + const Vec3d center = box.center(); + m_starting_drag_position = center + m_grabbers[m_hover_id].center; + m_starting_box_center = center; + m_starting_box_bottom_center = center; + m_starting_box_bottom_center.z() = box.min.z(); +#else m_starting_drag_position = m_grabbers[m_hover_id].center; m_starting_box_center = box.center(); m_starting_box_bottom_center = box.center(); - m_starting_box_bottom_center(2) = box.min(2); + m_starting_box_bottom_center.z() = box.min.z(); +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoMove3D::on_stop_dragging() @@ -119,7 +130,25 @@ void GLGizmoMove3D::on_render() const BoundingBoxf3& box = selection.get_bounding_box(); const Vec3d& center = box.center(); +#if ENABLE_WORLD_COORDINATE + glsafe(::glPushMatrix()); + transform_to_local(selection); + const Vec3d zero = Vec3d::Zero(); + const Vec3d half_box_size = 0.5 * box.size(); + + // x axis + m_grabbers[0].center = { half_box_size.x() + Offset, 0.0, 0.0 }; + m_grabbers[0].color = AXES_COLOR[0]; + + // y axis + m_grabbers[1].center = { 0.0, half_box_size.y() + Offset, 0.0 }; + m_grabbers[1].color = AXES_COLOR[1]; + + // z axis + m_grabbers[2].center = { 0.0, 0.0, half_box_size.z() + Offset }; + m_grabbers[2].color = AXES_COLOR[2]; +#else // x axis m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() }; m_grabbers[0].color = AXES_COLOR[0]; @@ -131,6 +160,7 @@ void GLGizmoMove3D::on_render() // z axis m_grabbers[2].center = { center.x(), center.y(), box.max.z() + Offset }; m_grabbers[2].color = AXES_COLOR[2]; +#endif // ENABLE_WORLD_COORDINATE glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); @@ -183,7 +213,11 @@ void GLGizmoMove3D::on_render() if (m_grabbers[i].enabled) { glsafe(::glColor4fv(AXES_COLOR[i].data())); ::glBegin(GL_LINES); +#if ENABLE_WORLD_COORDINATE + ::glVertex3dv(zero.data()); +#else ::glVertex3dv(center.data()); +#endif // ENABLE_WORLD_COORDINATE ::glVertex3dv(m_grabbers[i].center.data()); glsafe(::glEnd()); } @@ -225,7 +259,11 @@ void GLGizmoMove3D::on_render() #else glsafe(::glColor4fv(AXES_COLOR[m_hover_id].data())); ::glBegin(GL_LINES); +#if ENABLE_WORLD_COORDINATE + ::glVertex3dv(zero.data()); +#else ::glVertex3dv(center.data()); +#endif // ENABLE_WORLD_COORDINATE ::glVertex3dv(m_grabbers[m_hover_id].center.data()); glsafe(::glEnd()); @@ -243,36 +281,50 @@ void GLGizmoMove3D::on_render() render_grabber_extension((Axis)m_hover_id, box, false); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR } + +#if ENABLE_WORLD_COORDINATE + glsafe(::glPopMatrix()); +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoMove3D::on_render_for_picking() { glsafe(::glDisable(GL_DEPTH_TEST)); +#if ENABLE_WORLD_COORDINATE + const Selection& selection = m_parent.get_selection(); + const BoundingBoxf3& box = selection.get_bounding_box(); + glsafe(::glPushMatrix()); + transform_to_local(selection); +#else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); +#endif // ENABLE_WORLD_COORDINATE render_grabbers_for_picking(box); #if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(X, box, true); render_grabber_extension(Y, box, true); render_grabber_extension(Z, box, true); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE + glsafe(::glPopMatrix()); +#endif // ENABLE_WORLD_COORDINATE } double GLGizmoMove3D::calc_projection(const UpdateData& data) const { double projection = 0.0; - Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; - double len_starting_vec = starting_vec.norm(); + const Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; + const double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // in our case plane normal and ray direction are the same (orthogonal view) // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + const Vec3d inters = data.mouse_ray.a + (m_starting_drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; // vector from the starting position to the found intersection - Vec3d inters_vec = inters - m_starting_drag_position; + const Vec3d inters_vec = inters - m_starting_drag_position; // finds projection of the vector along the staring direction projection = inters_vec.dot(starting_vec.normalized()); @@ -345,5 +397,18 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box } #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE +void GLGizmoMove3D::transform_to_local(const Selection& selection) const +{ + const Vec3d center = selection.get_bounding_box().center(); + glsafe(::glTranslated(center.x(), center.y(), center.z())); + + if (!wxGetApp().obj_manipul()->get_world_coordinates()) { + const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + glsafe(::glMultMatrixd(orient_matrix.data())); + } +} +#endif // ENABLE_WORLD_COORDINATE + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 6a618c3e4..db9d3ac74 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -7,6 +7,10 @@ namespace Slic3r { namespace GUI { +#if ENABLE_WORLD_COORDINATE + class Selection; +#endif // ENABLE_WORLD_COORDINATE + class GLGizmoMove3D : public GLGizmoBase { static const double Offset; @@ -51,17 +55,23 @@ public: void data_changed() override; protected: - bool on_init() override; - std::string on_get_name() const override; - bool on_is_activable() const override; - void on_start_dragging() override; - void on_stop_dragging() override; - void on_dragging(const UpdateData& data) override; - void on_render() override; - void on_render_for_picking() override; + virtual bool on_init() override; + virtual std::string on_get_name() const override; + virtual bool on_is_activable() const override; + virtual void on_start_dragging() override; + virtual void on_stop_dragging() override; +#if !ENABLE_WORLD_COORDINATE + virtual void on_start_dragging() override; +#endif // !ENABLE_WORLD_COORDINATE + virtual void on_dragging(const UpdateData& data) override; + virtual void on_render() override; + virtual void on_render_for_picking() override; private: double calc_projection(const UpdateData& data) const; +#if ENABLE_WORLD_COORDINATE + void transform_to_local(const Selection& selection) const; +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index e7c4e1763..ae261fc7a 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -699,13 +699,31 @@ void Selection::translate(const Vec3d& displacement, bool local) if (local) v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); else { +#if ENABLE_WORLD_COORDINATE + const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Vec3d local_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix() * volume_data.get_instance_mirror_matrix()).inverse() * displacement; + v.set_volume_offset(volume_data.get_volume_position() + local_displacement); +#else const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); +#endif // ENABLE_WORLD_COORDINATE } } else if (m_mode == Instance) { +#if ENABLE_WORLD_COORDINATE + if (is_from_fully_selected_instance(i)) { + if (local) { + const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix() * volume_data.get_instance_mirror_matrix()) * displacement; + v.set_instance_offset(volume_data.get_instance_position() + world_displacement); + } + else + v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); + } +#else if (is_from_fully_selected_instance(i)) v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); +#endif // ENABLE_WORLD_COORDINATE else { const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); From 51e80f7049f32d71fc8eaa835a4da9fd38ea856f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 8 Oct 2021 14:43:46 +0200 Subject: [PATCH 056/169] Fixed syntax error introduced with 116f928903725485d0d5c690c4906fb083807dea Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index db9d3ac74..2c9e94f07 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -8,7 +8,7 @@ namespace Slic3r { namespace GUI { #if ENABLE_WORLD_COORDINATE - class Selection; +class Selection; #endif // ENABLE_WORLD_COORDINATE class GLGizmoMove3D : public GLGizmoBase From 61e7eb4adec03b5353378b5c025a6af51d395bd7 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 11 Oct 2021 09:52:39 +0200 Subject: [PATCH 057/169] Tech ENABLE_WORLD_COORDINATE - Modified text of tooltips for Gizmo Move Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 22d777d2c..2e943bbd8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -26,6 +26,25 @@ std::string GLGizmoMove3D::get_tooltip() const { const Selection& selection = m_parent.get_selection(); const bool show_position = selection.is_single_full_instance(); +#if ENABLE_WORLD_COORDINATE + const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); + Vec3d position = Vec3d::Zero(); + if (!world_coordinates) { + if (selection.is_single_modifier() || selection.is_single_volume() || selection.is_wipe_tower()) + position = selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_offset(); + } + else + position = selection.get_bounding_box().center(); + + if (m_hover_id == 0) + return m_grabbers[0].dragging ? "DX: " + format(m_displacement.x(), 2) : "X: " + format(position.x(), 2); + else if (m_hover_id == 1) + return m_grabbers[1].dragging ? "DY: " + format(m_displacement.y(), 2) : "Y: " + format(position.y(), 2); + else if (m_hover_id == 2) + return m_grabbers[2].dragging ? "DZ: " + format(m_displacement.z(), 2) : "Z: " + format(position.z(), 2); + else + return ""; +#else const Vec3d& position = selection.get_bounding_box().center(); if (m_hover_id == 0 || m_grabbers[0].dragging) @@ -36,6 +55,7 @@ std::string GLGizmoMove3D::get_tooltip() const return "Z: " + format(show_position ? position.z() : m_displacement.z(), 2); else return ""; +#endif // ENABLE_WORLD_COORDINATE } bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) { @@ -317,7 +337,7 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const const Vec3d starting_vec = m_starting_drag_position - m_starting_box_center; const double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { - Vec3d mouse_dir = data.mouse_ray.unit_vector(); + const Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // in our case plane normal and ray direction are the same (orthogonal view) From ca5742c40140ab3b02bcad1ed1be66841ece2e7f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 12 Oct 2021 11:07:31 +0200 Subject: [PATCH 058/169] Tech ENABLE_WORLD_COORDINATE - Gizmo rotate oriented in dependence of the selected coordinate system Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 2 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 48 +++++++++++++----- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 3 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 19 ++------ src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 3 -- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 59 ++++++++++++++++++----- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 7 +++ src/slic3r/GUI/Selection.cpp | 58 ++++++++++++++++++---- src/slic3r/GUI/Selection.hpp | 10 ++-- 9 files changed, 155 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 6eae83277..3c35f6bbd 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -619,7 +619,7 @@ Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) { const Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); - const Vec3d axis = angle_axis.axis(); + const Vec3d& axis = angle_axis.axis(); const double angle = angle_axis.angle(); #ifndef NDEBUG if (std::abs(angle) > 1e-8) { diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 1cc35be7f..3ed3d4bb7 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -278,7 +278,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : return; // Update mirroring at the GLVolumes. - selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); + selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL); selection.synchronize_unselected_volumes(); // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. canvas->do_mirror(L("Set Mirror")); @@ -379,7 +379,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : return; // Update rotation at the GLVolumes. - selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL); + selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL); selection.synchronize_unselected_volumes(); // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. canvas->do_rotate(L("Reset Rotation")); @@ -551,17 +551,23 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = volume->get_instance_offset(); #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(); +#if ENABLE_WORLD_COORDINATE + m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); +#else + m_new_rotation = Vec3d::Zero(); +#endif // ENABLE_WORLD_COORDINATE + m_new_size = selection.get_scaled_instance_bounding_box().size(); m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; } else { #if ENABLE_WORLD_COORDINATE m_new_position = Vec3d::Zero(); -#endif // ENABLE_WORLD_COORDINATE + m_new_rotation = Vec3d::Zero(); +#else m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); +#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.; + m_new_scale = volume->get_instance_scaling_factor() * 100.0; } m_new_enabled = true; @@ -570,7 +576,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) const BoundingBoxf3& box = selection.get_bounding_box(); m_new_position = box.center(); m_new_rotation = Vec3d::Zero(); - m_new_scale = Vec3d(100., 100., 100.); + m_new_scale = Vec3d(100.0, 100.0, 100.0); m_new_size = box.size(); m_new_rotate_label_string = L("Rotate"); m_new_scale_label_string = L("Scale"); @@ -586,7 +592,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) const Vec3d& offset = trafo.get_offset(); const Vec3d& rotation = trafo.get_rotation(); const Vec3d& scaling_factor = trafo.get_scaling_factor(); - const Vec3d& m = trafo.get_mirror(); +// const Vec3d& mirror = trafo.get_mirror(); m_new_position = offset; m_new_rotation = rotation * (180.0 / M_PI); @@ -596,8 +602,8 @@ void ObjectManipulation::update_settings_value(const Selection& selection) else { #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); - m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); - m_new_scale = volume->get_volume_scaling_factor() * 100.; + m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); + m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size())); #if ENABLE_WORLD_COORDINATE } @@ -712,11 +718,20 @@ void ObjectManipulation::update_reset_buttons_visibility() if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_WORLD_COORDINATE + Vec3d rotation = Vec3d::Zero(); + Vec3d scale = Vec3d::Ones(); +#else Vec3d rotation; Vec3d scale; - double min_z = 0.; +#endif // ENABLE_WORLD_COORDINATE + double min_z = 0.0; +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_full_instance() && m_world_coordinates) { +#else if (selection.is_single_full_instance()) { +#endif // ENABLE_WORLD_COORDINATE rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); min_z = wxGetApp().model().objects[volume->composite_id.object_id]->bounding_box().min.z(); @@ -728,7 +743,11 @@ void ObjectManipulation::update_reset_buttons_visibility() } show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); +#if ENABLE_WORLD_COORDINATE + show_drop_to_bed = min_z > EPSILON; +#else show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; +#endif // ENABLE_WORLD_COORDINATE } wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] { @@ -1042,6 +1061,13 @@ void ObjectManipulation::set_world_coordinates(const bool world_coordinates) canvas->set_as_dirty(); canvas->request_extra_frame(); } + +bool ObjectManipulation::get_world_coordinates() const +{ + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + return wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()) ? + m_world_coordinates : true; +} #endif // ENABLE_WORLD_COORDINATE void ObjectManipulation::msw_rescale() diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 6d0383038..a4f826fea 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -185,10 +185,11 @@ public: // Does the object manipulation panel work in World or Local coordinates? #if ENABLE_WORLD_COORDINATE void set_world_coordinates(const bool world_coordinates); + bool get_world_coordinates() const; #else void set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); } -#endif // ENABLE_WORLD_COORDINATE bool get_world_coordinates() const { return m_world_coordinates; } +#endif // ENABLE_WORLD_COORDINATE void reset_cache() { m_cache.reset(); } #ifndef __APPLE__ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 2e943bbd8..8d44e87d5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -24,27 +24,18 @@ GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filenam std::string GLGizmoMove3D::get_tooltip() const { - const Selection& selection = m_parent.get_selection(); - const bool show_position = selection.is_single_full_instance(); #if ENABLE_WORLD_COORDINATE - const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); - Vec3d position = Vec3d::Zero(); - if (!world_coordinates) { - if (selection.is_single_modifier() || selection.is_single_volume() || selection.is_wipe_tower()) - position = selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_offset(); - } - else - position = selection.get_bounding_box().center(); - if (m_hover_id == 0) - return m_grabbers[0].dragging ? "DX: " + format(m_displacement.x(), 2) : "X: " + format(position.x(), 2); + return "X: " + format(m_displacement.x(), 2); else if (m_hover_id == 1) - return m_grabbers[1].dragging ? "DY: " + format(m_displacement.y(), 2) : "Y: " + format(position.y(), 2); + return "Y: " + format(m_displacement.y(), 2); else if (m_hover_id == 2) - return m_grabbers[2].dragging ? "DZ: " + format(m_displacement.z(), 2) : "Z: " + format(position.z(), 2); + return "Z: " + format(m_displacement.z(), 2); else return ""; #else + const Selection& selection = m_parent.get_selection(); + const bool show_position = selection.is_single_full_instance(); const Vec3d& position = selection.get_bounding_box().center(); if (m_hover_id == 0 || m_grabbers[0].dragging) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 2c9e94f07..fbc9ca0df 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -60,9 +60,6 @@ protected: virtual bool on_is_activable() const override; virtual void on_start_dragging() override; virtual void on_stop_dragging() override; -#if !ENABLE_WORLD_COORDINATE - virtual void on_start_dragging() override; -#endif // !ENABLE_WORLD_COORDINATE virtual void on_dragging(const UpdateData& data) override; virtual void on_render() override; virtual void on_render_for_picking() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 640199019..b6bffac5f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -2,15 +2,18 @@ #include "GLGizmoRotate.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp" - -#include +#if ENABLE_WORLD_COORDINATE +#include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#endif // ENABLE_WORLD_COORDINATE #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp" + #include "libslic3r/PresetBundle.hpp" -#include "slic3r/GUI/Jobs/RotoptimizeJob.hpp" +#include namespace Slic3r { namespace GUI { @@ -99,6 +102,9 @@ bool GLGizmoRotate::on_init() void GLGizmoRotate::on_start_dragging() { +#if ENABLE_WORLD_COORDINATE + init_data_from_selection(m_parent.get_selection()); +#else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); m_center = box.center(); m_radius = Offset + box.radius(); @@ -106,6 +112,7 @@ void GLGizmoRotate::on_start_dragging() 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; +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoRotate::on_dragging(const UpdateData &data) @@ -154,12 +161,16 @@ void GLGizmoRotate::on_render() const BoundingBoxf3& box = selection.get_bounding_box(); if (m_hover_id != 0 && !m_grabbers.front().dragging) { +#if ENABLE_WORLD_COORDINATE + init_data_from_selection(selection); +#else m_center = box.center(); m_radius = Offset + 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_radius * (1.0f + ScaleLongTooth); +#endif // ENABLE_WORLD_COORDINATE } const double grabber_radius = (double)m_radius * (1.0 + (double)GrabberOffset); @@ -257,6 +268,24 @@ void GLGizmoRotate::on_render_for_picking() #endif // !ENABLE_GL_SHADERS_ATTRIBUTES } +#if ENABLE_WORLD_COORDINATE +void GLGizmoRotate::init_data_from_selection(const Selection& selection) +{ + const BoundingBoxf3& box = selection.get_bounding_box(); + m_center = box.center(); + m_radius = Offset + 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 (wxGetApp().obj_manipul()->get_world_coordinates()) + m_orient_matrix = Transform3d::Identity(); + else + m_orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); +} +#endif // ENABLE_WORLD_COORDINATE + void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit) { if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) @@ -317,10 +346,10 @@ void GLGizmoRotate::render_circle() const #else ::glBegin(GL_LINE_LOOP); for (unsigned int i = 0; i < ScaleStepsCount; ++i) { - float angle = (float)i * ScaleStepRad; - float x = ::cos(angle) * m_radius; - float y = ::sin(angle) * m_radius; - float z = 0.0f; + const float angle = (float)i * ScaleStepRad; + const float x = ::cos(angle) * m_radius; + const float y = ::sin(angle) * m_radius; + const float z = 0.0f; ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); } glsafe(::glEnd()); @@ -519,10 +548,10 @@ void GLGizmoRotate::render_angle() const #else ::glBegin(GL_LINE_STRIP); for (unsigned int i = 0; i <= AngleResolution; ++i) { - float angle = (float)i * step_angle; - float x = ::cos(angle) * ex_radius; - float y = ::sin(angle) * ex_radius; - float z = 0.0f; + const float angle = (float)i * step_angle; + const float x = ::cos(angle) * ex_radius; + const float y = ::sin(angle) * ex_radius; + const float z = 0.0f; ::glVertex3f((GLfloat)x, (GLfloat)y, (GLfloat)z); } glsafe(::glEnd()); @@ -687,10 +716,14 @@ void GLGizmoRotate::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); +#if ENABLE_WORLD_COORDINATE + glsafe(::glMultMatrixd(m_orient_matrix.data())); +#else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) { const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } +#endif // ENABLE_WORLD_COORDINATE switch (m_axis) { @@ -744,8 +777,12 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons } } +#if ENABLE_WORLD_COORDINATE + m = m * m_orient_matrix.inverse(); +#else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) m = m * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true).inverse(); +#endif // ENABLE_WORLD_COORDINATE m.translate(-m_center); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index f4594bc33..0e01b4beb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -34,6 +34,9 @@ private: float m_snap_coarse_out_radius{ 0.0f }; float m_snap_fine_in_radius{ 0.0f }; float m_snap_fine_out_radius{ 0.0f }; +#if ENABLE_WORLD_COORDINATE + Transform3d m_orient_matrix{ Transform3d::Identity() }; +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR GLModel m_cone; @@ -119,6 +122,10 @@ private: // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray, const Selection& selection) const; + +#if ENABLE_WORLD_COORDINATE + void init_data_from_selection(const Selection& selection); +#endif // ENABLE_WORLD_COORDINATE }; class GLGizmoRotate3D : public GLGizmoBase diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index ae261fc7a..1aacd41bb 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -734,7 +734,7 @@ void Selection::translate(const Vec3d& displacement, bool local) #if !DISABLE_INSTANCES_SYNCH if (translation_type == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); + synchronize_unselected_instances(SyncRotationType::NONE); else if (translation_type == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -791,7 +791,18 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } else { // extracts rotations from the composed transformation - Vec3d new_rotation = transformation_type.world() ? +#if ENABLE_WORLD_COORDINATE + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + const Vec3d new_rotation = transformation_type.world() ? + Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()) : + transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix() * m); + if (rot_axis_max == 2 && transformation_type.world() && transformation_type.joint()) { + // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); + volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + } +#else + const Vec3d new_rotation = transformation_type.world() ? Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); if (rot_axis_max == 2 && transformation_type.joint()) { @@ -799,6 +810,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); } +#endif // ENABLE_WORLD_COORDINATE else if (!(m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center).isApprox(Vec3d::Zero())) volume.set_instance_offset(m_cache.dragging_center + Geometry::assemble_transform(Vec3d::Zero(), new_rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix().inverse() * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); @@ -826,8 +838,8 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ rotate_instance(v, i); else if (m_mode == Volume) { // extracts rotations from the composed transformation - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); if (transformation_type.joint()) { const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); @@ -840,11 +852,24 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } #if !DISABLE_INSTANCES_SYNCH +#if ENABLE_WORLD_COORDINATE + if (m_mode == Instance) { + SyncRotationType synch; + if (transformation_type.world() && rot_axis_max == 2) + synch = SyncRotationType::NONE; + else if (transformation_type.local()) + synch = SyncRotationType::FULL; + else + synch = SyncRotationType::GENERAL; + synchronize_unselected_instances(synch); + } +#else if (m_mode == Instance) synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); +#endif // ENABLE_WORLD_COORDINATE else if (m_mode == Volume) synchronize_unselected_volumes(); - #endif // !DISABLE_INSTANCES_SYNCH +#endif // !DISABLE_INSTANCES_SYNCH } else { // it's the wipe tower that's selected and being rotated GLVolume& volume = *((*m_volumes)[*m_list.begin()]); // the wipe tower is always alone in the selection @@ -887,7 +912,7 @@ void Selection::flattening_rotate(const Vec3d& normal) // Apply the same transformation also to other instances, // but respect their possibly diffrent z-rotation. if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_GENERAL); + synchronize_unselected_instances(SyncRotationType::GENERAL); #endif // !DISABLE_INSTANCES_SYNCH this->set_bounding_boxes_dirty(); @@ -950,7 +975,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); + synchronize_unselected_instances(SyncRotationType::NONE); else if (m_mode == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -1063,7 +1088,7 @@ void Selection::mirror(Axis axis) #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - synchronize_unselected_instances(SYNC_ROTATION_NONE); + synchronize_unselected_instances(SyncRotationType::NONE); else if (m_mode == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -2528,7 +2553,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); switch (sync_rotation_type) { - case SYNC_ROTATION_NONE: { + case SyncRotationType::NONE: { // z only rotation -> synch instance z // The X,Y rotations should be synchronized from start to end of the rotation. assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); @@ -2536,12 +2561,25 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ v->set_instance_offset(Z, volume->get_instance_offset().z()); break; } - case SYNC_ROTATION_GENERAL: + case SyncRotationType::GENERAL: { // generic rotation -> update instance z with the delta of the rotation. const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); break; } +#if ENABLE_WORLD_COORDINATE + case SyncRotationType::FULL: { + // generic rotation -> update instance z with the delta of the rotation. + const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, m_cache.volumes_data[j].get_instance_rotation())); + const Vec3d& axis = angle_axis.axis(); + const double z_diff = (std::abs(axis.x()) > EPSILON || std::abs(axis.y()) > EPSILON) ? + angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(rotation, m_cache.volumes_data[j].get_instance_rotation()); + + v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); + break; + } +#endif // ENABLE_WORLD_COORDINATE + } v->set_instance_scaling_factor(scaling_factor); v->set_instance_mirror(mirror); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index c9e55cf82..8cd6cdb6f 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -389,11 +389,15 @@ private: #endif // ENABLE_GL_SHADERS_ATTRIBUTES public: - enum SyncRotationType { + enum class SyncRotationType { // Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis. - SYNC_ROTATION_NONE = 0, + NONE = 0, // Synchronize after rotation by an axis not parallel with Z. - SYNC_ROTATION_GENERAL = 1, + GENERAL = 1, +#if ENABLE_WORLD_COORDINATE + // Fully synchronize rotation. + FULL = 2, +#endif // ENABLE_WORLD_COORDINATE }; void synchronize_unselected_instances(SyncRotationType sync_rotation_type); void synchronize_unselected_volumes(); From c968ba05fb7f2b04c015d0975c7ff1ed14feb712 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 12 Oct 2021 13:42:55 +0200 Subject: [PATCH 059/169] Tech ENABLE_WORLD_COORDINATE - Resize Move and Rotate gizmos in dependence of the selected coordinate system Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 32 +++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 33 ++++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 1 + src/slic3r/GUI/Selection.cpp | 4 +-- 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 8d44e87d5..6e001fefc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -134,14 +134,23 @@ void GLGizmoMove3D::on_render() m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 18.0)); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR - const Selection& selection = m_parent.get_selection(); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); - const BoundingBoxf3& box = selection.get_bounding_box(); - const Vec3d& center = box.center(); + const Selection& selection = m_parent.get_selection(); + #if ENABLE_WORLD_COORDINATE + BoundingBoxf3 box; + if (wxGetApp().obj_manipul()->get_world_coordinates()) + box = selection.get_bounding_box(); + else { + const Selection::IndicesList& ids = selection.get_volume_idxs(); + for (unsigned int id : ids) { + const GLVolume* v = selection.get_volume(id); + box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + } + } + glsafe(::glPushMatrix()); transform_to_local(selection); @@ -160,6 +169,8 @@ void GLGizmoMove3D::on_render() m_grabbers[2].center = { 0.0, 0.0, half_box_size.z() + Offset }; m_grabbers[2].color = AXES_COLOR[2]; #else + const Selection& selection = m_parent.get_selection(); + const BoundingBoxf3& box = selection.get_bounding_box(); // x axis m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() }; m_grabbers[0].color = AXES_COLOR[0]; @@ -176,10 +187,19 @@ void GLGizmoMove3D::on_render() glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE + auto render_grabber_connection = [this, &zero](unsigned int id) { +#else auto render_grabber_connection = [this, ¢er](unsigned int id) { +#endif // ENABLE_WORLD_COORDINATE if (m_grabbers[id].enabled) { +#if ENABLE_WORLD_COORDINATE + if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(m_grabbers[id].center)) { + m_grabber_connections[id].old_center = m_grabbers[id].center; +#else if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(center)) { m_grabber_connections[id].old_center = center; +#endif // ENABLE_WORLD_COORDINATE m_grabber_connections[id].model.reset(); GLModel::Geometry init_data; @@ -189,7 +209,11 @@ void GLGizmoMove3D::on_render() init_data.reserve_indices(2); // vertices +#if ENABLE_WORLD_COORDINATE + init_data.add_vertex((Vec3f)zero.cast()); +#else init_data.add_vertex((Vec3f)center.cast()); +#endif // ENABLE_WORLD_COORDINATE init_data.add_vertex((Vec3f)m_grabbers[id].center.cast()); // indices diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index b6bffac5f..ec75d3093 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -158,7 +158,9 @@ void GLGizmoRotate::on_render() #endif // !ENABLE_GIZMO_GRABBER_REFACTOR const Selection& selection = m_parent.get_selection(); +#if !ENABLE_WORLD_COORDINATE const BoundingBoxf3& box = selection.get_bounding_box(); +#endif // !ENABLE_WORLD_COORDINATE if (m_hover_id != 0 && !m_grabbers.front().dragging) { #if ENABLE_WORLD_COORDINATE @@ -234,10 +236,17 @@ void GLGizmoRotate::on_render() render_angle(); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE + render_grabber(m_bounding_box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR + render_grabber_extension(m_bounding_box, false); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#else render_grabber(box); #if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(box, false); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); @@ -257,11 +266,18 @@ void GLGizmoRotate::on_render_for_picking() transform_to_local(selection); #endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_WORLD_COORDINATE + render_grabbers_for_picking(m_bounding_box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR + render_grabber_extension(m_bounding_box, true); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#else const BoundingBoxf3& box = selection.get_bounding_box(); render_grabbers_for_picking(box); #if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(box, true); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); @@ -271,9 +287,20 @@ void GLGizmoRotate::on_render_for_picking() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { - const BoundingBoxf3& box = selection.get_bounding_box(); - m_center = box.center(); - m_radius = Offset + box.radius(); + m_bounding_box.reset(); + if (wxGetApp().obj_manipul()->get_world_coordinates()) { + m_bounding_box = selection.get_bounding_box(); + m_center = m_bounding_box.center(); + } + else { + 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())); + } + m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix() * m_bounding_box.center(); + } + 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; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 0e01b4beb..5fc24ed90 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -35,6 +35,7 @@ private: float m_snap_fine_in_radius{ 0.0f }; float m_snap_fine_out_radius{ 0.0f }; #if ENABLE_WORLD_COORDINATE + BoundingBoxf3 m_bounding_box; Transform3d m_orient_matrix{ Transform3d::Identity() }; #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 1aacd41bb..efeba89aa 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -851,7 +851,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } } - #if !DISABLE_INSTANCES_SYNCH +#if !DISABLE_INSTANCES_SYNCH #if ENABLE_WORLD_COORDINATE if (m_mode == Instance) { SyncRotationType synch; @@ -877,7 +877,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ // make sure the wipe tower rotates around its center, not origin // we can assume that only Z rotation changes const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); - const Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0.0, 0.0, 1.0)) * center_local; + const Vec3d center_local_new = Eigen::AngleAxisd(rotation.z()-volume.get_volume_rotation().z(), Vec3d(0.0, 0.0, 1.0)) * center_local; volume.set_volume_rotation(rotation); volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); } From e89dc34b3acb84ef7b6737736d1b2ac183756023 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 12 Oct 2021 15:33:03 +0200 Subject: [PATCH 060/169] Tech ENABLE_WORLD_COORDINATE - Fixed drop to bed button behavior --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 39 ++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 3ed3d4bb7..ef4db56d7 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -328,27 +328,58 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : Selection& selection = canvas->get_selection(); if (selection.is_single_volume() || selection.is_single_modifier()) { +#if ENABLE_WORLD_COORDINATE const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const double min_z = get_volume_min_z(*volume); + if (!m_world_coordinates) { + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); - const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); - const Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(*volume)); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); + change_position_value(0, diff.x()); + change_position_value(1, diff.y()); + change_position_value(2, diff.z()); + } + else { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); + change_position_value(2, m_cache.position.z() - min_z); + } +#else + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (get_volume_min_z(*volume) * Vec3d::UnitZ()); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); change_position_value(1, diff.y()); change_position_value(2, diff.z()); +#endif // ENABLE_WORLD_COORDINATE } else if (selection.is_single_full_instance()) { +#if ENABLE_WORLD_COORDINATE + const double min_z = selection.get_scaled_instance_bounding_box().min.z(); + if (!m_world_coordinates) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); + change_position_value(0, diff.x()); + change_position_value(1, diff.y()); + change_position_value(2, diff.z()); + } + else { +#else const ModelObjectPtrs& objects = wxGetApp().model().objects; const int idx = selection.get_object_idx(); if (0 <= idx && idx < static_cast(objects.size())) { const ModelObject* mo = wxGetApp().model().objects[idx]; const double min_z = mo->bounding_box().min.z(); if (std::abs(min_z) > SINKING_Z_THRESHOLD) { +#endif // ENABLE_WORLD_COORDINATE Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(2, m_cache.position.z() - min_z); } +#if !ENABLE_WORLD_COORDINATE } +#endif // !ENABLE_WORLD_COORDINATE } }); editors_grid_sizer->Add(m_drop_to_bed_button); @@ -734,7 +765,7 @@ void ObjectManipulation::update_reset_buttons_visibility() #endif // ENABLE_WORLD_COORDINATE rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); - min_z = wxGetApp().model().objects[volume->composite_id.object_id]->bounding_box().min.z(); + min_z = selection.get_scaled_instance_bounding_box().min.z(); } else { rotation = volume->get_volume_rotation(); @@ -744,7 +775,7 @@ void ObjectManipulation::update_reset_buttons_visibility() show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); #if ENABLE_WORLD_COORDINATE - show_drop_to_bed = min_z > EPSILON; + show_drop_to_bed = min_z < SINKING_Z_THRESHOLD; #else show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; #endif // ENABLE_WORLD_COORDINATE From 345ee7cf28442980009ed790edc44c7bf40e7a11 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Jun 2022 10:55:59 +0200 Subject: [PATCH 061/169] Let's not call yield in PlaterWorker Not worth the risk, needs further investigation --- src/slic3r/GUI/Jobs/PlaterWorker.hpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/slic3r/GUI/Jobs/PlaterWorker.hpp b/src/slic3r/GUI/Jobs/PlaterWorker.hpp index 0fef3655e..37b18b3b8 100644 --- a/src/slic3r/GUI/Jobs/PlaterWorker.hpp +++ b/src/slic3r/GUI/Jobs/PlaterWorker.hpp @@ -39,13 +39,7 @@ class PlaterWorker: public Worker { void update_status(int st, const std::string &msg = "") override { ctl.update_status(st, msg); - - // If the worker is not using additional threads, the UI - // is refreshed with this call. If the worker is running - // in it's own thread, this will be one additional - // evaluation of the event loop which should have no visible - // effects. - call_on_main_thread([] { wxYieldIfNeeded(); }); + wxWakeUpIdle(); } bool was_canceled() const override { return ctl.was_canceled(); } From bd58b1c1c5cf11d189096eb67affd8612e4a351a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 12 Oct 2021 15:41:02 +0200 Subject: [PATCH 062/169] Fixed build when tech ENABLE_WORLD_COORDINATE is disabled Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index efeba89aa..cacda0cd5 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -865,7 +865,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } #else if (m_mode == Instance) - synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); + synchronize_unselected_instances((rot_axis_max == 2) ? SyncRotationType::NONE : SyncRotationType::GENERAL); #endif // ENABLE_WORLD_COORDINATE else if (m_mode == Volume) synchronize_unselected_volumes(); @@ -877,7 +877,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ // make sure the wipe tower rotates around its center, not origin // we can assume that only Z rotation changes const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); - const Vec3d center_local_new = Eigen::AngleAxisd(rotation.z()-volume.get_volume_rotation().z(), Vec3d(0.0, 0.0, 1.0)) * center_local; + const Vec3d center_local_new = Eigen::AngleAxisd(rotation.z()-volume.get_volume_rotation().z(), Vec3d::UnitZ()) * center_local; volume.set_volume_rotation(rotation); volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); } From 30a02466100f4020c858984ad3f3f742b321d193 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 Oct 2021 09:03:35 +0200 Subject: [PATCH 063/169] Tech ENABLE_WORLD_COORDINATE - Fixes in Gizmo Move behavior Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 75 ++++++++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 1 + 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 6e001fefc..c51196137 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -90,23 +90,30 @@ bool GLGizmoMove3D::on_is_activable() const void GLGizmoMove3D::on_start_dragging() { - assert(m_hover_id != -1); - - m_displacement = Vec3d::Zero(); - const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); + if (m_hover_id != -1) { + m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE - const Vec3d center = box.center(); - m_starting_drag_position = center + m_grabbers[m_hover_id].center; - m_starting_box_center = center; - m_starting_box_bottom_center = center; - m_starting_box_bottom_center.z() = box.min.z(); + const BoundingBoxf3 box = get_selection_box(); + Vec3d center; + if (wxGetApp().obj_manipul()->get_world_coordinates()) + center = box.center(); + else { + const Selection& selection = m_parent.get_selection(); + center = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix() * box.center(); + } + m_starting_drag_position = center + m_grabbers[m_hover_id].center; + m_starting_box_center = center; + m_starting_box_bottom_center = center; + m_starting_box_bottom_center.z() = box.min.z(); #else - m_starting_drag_position = m_grabbers[m_hover_id].center; - m_starting_box_center = box.center(); - m_starting_box_bottom_center = box.center(); - m_starting_box_bottom_center.z() = box.min.z(); + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); + m_starting_drag_position = m_grabbers[m_hover_id].center; + m_starting_box_center = box.center(); + m_starting_box_bottom_center = box.center(); + m_starting_box_bottom_center.z() = box.min.z(); #endif // ENABLE_WORLD_COORDINATE -} + } + } void GLGizmoMove3D::on_stop_dragging() { @@ -140,21 +147,11 @@ void GLGizmoMove3D::on_render() const Selection& selection = m_parent.get_selection(); #if ENABLE_WORLD_COORDINATE - BoundingBoxf3 box; - if (wxGetApp().obj_manipul()->get_world_coordinates()) - box = selection.get_bounding_box(); - else { - const Selection::IndicesList& ids = selection.get_volume_idxs(); - for (unsigned int id : ids) { - const GLVolume* v = selection.get_volume(id); - box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); - } - } - glsafe(::glPushMatrix()); - transform_to_local(selection); + transform_to_local(m_parent.get_selection()); const Vec3d zero = Vec3d::Zero(); + BoundingBoxf3 box = get_selection_box(); const Vec3d half_box_size = 0.5 * box.size(); // x axis @@ -171,6 +168,8 @@ void GLGizmoMove3D::on_render() #else const Selection& selection = m_parent.get_selection(); const BoundingBoxf3& box = selection.get_bounding_box(); + const Vec3d& center = box.center(); + // x axis m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() }; m_grabbers[0].color = AXES_COLOR[0]; @@ -308,7 +307,8 @@ void GLGizmoMove3D::on_render() shader->start_using(); shader->set_uniform("emission_factor", 0.1f); // draw grabber - const float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); + const Vec3d box_size = box.size(); + const float mean_size = (float)((box_size.x() + box_size.y() + box_size.z()) / 3.0); m_grabbers[m_hover_id].render(true, mean_size); shader->stop_using(); } @@ -327,10 +327,9 @@ void GLGizmoMove3D::on_render_for_picking() glsafe(::glDisable(GL_DEPTH_TEST)); #if ENABLE_WORLD_COORDINATE - const Selection& selection = m_parent.get_selection(); - const BoundingBoxf3& box = selection.get_bounding_box(); glsafe(::glPushMatrix()); - transform_to_local(selection); + transform_to_local(m_parent.get_selection()); + const BoundingBoxf3 box = get_selection_box(); #else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); #endif // ENABLE_WORLD_COORDINATE @@ -443,6 +442,22 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const glsafe(::glMultMatrixd(orient_matrix.data())); } } + +BoundingBoxf3 GLGizmoMove3D::get_selection_box() +{ + const Selection& selection = m_parent.get_selection(); + BoundingBoxf3 box; + if (wxGetApp().obj_manipul()->get_world_coordinates()) + box = selection.get_bounding_box(); + else { + const Selection::IndicesList& ids = selection.get_volume_idxs(); + for (unsigned int id : ids) { + const GLVolume* v = selection.get_volume(id); + box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + } + } + return box; +} #endif // ENABLE_WORLD_COORDINATE } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index fbc9ca0df..e97d1c896 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -68,6 +68,7 @@ private: double calc_projection(const UpdateData& data) const; #if ENABLE_WORLD_COORDINATE void transform_to_local(const Selection& selection) const; + BoundingBoxf3 get_selection_box(); #endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking); From e76b5875b7b1d098f9ed1c49b300bf83c5809ec0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 Oct 2021 13:11:59 +0200 Subject: [PATCH 064/169] Tech ENABLE_WORLD_COORDINATE - Fixed Move and Rotate Gizmo size when the selected instance is scaled Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index c51196137..6e999ef07 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -453,7 +453,7 @@ BoundingBoxf3 GLGizmoMove3D::get_selection_box() const Selection::IndicesList& ids = selection.get_volume_idxs(); for (unsigned int id : ids) { const GLVolume* v = selection.get_volume(id); - box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); } } return box; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index ec75d3093..b10c9bb19 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -296,9 +296,9 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) 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())); + m_bounding_box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); } - m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix() * m_bounding_box.center(); + m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); } m_radius = Offset + m_bounding_box.radius(); m_snap_coarse_in_radius = m_radius / 3.0f; From 567162a64778fa7ad9db43a9851c10cb5c391289 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 Oct 2021 14:00:00 +0200 Subject: [PATCH 065/169] Refactoring into GLGizmoScale3D Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 46 +++++++++++--------------- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 12 +++---- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 26c9251b4..c44fc41c7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -14,7 +14,7 @@ namespace Slic3r { namespace GUI { -const float GLGizmoScale3D::Offset = 5.0f; +const double GLGizmoScale3D::Offset = 5.0; GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) @@ -40,14 +40,11 @@ std::string GLGizmoScale3D::get_tooltip() const { const Selection& selection = m_parent.get_selection(); - bool single_instance = selection.is_single_full_instance(); - bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); - - Vec3f scale = 100.0f * Vec3f::Ones(); - if (single_instance) - scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast(); - else if (single_volume) - scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast(); + Vec3d scale = 100.0 * Vec3d::Ones(); + if (selection.is_single_full_instance()) + scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor(); + else if (selection.is_single_modifier() || selection.is_single_volume()) + scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor(); if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging) return "X: " + format(scale.x(), 4) + "%"; @@ -116,12 +113,12 @@ bool GLGizmoScale3D::on_init() double half_pi = 0.5 * (double)PI; // x axis - m_grabbers[0].angles(1) = half_pi; - m_grabbers[1].angles(1) = half_pi; + m_grabbers[0].angles.y() = half_pi; + m_grabbers[1].angles.y() = half_pi; // y axis - m_grabbers[2].angles(0) = half_pi; - m_grabbers[3].angles(0) = half_pi; + m_grabbers[2].angles.x() = half_pi; + m_grabbers[3].angles.x() = half_pi; m_shortcut_key = WXK_CONTROL_S; @@ -175,9 +172,6 @@ void GLGizmoScale3D::on_render() { const Selection& selection = m_parent.get_selection(); - bool single_instance = selection.is_single_full_instance(); - bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); @@ -188,7 +182,7 @@ void GLGizmoScale3D::on_render() m_offsets_transform = Transform3d::Identity(); Vec3d angles = Vec3d::Zero(); - if (single_instance) { + 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) { @@ -205,7 +199,7 @@ void GLGizmoScale3D::on_render() offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); m_offsets_transform = offsets_transform; } - else if (single_volume) { + else if (selection.is_single_modifier() || selection.is_single_volume()) { const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); m_box = v->bounding_box(); m_transform = v->world_matrix(); @@ -511,7 +505,7 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { - double ratio = calc_ratio(data); + const double ratio = calc_ratio(data); if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; if (m_starting.ctrl_down) { @@ -537,7 +531,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) { - double ratio = calc_ratio(data); + const double ratio = calc_ratio(data); if (ratio > 0.0) { m_scale = m_starting.scale * ratio; m_offset = Vec3d::Zero(); @@ -548,22 +542,22 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { double ratio = 0.0; - Vec3d pivot = (m_starting.ctrl_down && m_hover_id < 6) ? m_starting.pivots[m_hover_id] : m_starting.box.center(); + const Vec3d pivot = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_starting.pivots[m_hover_id] : m_starting.box.center(); - Vec3d starting_vec = m_starting.drag_position - pivot; - double len_starting_vec = starting_vec.norm(); + const Vec3d starting_vec = m_starting.drag_position - pivot; + const double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // in our case plane normal and ray direction are the same (orthogonal view) // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - Vec3d inters = data.mouse_ray.a + (m_starting.drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + const Vec3d inters = data.mouse_ray.a + (m_starting.drag_position - data.mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; // vector from the starting position to the found intersection - Vec3d inters_vec = inters - m_starting.drag_position; + const Vec3d inters_vec = inters - m_starting.drag_position; // finds projection of the vector along the staring direction - double proj = inters_vec.dot(starting_vec.normalized()); + const double proj = inters_vec.dot(starting_vec.normalized()); ratio = (len_starting_vec + proj) / len_starting_vec; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index f4efe052a..40c7a1d38 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -11,17 +11,15 @@ namespace GUI { class GLGizmoScale3D : public GLGizmoBase { - static const float Offset; + static const double Offset; struct StartingData { - Vec3d scale; - Vec3d drag_position; + bool ctrl_down{ false }; + Vec3d scale{ Vec3d::Ones() }; + Vec3d drag_position{ Vec3d::Zero() }; BoundingBoxf3 box; - Vec3d pivots[6]; - bool ctrl_down; - - StartingData() : scale(Vec3d::Ones()), drag_position(Vec3d::Zero()), ctrl_down(false) { for (int i = 0; i < 5; ++i) { pivots[i] = Vec3d::Zero(); } } + std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; }; mutable BoundingBoxf3 m_box; From d50ce6c69c3ddfaa19a0c868e577b9280b02a9d2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 Oct 2021 14:15:20 +0200 Subject: [PATCH 066/169] Another small refactoring into GLGizmoScale3D Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index c44fc41c7..cdec33a1e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -212,35 +212,31 @@ void GLGizmoScale3D::on_render() m_box = selection.get_bounding_box(); const Vec3d& center = m_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_box.min.x(), center.y(), center.z()) - offset_x; + m_grabbers[0].center = m_transform * Vec3d(m_box.min.x() - Offset, center.y(), center.z()); m_grabbers[0].color = (ctrl_down && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; - m_grabbers[1].center = m_transform * Vec3d(m_box.max.x(), center.y(), center.z()) + offset_x; + m_grabbers[1].center = m_transform * Vec3d(m_box.max.x() + Offset, center.y(), center.z()); 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_box.min.y(), center.z()) - offset_y; + m_grabbers[2].center = m_transform * Vec3d(center.x(), m_box.min.y() - Offset, center.z()); 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_box.max.y(), center.z()) + offset_y; + m_grabbers[3].center = m_transform * Vec3d(center.x(), m_box.max.y() + Offset, center.z()); 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_box.min.z()) - offset_z; + m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_box.min.z() - Offset); 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_box.max.z()) + offset_z; + m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_box.max.z() + Offset); m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; // uniform - m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), center.z()) - offset_x - offset_y; - m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), center.z()) + offset_x - offset_y; - m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), center.z()) + offset_x + offset_y; - m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), center.z()) - offset_x + offset_y; + m_grabbers[6].center = m_transform * Vec3d(m_box.min.x() - Offset, m_box.min.y() - Offset, center.z()); + m_grabbers[7].center = m_transform * Vec3d(m_box.max.x() + Offset, m_box.min.y() - Offset, center.z()); + m_grabbers[8].center = m_transform * Vec3d(m_box.max.x() + Offset, m_box.max.y() + Offset, center.z()); + m_grabbers[9].center = m_transform * Vec3d(m_box.min.x() - Offset, m_box.max.y() + Offset, center.z()); for (int i = 6; i < 10; ++i) { m_grabbers[i].color = m_highlight_color; } @@ -547,7 +543,7 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const const Vec3d starting_vec = m_starting.drag_position - pivot; const double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { - Vec3d mouse_dir = data.mouse_ray.unit_vector(); + const Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form // in our case plane normal and ray direction are the same (orthogonal view) From 4946466633e8c3f49d78416b7bd8b343d47e791e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 13 Oct 2021 14:23:51 +0200 Subject: [PATCH 067/169] Fixed color of the line connecting the grabbers while hovering one grabber and pressing CTRL key in Gizmo Scale --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index cdec33a1e..73cd649ef 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -318,7 +318,7 @@ void GLGizmoScale3D::on_render() shader = wxGetApp().get_shader("gouraud_light"); #else // draw connection - glsafe(::glColor4fv(m_grabbers[0].color.data())); + glsafe(::glColor4fv(AXES_COLOR[0].data())); render_grabbers_connection(0, 1); // draw grabbers @@ -351,7 +351,7 @@ void GLGizmoScale3D::on_render() shader = wxGetApp().get_shader("gouraud_light"); #else // draw connection - glsafe(::glColor4fv(m_grabbers[2].color.data())); + glsafe(::glColor4fv(AXES_COLOR[1].data())); render_grabbers_connection(2, 3); // draw grabbers @@ -384,7 +384,7 @@ void GLGizmoScale3D::on_render() shader = wxGetApp().get_shader("gouraud_light"); #else // draw connection - glsafe(::glColor4fv(m_grabbers[4].color.data())); + glsafe(::glColor4fv(AXES_COLOR[2].data())); render_grabbers_connection(4, 5); // draw grabbers From c4ad8bc41aa467e99a29dca2c6ea7550460324e0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 14 Oct 2021 13:59:27 +0200 Subject: [PATCH 068/169] Other refactoring plus some fixes into GLGizmoScale3D Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 43 ++++++++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 6 ++-- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 73cd649ef..1ef63536f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -179,15 +179,14 @@ void GLGizmoScale3D::on_render() 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* vol = selection.get_volume(idx); - m_box.merge(vol->bounding_box().transformed(vol->get_volume_transformation().get_matrix())); + const GLVolume* v = selection.get_volume(idx); + m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); } // gets transform from first selected volume @@ -197,7 +196,6 @@ void GLGizmoScale3D::on_render() 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_volume(*selection.get_volume_idxs().begin()); @@ -206,37 +204,40 @@ void GLGizmoScale3D::on_render() angles = Geometry::extract_euler_angles(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_box = selection.get_bounding_box(); const Vec3d& center = m_box.center(); - const bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); + Vec3d offset_x = offsets_transform * (Offset * Vec3d::UnitX()); + Vec3d offset_y = offsets_transform * (Offset * Vec3d::UnitY()); + Vec3d offset_z = offsets_transform * (Offset * Vec3d::UnitZ()); + + bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); // x axis - m_grabbers[0].center = m_transform * Vec3d(m_box.min.x() - Offset, center.y(), center.z()); + m_grabbers[0].center = m_transform * Vec3d(m_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_box.max.x() + Offset, center.y(), center.z()); + m_grabbers[1].center = m_transform * Vec3d(m_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_box.min.y() - Offset, center.z()); + m_grabbers[2].center = m_transform * Vec3d(center.x(), m_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_box.max.y() + Offset, center.z()); + m_grabbers[3].center = m_transform * Vec3d(center.x(), m_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_box.min.z() - Offset); + m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_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_box.max.z() + Offset); + m_grabbers[5].center = m_transform * Vec3d(center.x(), center.y(), m_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_box.min.x() - Offset, m_box.min.y() - Offset, center.z()); - m_grabbers[7].center = m_transform * Vec3d(m_box.max.x() + Offset, m_box.min.y() - Offset, center.z()); - m_grabbers[8].center = m_transform * Vec3d(m_box.max.x() + Offset, m_box.max.y() + Offset, center.z()); - m_grabbers[9].center = m_transform * Vec3d(m_box.min.x() - Offset, m_box.max.y() + Offset, center.z()); + m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), center.z()) - offset_x - offset_y; + m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), center.z()) + offset_x - offset_y; + m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), center.z()) + offset_x + offset_y; + m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), center.z()) - offset_x + offset_y; for (int i = 6; i < 10; ++i) { m_grabbers[i].color = m_highlight_color; } @@ -505,7 +506,10 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; if (m_starting.ctrl_down) { - double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis); + double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); + if (!m_parent.get_selection().is_single_full_instance()) + local_offset *= m_starting.scale(axis); + if (m_hover_id == 2 * axis) local_offset *= -1.0; @@ -518,7 +522,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) default: break; } - m_offset = m_offsets_transform * local_offset_vec; + m_offset = local_offset_vec; } else m_offset = Vec3d::Zero(); @@ -538,10 +542,11 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { double ratio = 0.0; - const Vec3d pivot = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_starting.pivots[m_hover_id] : m_starting.box.center(); + const Vec3d pivot = (m_starting.ctrl_down && m_hover_id < 6) ? m_starting.pivots[m_hover_id] : m_starting.box.center(); const Vec3d starting_vec = m_starting.drag_position - pivot; const double len_starting_vec = starting_vec.norm(); + if (len_starting_vec != 0.0) { const Vec3d mouse_dir = data.mouse_ray.unit_vector(); // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 40c7a1d38..d664a099e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -22,10 +22,8 @@ class GLGizmoScale3D : public GLGizmoBase std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; }; - mutable BoundingBoxf3 m_box; - mutable Transform3d m_transform; - // Transforms grabbers offsets to the proper reference system (world for instances, instance for volumes) - mutable Transform3d m_offsets_transform; + BoundingBoxf3 m_box; + Transform3d m_transform; Vec3d m_scale{ Vec3d::Ones() }; Vec3d m_offset{ Vec3d::Zero() }; double m_snap_step{ 0.05 }; From b2a7c84c85db077064882aff75a2ea851627b19d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 15 Oct 2021 11:58:32 +0200 Subject: [PATCH 069/169] Tech ENABLE_WORLD_COORDINATE - Fixed gizmo Scale in world coordinates Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 161 ++++++++++++++++++++++--- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 15 ++- 2 files changed, 161 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 1ef63536f..5d459f747 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -2,6 +2,9 @@ #include "GLGizmoScale.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" +#if ENABLE_WORLD_COORDINATE +#include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_GL_SHADERS_ATTRIBUTES #include "slic3r/GUI/Plater.hpp" #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -110,6 +113,7 @@ bool GLGizmoScale3D::on_init() m_grabbers.push_back(Grabber()); } +#if !ENABLE_WORLD_COORDINATE double half_pi = 0.5 * (double)PI; // x axis @@ -119,6 +123,7 @@ bool GLGizmoScale3D::on_init() // y axis m_grabbers[2].angles.x() = half_pi; m_grabbers[3].angles.x() = half_pi; +#endif // !ENABLE_WORLD_COORDINATE m_shortcut_key = WXK_CONTROL_S; @@ -138,18 +143,36 @@ bool GLGizmoScale3D::on_is_activable() const void GLGizmoScale3D::on_start_dragging() { - assert(m_hover_id != -1); - m_starting.drag_position = m_grabbers[m_hover_id].center; - m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); - m_starting.box = (m_starting.ctrl_down && (m_hover_id < 6)) ? m_box : m_parent.get_selection().get_bounding_box(); + if (m_hover_id != -1) { + m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); +#if ENABLE_WORLD_COORDINATE + m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center; + m_starting.box = m_box; + m_starting.center = m_center; - const Vec3d& center = m_starting.box.center(); - m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); - m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z()); - m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z()); - m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z()); - m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z()); - m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z()); + if (m_starting.ctrl_down) { + const Vec3d center = m_starting.box.center(); + const Transform3d trafo = wxGetApp().obj_manipul()->get_world_coordinates() ? Transform3d::Identity() : m_transform; + m_starting.pivots[0] = trafo * Vec3d(m_starting.box.max.x(), center.y(), center.z()); + m_starting.pivots[1] = trafo * Vec3d(m_starting.box.min.x(), center.y(), center.z()); + m_starting.pivots[2] = trafo * Vec3d(center.x(), m_starting.box.max.y(), center.z()); + m_starting.pivots[3] = trafo * Vec3d(center.x(), m_starting.box.min.y(), center.z()); + m_starting.pivots[4] = trafo * Vec3d(center.x(), center.y(), m_starting.box.max.z()); + m_starting.pivots[5] = trafo * Vec3d(center.x(), center.y(), m_starting.box.min.z()); + } +#else + m_starting.drag_position = m_grabbers[m_hover_id].center; + m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_box : m_parent.get_selection().get_bounding_box(); + + const Vec3d center = m_starting.box.center(); + m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); + m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z()); + m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z()); + m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z()); + m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z()); + m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z()); +#endif // ENABLE_WORLD_COORDINATE + } } void GLGizmoScale3D::on_stop_dragging() { @@ -177,81 +200,156 @@ void GLGizmoScale3D::on_render() m_box.reset(); m_transform = Transform3d::Identity(); +#if ENABLE_WORLD_COORDINATE + m_grabbers_transform = Transform3d::Identity(); + + if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { +#else // Transforms grabbers' offsets to world refefence system Transform3d 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); +#if ENABLE_WORLD_COORDINATE + m_box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); +#else m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); +#endif // ENABLE_WORLD_COORDINATE } // gets transform from first selected volume const GLVolume* v = selection.get_volume(*idxs.begin()); m_transform = v->get_instance_transformation().get_matrix(); +#if ENABLE_WORLD_COORDINATE + m_grabbers_transform = v->get_instance_transformation().get_matrix(true, true, false, true); +#else // 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()); +#endif // ENABLE_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE + else if ((selection.is_single_modifier() || selection.is_single_volume()) && !wxGetApp().obj_manipul()->get_world_coordinates()) { +#else else if (selection.is_single_modifier() || selection.is_single_volume()) { +#endif // ENABLE_WORLD_COORDINATE const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_WORLD_COORDINATE + m_box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); +#else m_box = v->bounding_box(); +#endif // ENABLE_WORLD_COORDINATE m_transform = v->world_matrix(); +#if ENABLE_WORLD_COORDINATE + m_grabbers_transform = m_transform; +#else angles = Geometry::extract_euler_angles(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()); +#endif // ENABLE_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE + else { + m_box = selection.get_bounding_box(); + m_transform = Geometry::assemble_transform(m_box.center()); + m_grabbers_transform = m_transform; + m_center = selection.is_single_full_instance() ? selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_offset() : m_box.center(); + } +#else else m_box = selection.get_bounding_box(); - const Vec3d& center = m_box.center(); Vec3d offset_x = offsets_transform * (Offset * Vec3d::UnitX()); Vec3d offset_y = offsets_transform * (Offset * Vec3d::UnitY()); Vec3d offset_z = offsets_transform * (Offset * Vec3d::UnitZ()); +#endif // ENABLE_WORLD_COORDINATE - bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); + bool ctrl_down = m_dragging && m_starting.ctrl_down || !m_dragging && wxGetKeyState(WXK_CONTROL); // x axis +#if ENABLE_WORLD_COORDINATE + const Vec3d box_half_size = 0.5 * m_box.size(); + bool use_constrain = ctrl_down && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()); + + m_grabbers[0].center = { -(box_half_size.x() + Offset), 0.0, 0.0 }; + m_grabbers[0].color = (use_constrain && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; + m_grabbers[1].center = { box_half_size.x() + Offset, 0.0, 0.0 }; + m_grabbers[1].color = (use_constrain && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; +#else + const Vec3d center = m_box.center(); + m_grabbers[0].center = m_transform * Vec3d(m_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_box.max.x(), center.y(), center.z()) + offset_x; m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; +#endif // ENABLE_WORLD_COORDINATE // y axis +#if ENABLE_WORLD_COORDINATE + m_grabbers[2].center = { 0.0, -(box_half_size.y() + Offset), 0.0 }; + m_grabbers[2].color = (use_constrain && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; + m_grabbers[3].center = { 0.0, box_half_size.y() + Offset, 0.0 }; + m_grabbers[3].color = (use_constrain && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; +#else m_grabbers[2].center = m_transform * Vec3d(center.x(), m_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_box.max.y(), center.z()) + offset_y; m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; +#endif // ENABLE_WORLD_COORDINATE // z axis +#if ENABLE_WORLD_COORDINATE + m_grabbers[4].center = { 0.0, 0.0, -(box_half_size.z() + Offset) }; + m_grabbers[4].color = (use_constrain && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2]; + m_grabbers[5].center = { 0.0, 0.0, box_half_size.z() + Offset }; + m_grabbers[5].color = (use_constrain && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; +#else m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_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_box.max.z()) + offset_z; m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; +#endif // ENABLE_WORLD_COORDINATE // uniform +#if ENABLE_WORLD_COORDINATE + m_grabbers[6].center = { -(box_half_size.x() + Offset), -(box_half_size.y() + Offset), 0.0 }; + m_grabbers[7].center = { box_half_size.x() + Offset, -(box_half_size.y() + Offset), 0.0 }; + m_grabbers[8].center = { box_half_size.x() + Offset, box_half_size.y() + Offset, 0.0 }; + m_grabbers[9].center = { -(box_half_size.x() + Offset), box_half_size.y() + Offset, 0.0 }; +#else m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), center.z()) - offset_x - offset_y; m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), center.z()) + offset_x - offset_y; m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), center.z()) + offset_x + offset_y; m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), center.z()) - offset_x + offset_y; +#endif // ENABLE_WORLD_COORDINATE for (int i = 6; i < 10; ++i) { m_grabbers[i].color = m_highlight_color; } +#if !ENABLE_WORLD_COORDINATE // sets grabbers orientation for (int i = 0; i < 10; ++i) { m_grabbers[i].angles = angles; } +#endif // !ENABLE_WORLD_COORDINATE glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); +#if ENABLE_WORLD_COORDINATE + glsafe(::glPushMatrix()); + transform_to_local(selection); + const float grabber_mean_size = (float)((m_box.size().x() + m_box.size().y() + m_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) { #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -439,12 +537,23 @@ void GLGizmoScale3D::on_render() shader->stop_using(); } } + +#if ENABLE_WORLD_COORDINATE + glsafe(::glPopMatrix()); +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoScale3D::on_render_for_picking() { glsafe(::glDisable(GL_DEPTH_TEST)); +#if ENABLE_WORLD_COORDINATE + glsafe(::glPushMatrix()); + transform_to_local(m_parent.get_selection()); + render_grabbers_for_picking(m_box); + glsafe(::glPopMatrix()); +#else render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); +#endif // ENABLE_WORLD_COORDINATE } #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -502,13 +611,24 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { +#if ENABLE_WORLD_COORDINATE + double ratio = calc_ratio(data); +#else const double ratio = calc_ratio(data); +#endif // ENABLE_WORLD_COORDINATE if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; if (m_starting.ctrl_down) { +#if ENABLE_WORLD_COORDINATE + const double len_starting_vec = std::abs(m_starting.box.center()(axis) - m_starting.pivots[m_hover_id](axis)); + const double len_center_vec = std::abs(m_starting.center(axis) - m_starting.pivots[m_hover_id](axis)); + const double inner_ratio = len_center_vec / len_starting_vec; + double local_offset = inner_ratio * 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); +#else double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); if (!m_parent.get_selection().is_single_full_instance()) local_offset *= m_starting.scale(axis); +#endif // ENABLE_WORLD_COORDINATE if (m_hover_id == 2 * axis) local_offset *= -1.0; @@ -569,5 +689,18 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const return ratio; } +#if ENABLE_WORLD_COORDINATE +void GLGizmoScale3D::transform_to_local(const Selection& selection) const +{ + const Vec3d center = selection.get_bounding_box().center(); + glsafe(::glTranslated(center.x(), center.y(), center.z())); + + if (!wxGetApp().obj_manipul()->get_world_coordinates()) { + const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + glsafe(::glMultMatrixd(orient_matrix.data())); + } +} +#endif // ENABLE_WORLD_COORDINATE + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index d664a099e..70ea91016 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -5,10 +5,13 @@ #include "libslic3r/BoundingBox.hpp" - namespace Slic3r { namespace GUI { +#if ENABLE_WORLD_COORDINATE +class Selection; +#endif // ENABLE_WORLD_COORDINATE + class GLGizmoScale3D : public GLGizmoBase { static const double Offset; @@ -18,12 +21,19 @@ class GLGizmoScale3D : public GLGizmoBase bool ctrl_down{ false }; Vec3d scale{ Vec3d::Ones() }; Vec3d drag_position{ Vec3d::Zero() }; +#if ENABLE_WORLD_COORDINATE + Vec3d center{ Vec3d::Zero() }; +#endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 box; std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; }; BoundingBoxf3 m_box; Transform3d m_transform; +#if ENABLE_WORLD_COORDINATE + Transform3d m_grabbers_transform; + Vec3d m_center{ Vec3d::Zero() }; +#endif // ENABLE_WORLD_COORDINATE Vec3d m_scale{ Vec3d::Ones() }; Vec3d m_offset{ Vec3d::Zero() }; double m_snap_step{ 0.05 }; @@ -83,6 +93,9 @@ private: void do_scale_uniform(const UpdateData& data); double calc_ratio(const UpdateData& data) const; +#if ENABLE_WORLD_COORDINATE + void transform_to_local(const Selection& selection) const; +#endif // ENABLE_WORLD_COORDINATE }; From 6433d3af91d06f042a979fdb89b970f63704ad8a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 18 Oct 2021 15:13:47 +0200 Subject: [PATCH 070/169] Tech ENABLE_WORLD_COORDINATE - Fixed volumes rotation in world coordinate Added sub-tech ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET which enable showing world coordinates of volumes' offset relative to the instance containing them Show 'Drop to bed' button in sidebar whenever the selected instance or volume is not laying on the printbed Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 2 +- src/libslic3r/Technologies.hpp | 2 ++ src/slic3r/GUI/GUI_ObjectManipulation.cpp | 19 +++++++++++++++++-- src/slic3r/GUI/Selection.cpp | 19 +++++++++++++++---- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 3c35f6bbd..58c90d9bc 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -315,7 +315,7 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation, Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix) { - // reference: http://www.gregslabaugh.net/publications/euler.pdf + // reference: http://eecs.qmul.ac.uk/~gslabaugh/publications/euler.pdf Vec3d angles1 = Vec3d::Zero(); Vec3d angles2 = Vec3d::Zero(); if (std::abs(std::abs(rotation_matrix(2, 0)) - 1.0) < 1e-5) { diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 302c62879..40abad362 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -71,6 +71,8 @@ #define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1) // Enable editing volumes transformation in world coordinates and instances in local coordinates #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) +// Enable showing world coordinates of volumes' offset relative to the instance containing them +#define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index ef4db56d7..d4b1a3b67 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -620,13 +620,17 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (m_world_coordinates) { const Geometry::Transformation trafo(volume->world_matrix()); +#if ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET + const Vec3d offset = trafo.get_offset() - volume->get_instance_offset(); +#else const Vec3d& offset = trafo.get_offset(); const Vec3d& rotation = trafo.get_rotation(); +#endif // ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET const Vec3d& scaling_factor = trafo.get_scaling_factor(); // const Vec3d& mirror = trafo.get_mirror(); m_new_position = offset; - m_new_rotation = rotation * (180.0 / M_PI); + m_new_rotation = Vec3d::Zero(); m_new_scale = scaling_factor * 100.0; m_new_size = volume->bounding_box().size().cwiseProduct(scaling_factor); } @@ -775,7 +779,7 @@ void ObjectManipulation::update_reset_buttons_visibility() show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); #if ENABLE_WORLD_COORDINATE - show_drop_to_bed = min_z < SINKING_Z_THRESHOLD; + show_drop_to_bed = std::abs(min_z) > EPSILON; #else show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; #endif // ENABLE_WORLD_COORDINATE @@ -921,6 +925,16 @@ void ObjectManipulation::change_rotation_value(int axis, double value) Selection& selection = canvas->get_selection(); TransformationType transformation_type(TransformationType::World_Relative_Joint); +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_full_instance()) + transformation_type.set_independent(); + + if (!m_world_coordinates) { + //FIXME Selection::rotate() does not process absolute rotations correctly: It does not recognize the axis index, which was changed. + // transformation_type.set_absolute(); + transformation_type.set_local(); + } +#else if (selection.is_single_full_instance() || selection.requires_local_axes()) transformation_type.set_independent(); if (selection.is_single_full_instance() && ! m_world_coordinates) { @@ -928,6 +942,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value) // transformation_type.set_absolute(); transformation_type.set_local(); } +#endif // ENABLE_WORLD_COORDINATE selection.setup_cache(); selection.rotate( diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index cacda0cd5..dab2b6414 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -787,7 +787,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; const Vec3d &rotation = first_volume.get_instance_rotation(); const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); - volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + volume.set_instance_rotation(Vec3d(rotation.x(), rotation.y(), rotation.z() + z_diff)); } else { // extracts rotations from the composed transformation @@ -824,16 +824,27 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ if (is_single_full_instance()) rotate_instance(v, i); else if (is_single_volume() || is_single_modifier()) { - if (transformation_type.independent()) +#if ENABLE_WORLD_COORDINATE + if (transformation_type.local()) v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); + else { + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + m = m * m_cache.volumes_data[i].get_instance_rotation_matrix(); + m = m * m_cache.volumes_data[i].get_volume_rotation_matrix(); + m = m_cache.volumes_data[i].get_instance_rotation_matrix().inverse() * m; + v.set_volume_rotation(Geometry::extract_euler_angles(m)); + } +#else + if (transformation_type.independent()) + v.set_volume_rotation(v.get_volume_rotation() + rotation); else { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); v.set_volume_rotation(new_rotation); } +#endif // ENABLE_WORLD_COORDINATE } - else - { + else { if (m_mode == Instance) rotate_instance(v, i); else if (m_mode == Volume) { From 6bdaf0eaec949e1f50556c83d9e19fe94302a528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 2 Jun 2022 11:51:43 +0200 Subject: [PATCH 071/169] Follow-up of a47446574eb3f831907248dc841eb6f7684bbdb3 - Disable tbb::task_scheduler_observer in TBBLocalesSetter destructor. The base class wasn't disabling observing when tbb::task_scheduler_observer was destructed, which leads to undefined behavior. --- src/libslic3r/GCode.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 1f7eaba24..58951ffa5 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1501,7 +1501,7 @@ class TBBLocalesSetter : public tbb::task_scheduler_observer { public: TBBLocalesSetter() { this->observe(true); } - ~TBBLocalesSetter() override = default; + ~TBBLocalesSetter() override { this->observe(false); }; void on_scheduler_entry(bool is_worker) override { @@ -1513,7 +1513,7 @@ public: } private: - tbb::enumerable_thread_specific, tbb::ets_key_usage_type::ets_key_per_instance> m_is_locales_sets; + tbb::enumerable_thread_specific, tbb::ets_key_usage_type::ets_key_per_instance> m_is_locales_sets{false}; }; // Process all layers of all objects (non-sequential mode) with a parallel pipeline: From ca5c04bab2c7b78959135b8ef577a817fce88549 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 18 Oct 2021 15:26:31 +0200 Subject: [PATCH 072/169] Tech ENABLE_WORLD_COORDINATE - Modified method Selection::requires_uniform_scale() Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 67 +++++++++++++++++++++++ src/slic3r/GUI/Selection.cpp | 7 +++ 2 files changed, 74 insertions(+) diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 21b55ea69..2b66c6263 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -227,12 +227,79 @@ void GLGizmosManager::set_hover_id(int id) void GLGizmosManager::update_data() { +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//<<<<<<< HEAD +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_enabled) return; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//======= +// if (!m_enabled) +// return; +// +// const Selection& selection = m_parent.get_selection(); +// +// bool is_wipe_tower = selection.is_wipe_tower(); +// enable_grabber(Move, 2, !is_wipe_tower); +// enable_grabber(Rotate, 0, !is_wipe_tower); +// enable_grabber(Rotate, 1, !is_wipe_tower); +// +//#if ENABLE_WORLD_COORDINATE +// bool enable_scale_xyz = !selection.requires_uniform_scale(); +//#else +// bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); +//#endif // ENABLE_WORLD_COORDINATE +// for (unsigned int i = 0; i < 6; ++i) { +// enable_grabber(Scale, i, enable_scale_xyz); +// } +// +//>>>>>>> 8cf66f52f (Tech ENABLE_WORLD_COORDINATE - Modified method Selection::requires_uniform_scale()) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_common_gizmos_data) m_common_gizmos_data->update(get_current() ? get_current()->get_requirements() : CommonGizmosDataID(0)); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//<<<<<<< HEAD +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_current != Undefined) m_gizmos[m_current]->data_changed(); +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//======= +// +// if (selection.is_single_full_instance()) { +// // all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first +// const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +// set_scale(volume->get_instance_scaling_factor()); +// set_rotation(Vec3d::Zero()); +// ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()]; +// set_flattening_data(model_object); +// set_sla_support_data(model_object); +// set_painter_gizmo_data(); +// } +// else if (selection.is_single_volume() || selection.is_single_modifier()) { +// const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +// set_scale(volume->get_volume_scaling_factor()); +// set_rotation(Vec3d::Zero()); +// set_flattening_data(nullptr); +// set_sla_support_data(nullptr); +// set_painter_gizmo_data(); +// } +// else if (is_wipe_tower) { +// DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; +// set_scale(Vec3d::Ones()); +// set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast(config.option("wipe_tower_rotation_angle"))->value)); +// set_flattening_data(nullptr); +// set_sla_support_data(nullptr); +// set_painter_gizmo_data(); +// } +// else { +// set_scale(Vec3d::Ones()); +// set_rotation(Vec3d::Zero()); +// set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); +// set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); +// set_painter_gizmo_data(); +// } +//>>>>>>> 8cf66f52f (Tech ENABLE_WORLD_COORDINATE - Modified method Selection::requires_uniform_scale()) +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } bool GLGizmosManager::is_running() const diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index dab2b6414..17fc4a674 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -593,8 +593,15 @@ bool Selection::matches(const std::vector& volume_idxs) const bool Selection::requires_uniform_scale() const { +#if ENABLE_WORLD_COORDINATE + if (is_single_modifier() || is_single_volume()) + return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); + else if (is_single_full_instance() && wxGetApp().obj_manipul()->get_world_coordinates()) + return !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()); +#else if (is_single_full_instance() || is_single_modifier() || is_single_volume()) return false; +#endif // ENABLE_WORLD_COORDINATE return true; } From f89a902bbb08ce9beba4a193f921160276b0dbf6 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 19 Oct 2021 10:04:22 +0200 Subject: [PATCH 073/169] Tech ENABLE_WORLD_COORDINATE - Fixed translation in local coordinate for single instance selection Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 12 ++-- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 67 ----------------------- src/slic3r/GUI/Selection.cpp | 2 +- 3 files changed, 9 insertions(+), 72 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 6e999ef07..84ba510bc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -95,13 +95,17 @@ void GLGizmoMove3D::on_start_dragging() #if ENABLE_WORLD_COORDINATE const BoundingBoxf3 box = get_selection_box(); Vec3d center; - if (wxGetApp().obj_manipul()->get_world_coordinates()) + if (wxGetApp().obj_manipul()->get_world_coordinates()) { center = box.center(); + m_starting_drag_position = center + m_grabbers[m_hover_id].center; + } else { const Selection& selection = m_parent.get_selection(); - center = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix() * box.center(); + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const Transform3d trafo = Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()); + center = v.get_instance_offset() + trafo * box.center(); + m_starting_drag_position = center + trafo * m_grabbers[m_hover_id].center; } - m_starting_drag_position = center + m_grabbers[m_hover_id].center; m_starting_box_center = center; m_starting_box_bottom_center = center; m_starting_box_bottom_center.z() = box.min.z(); @@ -112,8 +116,8 @@ void GLGizmoMove3D::on_start_dragging() m_starting_box_bottom_center = box.center(); m_starting_box_bottom_center.z() = box.min.z(); #endif // ENABLE_WORLD_COORDINATE - } } +} void GLGizmoMove3D::on_stop_dragging() { diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 2b66c6263..21b55ea69 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -227,79 +227,12 @@ void GLGizmosManager::set_hover_id(int id) void GLGizmosManager::update_data() { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//<<<<<<< HEAD -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (!m_enabled) return; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//======= -// if (!m_enabled) -// return; -// -// const Selection& selection = m_parent.get_selection(); -// -// bool is_wipe_tower = selection.is_wipe_tower(); -// enable_grabber(Move, 2, !is_wipe_tower); -// enable_grabber(Rotate, 0, !is_wipe_tower); -// enable_grabber(Rotate, 1, !is_wipe_tower); -// -//#if ENABLE_WORLD_COORDINATE -// bool enable_scale_xyz = !selection.requires_uniform_scale(); -//#else -// bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); -//#endif // ENABLE_WORLD_COORDINATE -// for (unsigned int i = 0; i < 6; ++i) { -// enable_grabber(Scale, i, enable_scale_xyz); -// } -// -//>>>>>>> 8cf66f52f (Tech ENABLE_WORLD_COORDINATE - Modified method Selection::requires_uniform_scale()) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_common_gizmos_data) m_common_gizmos_data->update(get_current() ? get_current()->get_requirements() : CommonGizmosDataID(0)); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//<<<<<<< HEAD -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (m_current != Undefined) m_gizmos[m_current]->data_changed(); -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//======= -// -// if (selection.is_single_full_instance()) { -// // all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first -// const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); -// set_scale(volume->get_instance_scaling_factor()); -// set_rotation(Vec3d::Zero()); -// ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()]; -// set_flattening_data(model_object); -// set_sla_support_data(model_object); -// set_painter_gizmo_data(); -// } -// else if (selection.is_single_volume() || selection.is_single_modifier()) { -// const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); -// set_scale(volume->get_volume_scaling_factor()); -// set_rotation(Vec3d::Zero()); -// set_flattening_data(nullptr); -// set_sla_support_data(nullptr); -// set_painter_gizmo_data(); -// } -// else if (is_wipe_tower) { -// DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; -// set_scale(Vec3d::Ones()); -// set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast(config.option("wipe_tower_rotation_angle"))->value)); -// set_flattening_data(nullptr); -// set_sla_support_data(nullptr); -// set_painter_gizmo_data(); -// } -// else { -// set_scale(Vec3d::Ones()); -// set_rotation(Vec3d::Zero()); -// set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); -// set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr); -// set_painter_gizmo_data(); -// } -//>>>>>>> 8cf66f52f (Tech ENABLE_WORLD_COORDINATE - Modified method Selection::requires_uniform_scale()) -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ } bool GLGizmosManager::is_running() const diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 17fc4a674..0d29fdaae 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -721,7 +721,7 @@ void Selection::translate(const Vec3d& displacement, bool local) if (is_from_fully_selected_instance(i)) { if (local) { const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix() * volume_data.get_instance_mirror_matrix()) * displacement; + const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_mirror_matrix()) * displacement; v.set_instance_offset(volume_data.get_instance_position() + world_displacement); } else From 856e0caea62055a8e94fa705170c8f0ae619e2ae Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 19 Oct 2021 10:26:51 +0200 Subject: [PATCH 074/169] Tech ENABLE_WORLD_COORDINATE - Fixed rotation in local coordinate for single instance selection Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 0d29fdaae..edbffacd1 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -803,7 +803,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const Vec3d new_rotation = transformation_type.world() ? Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()) : transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix() * m); - if (rot_axis_max == 2 && transformation_type.world() && transformation_type.joint()) { + if (rot_axis_max == 2 && transformation_type.joint()) { // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); From d5a02e617a1cf427703e0e94d784bec932f4f333 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 20 Oct 2021 12:29:27 +0200 Subject: [PATCH 075/169] Partial revert of 7e5c214b91ae14740fc188413948818a1b49928a to restore code mistakenly removed and needed when tech ENABLE_WORLD_COORDINATE is disabled Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 11 ++++++++--- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 5d459f747..14bbd5102 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -207,6 +207,7 @@ void GLGizmoScale3D::on_render() #else // 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()) { @@ -233,6 +234,7 @@ void GLGizmoScale3D::on_render() 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; #endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE @@ -253,6 +255,7 @@ void GLGizmoScale3D::on_render() angles = Geometry::extract_euler_angles(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()); #endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE @@ -625,9 +628,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) const double inner_ratio = len_center_vec / len_starting_vec; double local_offset = inner_ratio * 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); #else - double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); - if (!m_parent.get_selection().is_single_full_instance()) - local_offset *= m_starting.scale(axis); + double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis); #endif // ENABLE_WORLD_COORDINATE if (m_hover_id == 2 * axis) @@ -642,7 +643,11 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) default: break; } +#if ENABLE_WORLD_COORDINATE m_offset = local_offset_vec; +#else + m_offset = m_offsets_transform * local_offset_vec; +#endif // ENABLE_WORLD_COORDINATE } else m_offset = Vec3d::Zero(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 70ea91016..0792fcc7a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -33,6 +33,9 @@ class GLGizmoScale3D : public GLGizmoBase #if ENABLE_WORLD_COORDINATE Transform3d m_grabbers_transform; Vec3d m_center{ Vec3d::Zero() }; +#else + // Transforms grabbers offsets to the proper reference system (world for instances, instance for volumes) + Transform3d m_offsets_transform; #endif // ENABLE_WORLD_COORDINATE Vec3d m_scale{ Vec3d::Ones() }; Vec3d m_offset{ Vec3d::Zero() }; From d7753fc476d74187b638d13c7552ca1b9dd29770 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 Oct 2021 08:23:13 +0200 Subject: [PATCH 076/169] Tech ENABLE_WORLD_COORDINATE - Fixed constrained non-uniform scaling in world coordinates for rotated instances Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 19 ++++++++++++++++++- src/slic3r/GUI/Selection.cpp | 10 +++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 14bbd5102..34fe723b0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -616,11 +616,28 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { #if ENABLE_WORLD_COORDINATE double ratio = calc_ratio(data); + if (ratio > 0.0) { + Vec3d curr_scale = m_scale; + Vec3d starting_scale = m_starting.scale; + const Selection& selection = m_parent.get_selection(); + const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); + if (selection.is_single_full_instance() && world_coordinates) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); + curr_scale = (m * curr_scale).cwiseAbs(); + starting_scale = (m * starting_scale).cwiseAbs(); + } + + curr_scale(axis) = starting_scale(axis) * ratio; + + if (selection.is_single_full_instance() && world_coordinates) + m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); + else + m_scale = curr_scale; #else const double ratio = calc_ratio(data); -#endif // ENABLE_WORLD_COORDINATE if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; +#endif // ENABLE_WORLD_COORDINATE if (m_starting.ctrl_down) { #if ENABLE_WORLD_COORDINATE const double len_starting_vec = std::abs(m_starting.box.center()(axis) - m_starting.pivots[m_hover_id](axis)); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index edbffacd1..28e8674c4 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -945,16 +945,19 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type GLVolume &v = *(*m_volumes)[i]; if (is_single_full_instance()) { if (transformation_type.relative()) { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); + const Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + const Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); if (transformation_type.joint()) v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); v.set_instance_scaling_factor(new_scale); } else { +#if ENABLE_WORLD_COORDINATE + v.set_instance_scaling_factor(scale); +#else if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { // Non-uniform scaling. Transform the scaling factors into the local coordinate system. // This is only possible, if the instance rotation is mulitples of ninety degrees. @@ -963,6 +966,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type } else v.set_instance_scaling_factor(scale); +#endif // ENABLE_WORLD_COORDINATE } } else if (is_single_volume() || is_single_modifier()) From 1191ab42cb1e0a2046237f892b6d404415256dfd Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 Oct 2021 09:32:49 +0200 Subject: [PATCH 077/169] Tech ENABLE_WORLD_COORDINATE - Added constrained uniform scaling in world coordinates for instances Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 1 + src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 23 ++++++++++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 3 ++- src/slic3r/GUI/Selection.cpp | 4 +++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index d4b1a3b67..d55fd96f8 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1104,6 +1104,7 @@ void ObjectManipulation::set_world_coordinates(const bool world_coordinates) m_world_coordinates = world_coordinates; this->UpdateAndShow(true); GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + canvas->get_gizmos_manager().update_data(); canvas->set_as_dirty(); canvas->request_extra_frame(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 34fe723b0..fac348cdd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -159,6 +159,10 @@ void GLGizmoScale3D::on_start_dragging() m_starting.pivots[3] = trafo * Vec3d(center.x(), m_starting.box.min.y(), center.z()); m_starting.pivots[4] = trafo * Vec3d(center.x(), center.y(), m_starting.box.max.z()); m_starting.pivots[5] = trafo * Vec3d(center.x(), center.y(), m_starting.box.min.z()); + m_starting.pivots[6] = trafo * Vec3d(m_starting.box.max.x(), m_starting.box.max.y(), center.z()); + m_starting.pivots[7] = trafo * Vec3d(m_starting.box.min.x(), m_starting.box.max.y(), center.z()); + m_starting.pivots[8] = trafo * Vec3d(m_starting.box.min.x(), m_starting.box.min.y(), center.z()); + m_starting.pivots[9] = trafo * Vec3d(m_starting.box.max.x(), m_starting.box.min.y(), center.z()); } #else m_starting.drag_position = m_grabbers[m_hover_id].center; @@ -323,18 +327,22 @@ void GLGizmoScale3D::on_render() // uniform #if ENABLE_WORLD_COORDINATE m_grabbers[6].center = { -(box_half_size.x() + Offset), -(box_half_size.y() + Offset), 0.0 }; + m_grabbers[6].color = (use_constrain && m_hover_id == 8) ? CONSTRAINED_COLOR : m_highlight_color; m_grabbers[7].center = { box_half_size.x() + Offset, -(box_half_size.y() + Offset), 0.0 }; + m_grabbers[7].color = (use_constrain && m_hover_id == 9) ? CONSTRAINED_COLOR : m_highlight_color; m_grabbers[8].center = { box_half_size.x() + Offset, box_half_size.y() + Offset, 0.0 }; + 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 m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), center.z()) - offset_x - offset_y; m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), center.z()) + offset_x - offset_y; m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), center.z()) + offset_x + offset_y; m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), center.z()) - offset_x + offset_y; -#endif // ENABLE_WORLD_COORDINATE for (int i = 6; i < 10; ++i) { m_grabbers[i].color = m_highlight_color; } +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_WORLD_COORDINATE // sets grabbers orientation @@ -676,7 +684,20 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) const double ratio = calc_ratio(data); if (ratio > 0.0) { m_scale = m_starting.scale * ratio; +#if ENABLE_WORLD_COORDINATE + if (m_starting.ctrl_down) { + m_offset = 0.5 * (ratio - 1.0) * m_starting.box.size(); + if (m_hover_id == 6 || m_hover_id == 9) + m_offset.x() *= -1.0; + if (m_hover_id == 6 || m_hover_id == 7) + m_offset.y() *= -1.0; + } + else { +#endif // ENABLE_WORLD_COORDINATE m_offset = Vec3d::Zero(); +#if ENABLE_WORLD_COORDINATE + } +#endif // ENABLE_WORLD_COORDINATE } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 0792fcc7a..1429cf633 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -25,7 +25,8 @@ class GLGizmoScale3D : public GLGizmoBase Vec3d center{ Vec3d::Zero() }; #endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 box; - std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; + std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), + Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(),Vec3d::Zero() }; }; BoundingBoxf3 m_box; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 28e8674c4..897d153f9 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -598,12 +598,14 @@ bool Selection::requires_uniform_scale() const return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); else if (is_single_full_instance() && wxGetApp().obj_manipul()->get_world_coordinates()) return !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()); + + return false; #else if (is_single_full_instance() || is_single_modifier() || is_single_volume()) return false; -#endif // ENABLE_WORLD_COORDINATE return true; +#endif // ENABLE_WORLD_COORDINATE } int Selection::get_object_idx() const From 90744071962a091caef19e12aadd48f5d212c66d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 Oct 2021 11:27:36 +0200 Subject: [PATCH 078/169] Follow-up of 86b44b48005e3ddb96e851e19f8e2ccd1a38b667 - Constrained uniform scaling in world coordinates for rotated instances --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 7 +++---- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index fac348cdd..6cadae77d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -159,10 +159,6 @@ void GLGizmoScale3D::on_start_dragging() m_starting.pivots[3] = trafo * Vec3d(center.x(), m_starting.box.min.y(), center.z()); m_starting.pivots[4] = trafo * Vec3d(center.x(), center.y(), m_starting.box.max.z()); m_starting.pivots[5] = trafo * Vec3d(center.x(), center.y(), m_starting.box.min.z()); - m_starting.pivots[6] = trafo * Vec3d(m_starting.box.max.x(), m_starting.box.max.y(), center.z()); - m_starting.pivots[7] = trafo * Vec3d(m_starting.box.min.x(), m_starting.box.max.y(), center.z()); - m_starting.pivots[8] = trafo * Vec3d(m_starting.box.min.x(), m_starting.box.min.y(), center.z()); - m_starting.pivots[9] = trafo * Vec3d(m_starting.box.max.x(), m_starting.box.min.y(), center.z()); } #else m_starting.drag_position = m_grabbers[m_hover_id].center; @@ -687,10 +683,13 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) #if ENABLE_WORLD_COORDINATE if (m_starting.ctrl_down) { m_offset = 0.5 * (ratio - 1.0) * m_starting.box.size(); + if (m_hover_id == 6 || m_hover_id == 9) m_offset.x() *= -1.0; if (m_hover_id == 6 || m_hover_id == 7) m_offset.y() *= -1.0; + + m_offset += (ratio - 1.0) * (m_starting.center - m_starting.box.center()); } else { #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 1429cf633..0792fcc7a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -25,8 +25,7 @@ class GLGizmoScale3D : public GLGizmoBase Vec3d center{ Vec3d::Zero() }; #endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 box; - std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), - Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(),Vec3d::Zero() }; + std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; }; BoundingBoxf3 m_box; From 0eaa4c5dea87d9afd2c402236a19ea7226fa7094 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 Oct 2021 12:16:59 +0200 Subject: [PATCH 079/169] Tech ENABLE_WORLD_COORDINATE - Fixed unconstrained scaling in world coordinates for volumes Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 6 ++++++ src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 12 ++++++++++++ src/slic3r/GUI/Selection.cpp | 12 ++++++------ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index d55fd96f8..b7fc1282d 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1014,12 +1014,18 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); Vec3d scaling_factor = scale; +#if ENABLE_WORLD_COORDINATE + TransformationType transformation_type; + if (!m_world_coordinates) + transformation_type.set_local(); +#else TransformationType transformation_type(TransformationType::World_Relative_Joint); if (selection.is_single_full_instance()) { transformation_type.set_absolute(); if (! m_world_coordinates) transformation_type.set_local(); } +#endif // ENABLE_WORLD_COORDINATE if (m_uniform_scale || selection.requires_uniform_scale()) scaling_factor = scale(axis) * Vec3d::Ones(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 6cadae77d..e5eead56c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -630,11 +630,23 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) curr_scale = (m * curr_scale).cwiseAbs(); starting_scale = (m * starting_scale).cwiseAbs(); } + else if ((selection.is_single_volume() || selection.is_single_modifier()) && world_coordinates) { + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + const Transform3d m = mi * mv; + curr_scale = (m * curr_scale).cwiseAbs(); + starting_scale = (m * starting_scale).cwiseAbs(); + } curr_scale(axis) = starting_scale(axis) * ratio; if (selection.is_single_full_instance() && world_coordinates) m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); + else if ((selection.is_single_volume() || selection.is_single_modifier()) && world_coordinates) { + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + m_scale = (mv * mi * curr_scale).cwiseAbs(); + } else m_scale = curr_scale; #else diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 897d153f9..7db608a48 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -974,22 +974,22 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type else if (is_single_volume() || is_single_modifier()) v.set_volume_scaling_factor(scale); else { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); if (m_mode == Instance) { - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); + const Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + const Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); if (transformation_type.joint()) v.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); v.set_instance_scaling_factor(new_scale); } else if (m_mode == Volume) { - Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); + const Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); // extracts scaling factors from the composed transformation - Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); + const Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); if (transformation_type.joint()) { - Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); + const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); v.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); } v.set_volume_scaling_factor(new_scale); From 4aac01a2214ba379a362da215f2085f14434eaac Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 21 Oct 2021 14:05:56 +0200 Subject: [PATCH 080/169] Tech ENABLE_WORLD_COORDINATE - Fixed scaling using the Part Manipulation fields in world coordinates for volumes Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 22 ++++++++++++++++++---- src/slic3r/GUI/Selection.cpp | 4 ++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index b7fc1282d..14fc331dc 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -626,13 +626,12 @@ void ObjectManipulation::update_settings_value(const Selection& selection) const Vec3d& offset = trafo.get_offset(); const Vec3d& rotation = trafo.get_rotation(); #endif // ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET - const Vec3d& scaling_factor = trafo.get_scaling_factor(); // const Vec3d& mirror = trafo.get_mirror(); m_new_position = offset; m_new_rotation = Vec3d::Zero(); - m_new_scale = scaling_factor * 100.0; - m_new_size = volume->bounding_box().size().cwiseProduct(scaling_factor); + m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); + m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; } else { #endif // ENABLE_WORLD_COORDINATE @@ -1012,12 +1011,27 @@ void ObjectManipulation::change_size_value(int axis, double value) void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const { Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); +#if !ENABLE_WORLD_COORDINATE Vec3d scaling_factor = scale; +#endif // !ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; if (!m_world_coordinates) transformation_type.set_local(); + + bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); + Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; + + if (!uniform_scale && m_world_coordinates) { + if (selection.is_single_full_instance()) + scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); + else if (selection.is_single_volume() || selection.is_single_modifier()) { + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); + } + } #else TransformationType transformation_type(TransformationType::World_Relative_Joint); if (selection.is_single_full_instance()) { @@ -1025,10 +1039,10 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const if (! m_world_coordinates) transformation_type.set_local(); } -#endif // ENABLE_WORLD_COORDINATE if (m_uniform_scale || selection.requires_uniform_scale()) scaling_factor = scale(axis) * Vec3d::Ones(); +#endif // ENABLE_WORLD_COORDINATE selection.setup_cache(); selection.scale(scaling_factor, transformation_type); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 7db608a48..cffd05c0c 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -599,7 +599,7 @@ bool Selection::requires_uniform_scale() const else if (is_single_full_instance() && wxGetApp().obj_manipul()->get_world_coordinates()) return !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()); - return false; + return true; #else if (is_single_full_instance() || is_single_modifier() || is_single_volume()) return false; @@ -678,7 +678,7 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const const GLVolume& volume = *(*m_volumes)[i]; if (volume.is_modifier) continue; - Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix(); + Transform3d trafo = volume.get_instance_transformation().get_matrix() * volume.get_volume_transformation().get_matrix(); trafo.translation().z() += volume.get_sla_shift_z(); (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); } From ad6dcf3f1068948729324a66c9218e4090057170 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 26 Oct 2021 11:39:29 +0200 Subject: [PATCH 081/169] Tech ENABLE_WORLD_COORDINATE - Fixed constrained scaling of instances in local coordinates Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 12 ++-- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 86 ++++++++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 3 + src/slic3r/GUI/Selection.cpp | 10 ++- 4 files changed, 69 insertions(+), 42 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 14fc331dc..8a0df5552 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -761,11 +761,15 @@ void ObjectManipulation::update_reset_buttons_visibility() #endif // ENABLE_WORLD_COORDINATE double min_z = 0.0; -#if ENABLE_WORLD_COORDINATE - if (selection.is_single_full_instance() && m_world_coordinates) { -#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//#if ENABLE_WORLD_COORDINATE +// if (selection.is_single_full_instance() && m_world_coordinates) { +//#else +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (selection.is_single_full_instance()) { -#endif // ENABLE_WORLD_COORDINATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +//#endif // ENABLE_WORLD_COORDINATE +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); min_z = selection.get_scaled_instance_bounding_box().min.z(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index e5eead56c..8096ce260 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -149,17 +149,7 @@ void GLGizmoScale3D::on_start_dragging() m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center; m_starting.box = m_box; m_starting.center = m_center; - - if (m_starting.ctrl_down) { - const Vec3d center = m_starting.box.center(); - const Transform3d trafo = wxGetApp().obj_manipul()->get_world_coordinates() ? Transform3d::Identity() : m_transform; - m_starting.pivots[0] = trafo * Vec3d(m_starting.box.max.x(), center.y(), center.z()); - m_starting.pivots[1] = trafo * Vec3d(m_starting.box.min.x(), center.y(), center.z()); - m_starting.pivots[2] = trafo * Vec3d(center.x(), m_starting.box.max.y(), center.z()); - m_starting.pivots[3] = trafo * Vec3d(center.x(), m_starting.box.min.y(), center.z()); - m_starting.pivots[4] = trafo * Vec3d(center.x(), center.y(), m_starting.box.max.z()); - m_starting.pivots[5] = trafo * Vec3d(center.x(), center.y(), m_starting.box.min.z()); - } + m_starting.transform = wxGetApp().obj_manipul()->get_world_coordinates() ? Transform3d::Identity() : m_transform; #else m_starting.drag_position = m_grabbers[m_hover_id].center; m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_box : m_parent.get_selection().get_bounding_box(); @@ -202,8 +192,8 @@ void GLGizmoScale3D::on_render() m_transform = Transform3d::Identity(); #if ENABLE_WORLD_COORDINATE m_grabbers_transform = Transform3d::Identity(); - - if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { + bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); + if (selection.is_single_full_instance() && !world_coordinates) { #else // Transforms grabbers' offsets to world refefence system Transform3d offsets_transform = Transform3d::Identity(); @@ -211,25 +201,31 @@ void GLGizmoScale3D::on_render() Vec3d angles = Vec3d::Zero(); if (selection.is_single_full_instance()) { -#endif // !ENABLE_WORLD_COORDINATE - +#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); #if ENABLE_WORLD_COORDINATE - m_box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); + m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); #else m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); #endif // ENABLE_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE + m_box = m_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(true, true, false, true)); +#endif // ENABLE_WORLD_COORDINATE + // gets transform from first selected volume const GLVolume* v = selection.get_volume(*idxs.begin()); - m_transform = v->get_instance_transformation().get_matrix(); #if ENABLE_WORLD_COORDINATE - m_grabbers_transform = v->get_instance_transformation().get_matrix(true, true, false, true); + m_transform = v->get_instance_transformation().get_matrix(false, false, true); + m_grabbers_transform = v->get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_box.center()); + m_center = v->get_instance_offset(); #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 @@ -238,7 +234,7 @@ void GLGizmoScale3D::on_render() #endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE - else if ((selection.is_single_modifier() || selection.is_single_volume()) && !wxGetApp().obj_manipul()->get_world_coordinates()) { + else if ((selection.is_single_modifier() || selection.is_single_volume()) && !world_coordinates) { #else else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE @@ -272,14 +268,14 @@ void GLGizmoScale3D::on_render() Vec3d offset_x = offsets_transform * (Offset * Vec3d::UnitX()); Vec3d offset_y = offsets_transform * (Offset * Vec3d::UnitY()); Vec3d offset_z = offsets_transform * (Offset * Vec3d::UnitZ()); -#endif // ENABLE_WORLD_COORDINATE bool ctrl_down = m_dragging && m_starting.ctrl_down || !m_dragging && wxGetKeyState(WXK_CONTROL); +#endif // ENABLE_WORLD_COORDINATE // x axis #if ENABLE_WORLD_COORDINATE const Vec3d box_half_size = 0.5 * m_box.size(); - bool use_constrain = ctrl_down && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()); + bool use_constrain = wxGetKeyState(WXK_CONTROL) && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()); m_grabbers[0].center = { -(box_half_size.x() + Offset), 0.0, 0.0 }; m_grabbers[0].color = (use_constrain && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; @@ -656,10 +652,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) #endif // ENABLE_WORLD_COORDINATE if (m_starting.ctrl_down) { #if ENABLE_WORLD_COORDINATE - const double len_starting_vec = std::abs(m_starting.box.center()(axis) - m_starting.pivots[m_hover_id](axis)); - const double len_center_vec = std::abs(m_starting.center(axis) - m_starting.pivots[m_hover_id](axis)); - const double inner_ratio = len_center_vec / len_starting_vec; - double local_offset = inner_ratio * 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); + double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); #else double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis); #endif // ENABLE_WORLD_COORDINATE @@ -667,6 +660,23 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) if (m_hover_id == 2 * axis) local_offset *= -1.0; +#if ENABLE_WORLD_COORDINATE + Vec3d center_offset = m_starting.center - m_starting.transform * m_starting.box.center(); + if (selection.is_single_full_instance() && !world_coordinates) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + center_offset = m * center_offset; + } + + local_offset += (ratio - 1.0) * center_offset(axis); + + switch (axis) + { + case X: { m_offset = local_offset * Vec3d::UnitX(); break; } + case Y: { m_offset = local_offset * Vec3d::UnitY(); break; } + case Z: { m_offset = local_offset * Vec3d::UnitZ(); break; } + default: { m_offset = Vec3d::Zero(); break; } + } +#else Vec3d local_offset_vec; switch (axis) { @@ -676,9 +686,6 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) default: break; } -#if ENABLE_WORLD_COORDINATE - m_offset = local_offset_vec; -#else m_offset = m_offsets_transform * local_offset_vec; #endif // ENABLE_WORLD_COORDINATE } @@ -701,24 +708,33 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) if (m_hover_id == 6 || m_hover_id == 7) m_offset.y() *= -1.0; - m_offset += (ratio - 1.0) * (m_starting.center - m_starting.box.center()); + const Selection& selection = m_parent.get_selection(); + const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); + Vec3d center_offset = m_starting.center - m_starting.transform * m_starting.box.center(); + if (selection.is_single_full_instance() && !world_coordinates) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + center_offset = m * center_offset; + } + + m_offset += (ratio - 1.0) * center_offset; } - else { + else #endif // ENABLE_WORLD_COORDINATE m_offset = Vec3d::Zero(); -#if ENABLE_WORLD_COORDINATE - } -#endif // ENABLE_WORLD_COORDINATE - } +} } double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { double ratio = 0.0; +#if ENABLE_WORLD_COORDINATE + const Vec3d starting_vec = m_starting.drag_position - m_starting.center; +#else const Vec3d pivot = (m_starting.ctrl_down && m_hover_id < 6) ? m_starting.pivots[m_hover_id] : m_starting.box.center(); - const Vec3d starting_vec = m_starting.drag_position - pivot; +#endif // ENABLE_WORLD_COORDINATE + const double len_starting_vec = starting_vec.norm(); if (len_starting_vec != 0.0) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 0792fcc7a..16ecdbb6c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -23,9 +23,12 @@ class GLGizmoScale3D : public GLGizmoBase Vec3d drag_position{ Vec3d::Zero() }; #if ENABLE_WORLD_COORDINATE Vec3d center{ Vec3d::Zero() }; + Transform3d transform; #endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 box; +#if !ENABLE_WORLD_COORDINATE std::array pivots{ Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() }; +#endif // !ENABLE_WORLD_COORDINATE }; BoundingBoxf3 m_box; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index cffd05c0c..ad1241212 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -596,8 +596,12 @@ bool Selection::requires_uniform_scale() const #if ENABLE_WORLD_COORDINATE if (is_single_modifier() || is_single_volume()) return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); - else if (is_single_full_instance() && wxGetApp().obj_manipul()->get_world_coordinates()) - return !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()); + else if (is_single_full_instance()) { + if (wxGetApp().obj_manipul()->get_world_coordinates()) + return !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()); + else + return false; + } return true; #else @@ -606,7 +610,7 @@ bool Selection::requires_uniform_scale() const return true; #endif // ENABLE_WORLD_COORDINATE -} + } int Selection::get_object_idx() const { From 9e0bb83041e849605b0ed0be3bd342d998fc86b7 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 26 Oct 2021 11:55:11 +0200 Subject: [PATCH 082/169] Follow-up of 9ddf2ba41ccc59650c277d5a5b4604afa8702f3b - Code cleanup Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 8 -------- src/slic3r/GUI/Selection.cpp | 11 ++++------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 8a0df5552..238f26441 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -761,15 +761,7 @@ void ObjectManipulation::update_reset_buttons_visibility() #endif // ENABLE_WORLD_COORDINATE double min_z = 0.0; -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//#if ENABLE_WORLD_COORDINATE -// if (selection.is_single_full_instance() && m_world_coordinates) { -//#else -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ if (selection.is_single_full_instance()) { -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ -//#endif // ENABLE_WORLD_COORDINATE -//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); min_z = selection.get_scaled_instance_bounding_box().min.z(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index ad1241212..8a6022c3e 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -596,12 +596,9 @@ bool Selection::requires_uniform_scale() const #if ENABLE_WORLD_COORDINATE if (is_single_modifier() || is_single_volume()) return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); - else if (is_single_full_instance()) { - if (wxGetApp().obj_manipul()->get_world_coordinates()) - return !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()); - else - return false; - } + else if (is_single_full_instance()) + return wxGetApp().obj_manipul()->get_world_coordinates() ? + !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; return true; #else @@ -610,7 +607,7 @@ bool Selection::requires_uniform_scale() const return true; #endif // ENABLE_WORLD_COORDINATE - } +} int Selection::get_object_idx() const { From cf90ad699feda863782f18add5ae560269fcd57a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 26 Oct 2021 13:58:53 +0200 Subject: [PATCH 083/169] Tech ENABLE_WORLD_COORDINATE - Fixed unconstrained scaling of volumes in local coordinates Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 4 ++++ src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 238f26441..fdd83fe27 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -709,7 +709,11 @@ void ObjectManipulation::update_if_dirty() if (selection.requires_uniform_scale()) { m_lock_bnt->SetLock(true); +#if ENABLE_WORLD_COORDINATE + m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection or non axis-aligned objects/parts")); +#else m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); +#endif // ENABLE_WORLD_COORDINATE m_lock_bnt->disable(); } else { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 8096ce260..7c466137a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -240,14 +240,16 @@ void GLGizmoScale3D::on_render() #endif // ENABLE_WORLD_COORDINATE const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE - m_box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); + m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix(true, true, false, true))); #else m_box = v->bounding_box(); #endif // ENABLE_WORLD_COORDINATE - m_transform = v->world_matrix(); #if ENABLE_WORLD_COORDINATE - m_grabbers_transform = m_transform; + m_transform = v->get_volume_transformation().get_matrix(false, false, true); + m_grabbers_transform = v->get_instance_transformation().get_matrix(false, false, true) * m_transform * Geometry::assemble_transform(m_box.center()); + m_center = v->world_matrix() * m_box.center(); #else + m_transform = v->world_matrix(); angles = Geometry::extract_euler_angles(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()); @@ -766,7 +768,9 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const glsafe(::glTranslated(center.x(), center.y(), center.z())); if (!wxGetApp().obj_manipul()->get_world_coordinates()) { - const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + if (selection.is_single_volume() || selection.is_single_modifier()) + orient_matrix = orient_matrix * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } } From f944595d3d71dff9e97b3b005f32f3eb916cdd3e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 26 Oct 2021 14:49:47 +0200 Subject: [PATCH 084/169] Tech ENABLE_WORLD_COORDINATE - Fixed constrained scaling of volumes in local coordinates --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 7c466137a..39ef9e6d2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -245,8 +245,8 @@ void GLGizmoScale3D::on_render() m_box = v->bounding_box(); #endif // ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE - m_transform = v->get_volume_transformation().get_matrix(false, false, true); - m_grabbers_transform = v->get_instance_transformation().get_matrix(false, false, true) * m_transform * Geometry::assemble_transform(m_box.center()); + m_transform = v->world_matrix(); + m_grabbers_transform = v->get_instance_transformation().get_matrix(false, false, true) * v->get_volume_transformation().get_matrix(false, false, true); m_center = v->world_matrix() * m_box.center(); #else m_transform = v->world_matrix(); @@ -678,6 +678,11 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) case Z: { m_offset = local_offset * Vec3d::UnitZ(); break; } default: { m_offset = Vec3d::Zero(); break; } } + + if ((selection.is_single_volume() || selection.is_single_modifier()) && !world_coordinates) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + m_offset = m * m_offset; + } #else Vec3d local_offset_vec; switch (axis) @@ -719,6 +724,11 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) } m_offset += (ratio - 1.0) * center_offset; + + if ((selection.is_single_volume() || selection.is_single_modifier()) && !world_coordinates) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + m_offset = m * m_offset; + } } else #endif // ENABLE_WORLD_COORDINATE From 23b5860e350af70c0020ede01e1e2f20f8c85d73 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 27 Oct 2021 13:29:50 +0200 Subject: [PATCH 085/169] Tech ENABLE_WORLD_COORDINATE - Fixed visualization of sidebar hints when editing values in Object manipulation fields while using an MMU printer --- src/slic3r/GUI/GUI_ObjectList.cpp | 6 ++++++ src/slic3r/GUI/GUI_ObjectManipulation.hpp | 17 +++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 4370a2f64..477c13b31 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2545,7 +2545,13 @@ void ObjectList::part_selection_changed() Sidebar& panel = wxGetApp().sidebar(); 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() : ""; + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key, !opt_key.empty()); +#else wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event("", false); +#endif // ENABLE_WORLD_COORDINATE wxGetApp().obj_manipul() ->UpdateAndShow(update_and_show_manipulations); wxGetApp().obj_settings()->UpdateAndShow(update_and_show_settings); wxGetApp().obj_layers() ->UpdateAndShow(update_and_show_layers); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index a4f826fea..849a443f4 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -57,6 +57,10 @@ public: void set_value(const wxString& new_value); void kill_focus(ObjectManipulation *parent); +#if ENABLE_WORLD_COORDINATE + const std::string& get_full_opt_name() const { return m_full_opt_name; } +#endif // ENABLE_WORLD_COORDINATE + private: double get_value(); }; @@ -152,10 +156,15 @@ private: ScalableBitmap m_manifold_warning_bmp; wxStaticBitmap* m_fix_throught_netfab_bitmap; +#if ENABLE_WORLD_COORDINATE + // Currently focused editor (nullptr if none) + ManipulationEditor* m_focused_editor{ nullptr }; +#else #ifndef __APPLE__ // Currently focused editor (nullptr if none) ManipulationEditor* m_focused_editor {nullptr}; #endif // __APPLE__ +#endif // ENABLE_WORLD_COORDINATE wxFlexGridSizer* m_main_grid_sizer; wxFlexGridSizer* m_labels_grid_sizer; @@ -205,11 +214,19 @@ public: void sys_color_changed(); void on_change(const std::string& opt_key, int axis, double new_value); void set_focused_editor(ManipulationEditor* focused_editor) { +#if ENABLE_WORLD_COORDINATE + m_focused_editor = focused_editor; +#else #ifndef __APPLE__ m_focused_editor = focused_editor; #endif // __APPLE__ +#endif // ENABLE_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE + ManipulationEditor* get_focused_editor() { return m_focused_editor; } +#endif // ENABLE_WORLD_COORDINATE + private: void reset_settings_value(); void update_settings_value(const Selection& selection); From df552a92268e4a853c550935761740271da0675c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 2 Jun 2022 13:45:06 +0200 Subject: [PATCH 086/169] Fixed an undefined symbol when mold linker was used for linking slic3rutils_tests. --- tests/slic3rutils/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/slic3rutils/CMakeLists.txt b/tests/slic3rutils/CMakeLists.txt index 256e6efd6..e9fcf84f1 100644 --- a/tests/slic3rutils/CMakeLists.txt +++ b/tests/slic3rutils/CMakeLists.txt @@ -4,7 +4,8 @@ add_executable(${_TEST_NAME}_tests slic3r_jobs_tests.cpp ) -target_link_libraries(${_TEST_NAME}_tests test_common libslic3r_gui libslic3r) +# mold linker for successful linking needs also to link TBB library and link it before libslic3r. +target_link_libraries(${_TEST_NAME}_tests test_common TBB::tbb libslic3r_gui libslic3r) if (MSVC) target_link_libraries(${_TEST_NAME}_tests Setupapi.lib) endif () From 679f8b0111f56996afdb9282cd6d846a061b98f3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 3 Nov 2021 12:03:13 +0100 Subject: [PATCH 087/169] Tech ENABLE_WORLD_COORDINATE - Fixed center of Move and Scale gizmos Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 71 +++++---- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 5 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 201 ++++++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 7 +- 5 files changed, 158 insertions(+), 132 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 84ba510bc..11be47619 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -93,22 +93,16 @@ void GLGizmoMove3D::on_start_dragging() if (m_hover_id != -1) { m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE - const BoundingBoxf3 box = get_selection_box(); - Vec3d center; - if (wxGetApp().obj_manipul()->get_world_coordinates()) { - center = box.center(); - m_starting_drag_position = center + m_grabbers[m_hover_id].center; - } + if (wxGetApp().obj_manipul()->get_world_coordinates()) + m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; else { const Selection& selection = m_parent.get_selection(); const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); - const Transform3d trafo = Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()); - center = v.get_instance_offset() + trafo * box.center(); - m_starting_drag_position = center + trafo * m_grabbers[m_hover_id].center; + m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; } - m_starting_box_center = center; - m_starting_box_bottom_center = center; - m_starting_box_bottom_center.z() = box.min.z(); + m_starting_box_center = m_center; + m_starting_box_bottom_center = m_center; + m_starting_box_bottom_center.z() = m_bounding_box.min.z(); #else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); m_starting_drag_position = m_grabbers[m_hover_id].center; @@ -152,11 +146,11 @@ void GLGizmoMove3D::on_render() #if ENABLE_WORLD_COORDINATE glsafe(::glPushMatrix()); + calc_selection_box_and_center(); transform_to_local(m_parent.get_selection()); const Vec3d zero = Vec3d::Zero(); - BoundingBoxf3 box = get_selection_box(); - const Vec3d half_box_size = 0.5 * box.size(); + const Vec3d half_box_size = 0.5 * m_bounding_box.size(); // x axis m_grabbers[0].center = { half_box_size.x() + Offset, 0.0, 0.0 }; @@ -268,6 +262,15 @@ void GLGizmoMove3D::on_render() #endif // ENABLE_LEGACY_OPENGL_REMOVAL // draw grabbers +#if ENABLE_WORLD_COORDINATE + render_grabbers(m_bounding_box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR + for (unsigned int i = 0; i < 3; ++i) { + if (m_grabbers[i].enabled) + render_grabber_extension((Axis)i, m_bounding_box, false); + } +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#else render_grabbers(box); #if !ENABLE_GIZMO_GRABBER_REFACTOR for (unsigned int i = 0; i < 3; ++i) { @@ -275,6 +278,7 @@ void GLGizmoMove3D::on_render() render_grabber_extension((Axis)i, box, false); } #endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#endif // ENABLE_WORLD_COORDINATE } else { // draw axis @@ -311,13 +315,21 @@ void GLGizmoMove3D::on_render() shader->start_using(); shader->set_uniform("emission_factor", 0.1f); // draw grabber +#if ENABLE_WORLD_COORDINATE + const Vec3d box_size = m_bounding_box.size(); +#else const Vec3d box_size = box.size(); +#endif // ENABLE_WORLD_COORDINATE const float mean_size = (float)((box_size.x() + box_size.y() + box_size.z()) / 3.0); m_grabbers[m_hover_id].render(true, mean_size); shader->stop_using(); } #if !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE + render_grabber_extension((Axis)m_hover_id, m_bounding_box, false); +#else render_grabber_extension((Axis)m_hover_id, box, false); +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_GIZMO_GRABBER_REFACTOR } @@ -333,18 +345,21 @@ void GLGizmoMove3D::on_render_for_picking() #if ENABLE_WORLD_COORDINATE glsafe(::glPushMatrix()); transform_to_local(m_parent.get_selection()); - const BoundingBoxf3 box = get_selection_box(); + render_grabbers_for_picking(m_bounding_box); +#if !ENABLE_GIZMO_GRABBER_REFACTOR + render_grabber_extension(X, m_bounding_box, true); + render_grabber_extension(Y, m_bounding_box, true); + render_grabber_extension(Z, m_bounding_box, true); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR + glsafe(::glPopMatrix()); #else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); -#endif // ENABLE_WORLD_COORDINATE render_grabbers_for_picking(box); #if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(X, box, true); render_grabber_extension(Y, box, true); render_grabber_extension(Z, box, true); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR -#if ENABLE_WORLD_COORDINATE - glsafe(::glPopMatrix()); #endif // ENABLE_WORLD_COORDINATE } @@ -438,8 +453,7 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box #if ENABLE_WORLD_COORDINATE void GLGizmoMove3D::transform_to_local(const Selection& selection) const { - const Vec3d center = selection.get_bounding_box().center(); - glsafe(::glTranslated(center.x(), center.y(), center.z())); + glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); if (!wxGetApp().obj_manipul()->get_world_coordinates()) { const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); @@ -447,20 +461,23 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const } } -BoundingBoxf3 GLGizmoMove3D::get_selection_box() +void GLGizmoMove3D::calc_selection_box_and_center() { const Selection& selection = m_parent.get_selection(); - BoundingBoxf3 box; - if (wxGetApp().obj_manipul()->get_world_coordinates()) - box = selection.get_bounding_box(); + if (wxGetApp().obj_manipul()->get_world_coordinates()) { + m_bounding_box = selection.get_bounding_box(); + m_center = 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); - box.merge(v->transformed_convex_hull_bounding_box(v->get_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); + const GLVolume& v = *selection.get_volume(id); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } + m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); + m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); } - return box; } #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index e97d1c896..c1368cf62 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -16,6 +16,10 @@ class GLGizmoMove3D : public GLGizmoBase static const double Offset; Vec3d m_displacement{ Vec3d::Zero() }; +#if ENABLE_WORLD_COORDINATE + Vec3d m_center{ Vec3d::Zero() }; + BoundingBoxf3 m_bounding_box; +#endif // ENABLE_WORLD_COORDINATE double m_snap_step{ 1.0 }; Vec3d m_starting_drag_position{ Vec3d::Zero() }; Vec3d m_starting_box_center{ Vec3d::Zero() }; @@ -68,7 +72,7 @@ private: double calc_projection(const UpdateData& data) const; #if ENABLE_WORLD_COORDINATE void transform_to_local(const Selection& selection) const; - BoundingBoxf3 get_selection_box(); + void calc_selection_box_and_center(); #endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index b10c9bb19..d4200dbb8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -287,17 +287,18 @@ void GLGizmoRotate::on_render_for_picking() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { - m_bounding_box.reset(); if (wxGetApp().obj_manipul()->get_world_coordinates()) { m_bounding_box = selection.get_bounding_box(); m_center = 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_instance_transformation().get_matrix(true, true, false, true) * v->get_volume_transformation().get_matrix())); + m_bounding_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); } + m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); } m_radius = Offset + m_bounding_box.radius(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 39ef9e6d2..11ebf093c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -147,12 +147,12 @@ void GLGizmoScale3D::on_start_dragging() m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); #if ENABLE_WORLD_COORDINATE m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center; - m_starting.box = m_box; + m_starting.box = m_bounding_box; m_starting.center = m_center; - m_starting.transform = wxGetApp().obj_manipul()->get_world_coordinates() ? Transform3d::Identity() : m_transform; + m_starting.instance_center = m_instance_center; #else m_starting.drag_position = m_grabbers[m_hover_id].center; - m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_box : m_parent.get_selection().get_bounding_box(); + m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_bounding_box : m_parent.get_selection().get_bounding_box(); const Vec3d center = m_starting.box.center(); m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); @@ -188,13 +188,15 @@ void GLGizmoScale3D::on_render() glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); - m_box.reset(); - m_transform = Transform3d::Identity(); + m_bounding_box.reset(); #if ENABLE_WORLD_COORDINATE m_grabbers_transform = Transform3d::Identity(); + m_center = Vec3d::Zero(); + m_instance_center = Vec3d::Zero(); bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); if (selection.is_single_full_instance() && !world_coordinates) { #else + m_transform = Transform3d::Identity(); // Transforms grabbers' offsets to world refefence system Transform3d offsets_transform = Transform3d::Identity(); m_offsets_transform = Transform3d::Identity(); @@ -205,31 +207,31 @@ void GLGizmoScale3D::on_render() // 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); + const GLVolume& v = *selection.get_volume(idx); #if ENABLE_WORLD_COORDINATE - m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); #else - m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix())); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); #endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE - m_box = m_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(true, true, false, true)); + m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(true, true, false, true)); #endif // ENABLE_WORLD_COORDINATE // gets transform from first selected volume - const GLVolume* v = selection.get_volume(*idxs.begin()); + const GLVolume& v = *selection.get_volume(*idxs.begin()); #if ENABLE_WORLD_COORDINATE - m_transform = v->get_instance_transformation().get_matrix(false, false, true); - m_grabbers_transform = v->get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_box.center()); - m_center = v->get_instance_offset(); + m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center()); + m_center = selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); + m_instance_center = v.get_instance_offset(); #else - m_transform = v->get_instance_transformation().get_matrix(); + m_transform = v.get_instance_transformation().get_matrix(); // gets angles from first selected volume - angles = v->get_instance_rotation(); + 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()); + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v.get_instance_mirror()); m_offsets_transform = offsets_transform; #endif // ENABLE_WORLD_COORDINATE } @@ -238,34 +240,31 @@ void GLGizmoScale3D::on_render() #else else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE - m_box.merge(v->transformed_convex_hull_bounding_box(v->get_volume_transformation().get_matrix(true, true, false, true))); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); + Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true) * v.get_volume_transformation().get_matrix(true, false, true)); + trafo.set_offset(v.world_matrix().translation()); + m_grabbers_transform = trafo.get_matrix(); + m_center = v.world_matrix() * m_bounding_box.center(); + m_instance_center = m_center; + } + else { + m_bounding_box = selection.get_bounding_box(); + m_grabbers_transform = Geometry::assemble_transform(m_bounding_box.center()); + m_center = m_bounding_box.center(); + m_instance_center = selection.is_single_full_instance() ? selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_offset() : m_center; + } #else - m_box = v->bounding_box(); -#endif // ENABLE_WORLD_COORDINATE -#if ENABLE_WORLD_COORDINATE - m_transform = v->world_matrix(); - m_grabbers_transform = v->get_instance_transformation().get_matrix(false, false, true) * v->get_volume_transformation().get_matrix(false, false, true); - m_center = v->world_matrix() * m_box.center(); -#else - m_transform = v->world_matrix(); + m_bounding_box = v.bounding_box(); + m_transform = v.world_matrix(); angles = Geometry::extract_euler_angles(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()); -#endif // ENABLE_WORLD_COORDINATE + 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()); } -#if ENABLE_WORLD_COORDINATE - else { - m_box = selection.get_bounding_box(); - m_transform = Geometry::assemble_transform(m_box.center()); - m_grabbers_transform = m_transform; - m_center = selection.is_single_full_instance() ? selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_offset() : m_box.center(); - } -#else else - m_box = selection.get_bounding_box(); + m_bounding_box = selection.get_bounding_box(); Vec3d offset_x = offsets_transform * (Offset * Vec3d::UnitX()); Vec3d offset_y = offsets_transform * (Offset * Vec3d::UnitY()); @@ -274,52 +273,29 @@ void GLGizmoScale3D::on_render() bool ctrl_down = m_dragging && m_starting.ctrl_down || !m_dragging && wxGetKeyState(WXK_CONTROL); #endif // ENABLE_WORLD_COORDINATE - // x axis #if ENABLE_WORLD_COORDINATE - const Vec3d box_half_size = 0.5 * m_box.size(); + // 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() || selection.is_single_modifier()); m_grabbers[0].center = { -(box_half_size.x() + Offset), 0.0, 0.0 }; m_grabbers[0].color = (use_constrain && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; m_grabbers[1].center = { box_half_size.x() + Offset, 0.0, 0.0 }; m_grabbers[1].color = (use_constrain && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; -#else - const Vec3d center = m_box.center(); - - m_grabbers[0].center = m_transform * Vec3d(m_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_box.max.x(), center.y(), center.z()) + offset_x; - m_grabbers[1].color = (ctrl_down && m_hover_id == 0) ? CONSTRAINED_COLOR : AXES_COLOR[0]; -#endif // ENABLE_WORLD_COORDINATE // y axis -#if ENABLE_WORLD_COORDINATE m_grabbers[2].center = { 0.0, -(box_half_size.y() + Offset), 0.0 }; m_grabbers[2].color = (use_constrain && m_hover_id == 3) ? CONSTRAINED_COLOR : AXES_COLOR[1]; m_grabbers[3].center = { 0.0, box_half_size.y() + Offset, 0.0 }; m_grabbers[3].color = (use_constrain && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; -#else - m_grabbers[2].center = m_transform * Vec3d(center.x(), m_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_box.max.y(), center.z()) + offset_y; - m_grabbers[3].color = (ctrl_down && m_hover_id == 2) ? CONSTRAINED_COLOR : AXES_COLOR[1]; -#endif // ENABLE_WORLD_COORDINATE // z axis -#if ENABLE_WORLD_COORDINATE m_grabbers[4].center = { 0.0, 0.0, -(box_half_size.z() + Offset) }; m_grabbers[4].color = (use_constrain && m_hover_id == 5) ? CONSTRAINED_COLOR : AXES_COLOR[2]; m_grabbers[5].center = { 0.0, 0.0, box_half_size.z() + Offset }; m_grabbers[5].color = (use_constrain && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; -#else - m_grabbers[4].center = m_transform * Vec3d(center.x(), center.y(), m_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_box.max.z()) + offset_z; - m_grabbers[5].color = (ctrl_down && m_hover_id == 4) ? CONSTRAINED_COLOR : AXES_COLOR[2]; -#endif // ENABLE_WORLD_COORDINATE // uniform -#if ENABLE_WORLD_COORDINATE m_grabbers[6].center = { -(box_half_size.x() + Offset), -(box_half_size.y() + Offset), 0.0 }; m_grabbers[6].color = (use_constrain && m_hover_id == 8) ? CONSTRAINED_COLOR : m_highlight_color; m_grabbers[7].center = { box_half_size.x() + Offset, -(box_half_size.y() + Offset), 0.0 }; @@ -329,28 +305,47 @@ void GLGizmoScale3D::on_render() 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 - m_grabbers[6].center = m_transform * Vec3d(m_box.min.x(), m_box.min.y(), center.z()) - offset_x - offset_y; - m_grabbers[7].center = m_transform * Vec3d(m_box.max.x(), m_box.min.y(), center.z()) + offset_x - offset_y; - m_grabbers[8].center = m_transform * Vec3d(m_box.max.x(), m_box.max.y(), center.z()) + offset_x + offset_y; - m_grabbers[9].center = m_transform * Vec3d(m_box.min.x(), m_box.max.y(), center.z()) - offset_x + offset_y; + // x axis + const Vec3d center = m_bounding_box.center(); + + 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; } -#endif // ENABLE_WORLD_COORDINATE -#if !ENABLE_WORLD_COORDINATE // sets grabbers orientation for (int i = 0; i < 10; ++i) { m_grabbers[i].angles = angles; } -#endif // !ENABLE_WORLD_COORDINATE +#endif // ENABLE_WORLD_COORDINATE glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); #if ENABLE_WORLD_COORDINATE glsafe(::glPushMatrix()); transform_to_local(selection); - const float grabber_mean_size = (float)((m_box.size().x() + m_box.size().y() + m_box.size().z()) / 3.0); + 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); @@ -554,7 +549,7 @@ void GLGizmoScale3D::on_render_for_picking() #if ENABLE_WORLD_COORDINATE glsafe(::glPushMatrix()); transform_to_local(m_parent.get_selection()); - render_grabbers_for_picking(m_box); + render_grabbers_for_picking(m_bounding_box); glsafe(::glPopMatrix()); #else render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); @@ -623,27 +618,33 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) Vec3d starting_scale = m_starting.scale; const Selection& selection = m_parent.get_selection(); const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); - if (selection.is_single_full_instance() && world_coordinates) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); - curr_scale = (m * curr_scale).cwiseAbs(); - starting_scale = (m * starting_scale).cwiseAbs(); - } - else if ((selection.is_single_volume() || selection.is_single_modifier()) && world_coordinates) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); - const Transform3d m = mi * mv; - curr_scale = (m * curr_scale).cwiseAbs(); - starting_scale = (m * starting_scale).cwiseAbs(); + if (world_coordinates) { + if (selection.is_single_full_instance()) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); + curr_scale = (m * curr_scale).cwiseAbs(); + starting_scale = (m * starting_scale).cwiseAbs(); + } + else if (selection.is_single_volume() || selection.is_single_modifier()) { + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + const Transform3d m = mi * mv; + curr_scale = (m * curr_scale).cwiseAbs(); + starting_scale = (m * starting_scale).cwiseAbs(); + } } curr_scale(axis) = starting_scale(axis) * ratio; - if (selection.is_single_full_instance() && world_coordinates) - m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); - else if ((selection.is_single_volume() || selection.is_single_modifier()) && world_coordinates) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); - m_scale = (mv * mi * curr_scale).cwiseAbs(); + if (world_coordinates) { + if (selection.is_single_full_instance()) + m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); + else if (selection.is_single_volume() || selection.is_single_modifier()) { + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + m_scale = (mv * mi * curr_scale).cwiseAbs(); + } + else + m_scale = curr_scale; } else m_scale = curr_scale; @@ -663,7 +664,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) local_offset *= -1.0; #if ENABLE_WORLD_COORDINATE - Vec3d center_offset = m_starting.center - m_starting.transform * m_starting.box.center(); + Vec3d center_offset = m_starting.instance_center - m_starting.center; if (selection.is_single_full_instance() && !world_coordinates) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); center_offset = m * center_offset; @@ -680,8 +681,9 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) } if ((selection.is_single_volume() || selection.is_single_modifier()) && !world_coordinates) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); - m_offset = m * m_offset; + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); + m_offset = mv * mi * m_offset; } #else Vec3d local_offset_vec; @@ -717,23 +719,25 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) const Selection& selection = m_parent.get_selection(); const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); - Vec3d center_offset = m_starting.center - m_starting.transform * m_starting.box.center(); + Vec3d center_offset = m_starting.instance_center - m_starting.center; + if (selection.is_single_full_instance() && !world_coordinates) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); center_offset = m * center_offset; } - + m_offset += (ratio - 1.0) * center_offset; if ((selection.is_single_volume() || selection.is_single_modifier()) && !world_coordinates) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); - m_offset = m * m_offset; + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); + m_offset = mv * mi * m_offset; } } else #endif // ENABLE_WORLD_COORDINATE m_offset = Vec3d::Zero(); -} + } } double GLGizmoScale3D::calc_ratio(const UpdateData& data) const @@ -774,8 +778,7 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const #if ENABLE_WORLD_COORDINATE void GLGizmoScale3D::transform_to_local(const Selection& selection) const { - const Vec3d center = selection.get_bounding_box().center(); - glsafe(::glTranslated(center.x(), center.y(), center.z())); + glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); if (!wxGetApp().obj_manipul()->get_world_coordinates()) { Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 16ecdbb6c..afaf6cdcd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -23,7 +23,7 @@ class GLGizmoScale3D : public GLGizmoBase Vec3d drag_position{ Vec3d::Zero() }; #if ENABLE_WORLD_COORDINATE Vec3d center{ Vec3d::Zero() }; - Transform3d transform; + Vec3d instance_center{ Vec3d::Zero() }; #endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 box; #if !ENABLE_WORLD_COORDINATE @@ -31,12 +31,13 @@ class GLGizmoScale3D : public GLGizmoBase #endif // !ENABLE_WORLD_COORDINATE }; - BoundingBoxf3 m_box; - Transform3d m_transform; + BoundingBoxf3 m_bounding_box; #if ENABLE_WORLD_COORDINATE Transform3d m_grabbers_transform; Vec3d m_center{ Vec3d::Zero() }; + Vec3d m_instance_center{ Vec3d::Zero() }; #else + Transform3d m_transform; // Transforms grabbers offsets to the proper reference system (world for instances, instance for volumes) Transform3d m_offsets_transform; #endif // ENABLE_WORLD_COORDINATE From 79bdcefbde098bbfe9b1497bab22be57aefc26d8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 3 Nov 2021 14:50:30 +0100 Subject: [PATCH 088/169] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - 1st installment: introduction of instance reference system in part manipulation Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/GUI_ObjectList.cpp | 4 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 169 ++++++++++++++++++++-- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 32 +++- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 12 ++ src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 8 + src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 34 ++++- src/slic3r/GUI/Plater.cpp | 6 +- src/slic3r/GUI/Selection.cpp | 14 +- src/slic3r/GUI/Selection.hpp | 3 + 10 files changed, 264 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 40abad362..92212baef 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -73,6 +73,8 @@ #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them #define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (1 && ENABLE_WORLD_COORDINATE) +// Enable editing instance coordinates of volumes +#define ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 477c13b31..353577944 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3269,7 +3269,11 @@ void ObjectList::update_selections() return; sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); } +#if ENABLE_WORLD_COORDINATE + else if (selection.is_single_volume_or_modifier()) { +#else else if (selection.is_single_volume() || selection.is_any_modifier()) { +#endif // ENABLE_WORLD_COORDINATE const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin()); if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) return; diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index fdd83fe27..c0780b1fe 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -52,10 +52,17 @@ static choice_ctrl* create_word_local_combo(wxWindow *parent) temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + temp->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::World)); + temp->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Instance)); + temp->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Local)); + temp->Select((int)ObjectManipulation::ECoordinatesType::World); +#else temp->Append(_L("World coordinates")); temp->Append(_L("Local coordinates")); temp->SetSelection(0); temp->SetValue(temp->GetString(0)); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES temp->SetToolTip(_L("Select coordinate space, in which the transformation will be performed.")); return temp; @@ -81,8 +88,14 @@ void msw_rescale_word_local_combo(choice_ctrl* combo) // Set rescaled size combo->SetSize(size); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + combo->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::World)); + combo->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Instance)); + combo->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Local)); +#else combo->Append(_L("World coordinates")); combo->Append(_L("Local coordinates")); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES combo->SetValue(selection); #else @@ -101,6 +114,7 @@ static void set_font_and_background_style(wxWindow* win, const wxFont& font) static const wxString axes_color_text[] = { "#990000", "#009900", "#000099" }; static const wxString axes_color_back[] = { "#f5dcdc", "#dcf5dc", "#dcdcf5" }; + ObjectManipulation::ObjectManipulation(wxWindow* parent) : OG_Settings(parent, true) { @@ -157,7 +171,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // Add world local combobox m_word_local_combo = create_word_local_combo(parent); m_word_local_combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent& evt) { +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + this->set_coordinates_type(evt.GetString()); +#else this->set_world_coordinates(evt.GetSelection() != 1); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES }), m_word_local_combo->GetId()); // Small trick to correct layouting in different view_mode : @@ -264,7 +282,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_volume_or_modifier()) { +#else if (selection.is_single_volume() || selection.is_single_modifier()) { +#endif // ENABLE_WORLD_COORDINATE GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis)); } @@ -327,11 +349,15 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); - if (selection.is_single_volume() || selection.is_single_modifier()) { #if ENABLE_WORLD_COORDINATE - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + if (selection.is_single_volume_or_modifier()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const double min_z = get_volume_min_z(*volume); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!is_world_coordinates()) { +#else if (!m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); @@ -344,6 +370,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : change_position_value(2, m_cache.position.z() - min_z); } #else + if (selection.is_single_volume() || selection.is_single_modifier()) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (get_volume_min_z(*volume) * Vec3d::UnitZ()); @@ -356,7 +383,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : else if (selection.is_single_full_instance()) { #if ENABLE_WORLD_COORDINATE const double min_z = selection.get_scaled_instance_bounding_box().min.z(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!is_world_coordinates()) { +#else if (!m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); @@ -396,7 +427,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_volume_or_modifier()) { +#else if (selection.is_single_volume() || selection.is_single_modifier()) { +#endif // ENABLE_WORLD_COORDINATE GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); volume->set_volume_rotation(Vec3d::Zero()); } @@ -477,7 +512,19 @@ void ObjectManipulation::Show(const bool show) // Show the "World Coordinates" / "Local Coordintes" Combo in Advanced / Expert mode only. #if ENABLE_WORLD_COORDINATE const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); - bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()); + bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (selection.is_single_volume_or_modifier() && m_word_local_combo->GetCount() < 3) { + m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Instance), wxNullBitmap, 1); + m_word_local_combo->Select((int)ECoordinatesType::World); + this->set_coordinates_type(m_word_local_combo->GetStringSelection()); + } + else if (selection.is_single_full_instance() && m_word_local_combo->GetCount() > 2) { + m_word_local_combo->Delete(1); + m_word_local_combo->Select((int)ECoordinatesType::World); + this->set_coordinates_type(m_word_local_combo->GetStringSelection()); + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple; #endif // ENABLE_WORLD_COORDINATE @@ -558,8 +605,10 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_rotate_label_string = L("Rotation"); m_new_scale_label_string = L("Scale factors"); +#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (wxGetApp().get_mode() == comSimple) m_world_coordinates = true; +#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES ObjectList* obj_list = wxGetApp().obj_list(); if (selection.is_single_full_instance()) { @@ -570,14 +619,22 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // !ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. - if (m_world_coordinates && ! m_uniform_scale && +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (is_world_coordinates() && !m_uniform_scale && +#else + if (m_world_coordinates && ! m_uniform_scale && +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES ! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { // Manipulating an instance in the world coordinate system, rotation is not multiples of ninety degrees, therefore enforce uniform scaling. m_uniform_scale = true; m_lock_bnt->SetLock(true); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (is_world_coordinates()) { +#else if (m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #if ENABLE_WORLD_COORDINATE m_new_position = volume->get_instance_offset(); #endif // ENABLE_WORLD_COORDINATE @@ -613,11 +670,19 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_scale_label_string = L("Scale"); m_new_enabled = true; } +#if ENABLE_WORLD_COORDINATE + else if (selection.is_single_volume_or_modifier()) { +#else else if (selection.is_single_modifier() || selection.is_single_volume()) { +#endif // ENABLE_WORLD_COORDINATE // the selection contains a single volume const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (is_world_coordinates()) { +#else if (m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Geometry::Transformation trafo(volume->world_matrix()); #if ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET @@ -722,11 +787,13 @@ void ObjectManipulation::update_if_dirty() m_lock_bnt->enable(); } - { +#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + { int new_selection = m_world_coordinates ? 0 : 1; if (m_word_local_combo->GetSelection() != new_selection) m_word_local_combo->SetSelection(new_selection); } +#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (m_new_enabled) m_og->enable(); @@ -754,12 +821,14 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_scale = false; bool show_drop_to_bed = false; - if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); #else + if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation; Vec3d scale; #endif // ENABLE_WORLD_COORDINATE @@ -811,8 +880,16 @@ void ObjectManipulation::update_mirror_buttons_visibility() Selection& selection = canvas->get_selection(); std::array new_states = {mbHidden, mbHidden, mbHidden}; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!is_world_coordinates()) { +#else if (!m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { +#else if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { +#endif // ENABLE_WORLD_COORDINATE const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d mirror; @@ -877,6 +954,19 @@ void ObjectManipulation::update_warning_icon_state(const MeshErrorsInfo& warning m_fix_throught_netfab_bitmap->SetToolTip(tooltip); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +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"); } + default: { assert(false); break; } + } +} +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + void ObjectManipulation::reset_settings_value() { m_new_position = Vec3d::Zero(); @@ -901,7 +991,11 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.setup_cache(); #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + selection.translate(position - m_cache.position, !is_world_coordinates()); +#else selection.translate(position - m_cache.position, !m_world_coordinates); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else selection.translate(position - m_cache.position, selection.requires_local_axes()); #endif // ENABLE_WORLD_COORDINATE @@ -928,7 +1022,11 @@ void ObjectManipulation::change_rotation_value(int axis, double value) if (selection.is_single_full_instance()) transformation_type.set_independent(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!is_world_coordinates()) { +#else if (!m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES //FIXME Selection::rotate() does not process absolute rotations correctly: It does not recognize the axis index, which was changed. // transformation_type.set_absolute(); transformation_type.set_local(); @@ -987,7 +1085,11 @@ void ObjectManipulation::change_size_value(int axis, double value) const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); Vec3d ref_size = m_cache.size; +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_volume_or_modifier()) { +#else if (selection.is_single_volume() || selection.is_single_modifier()) { +#endif // ENABLE_WORLD_COORDINATE const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); 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()); @@ -997,7 +1099,11 @@ void ObjectManipulation::change_size_value(int axis, double value) ref_size = Vec3d::Ones(); } else if (selection.is_single_full_instance()) - ref_size = m_world_coordinates ? +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + ref_size = is_world_coordinates() ? +#else + ref_size = m_world_coordinates ? +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES selection.get_unscaled_instance_bounding_box().size() : wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); @@ -1017,16 +1123,24 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!is_world_coordinates()) +#else if (!m_world_coordinates) +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES transformation_type.set_local(); bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!uniform_scale && is_world_coordinates()) { +#else if (!uniform_scale && m_world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance()) scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); - else if (selection.is_single_volume() || selection.is_single_modifier()) { + else if (selection.is_single_volume_or_modifier()) { const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); @@ -1086,7 +1200,11 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double void ObjectManipulation::set_uniform_scaling(const bool new_value) { const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); - if (selection.is_single_full_instance() && m_world_coordinates && !new_value) { +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (selection.is_single_full_instance() && is_world_coordinates() && !new_value) { +#else + if (selection.is_single_full_instance() && m_world_coordinates && !new_value) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); @@ -1119,6 +1237,23 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) } #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +void ObjectManipulation::set_coordinates_type(ECoordinatesType type) +{ + if (wxGetApp().get_mode() == comSimple) + type = ECoordinatesType::World; + + if (m_coordinates_type == type) + return; + + m_coordinates_type = type; + this->UpdateAndShow(true); + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + canvas->get_gizmos_manager().update_data(); + canvas->set_as_dirty(); + canvas->request_extra_frame(); +} +#else void ObjectManipulation::set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; @@ -1135,6 +1270,7 @@ bool ObjectManipulation::get_world_coordinates() const return wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()) ? m_world_coordinates : true; } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #endif // ENABLE_WORLD_COORDINATE void ObjectManipulation::msw_rescale() @@ -1200,6 +1336,19 @@ void ObjectManipulation::sys_color_changed() m_mirror_buttons[id].first->msw_rescale(); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +void ObjectManipulation::set_coordinates_type(const wxString& type_string) +{ + ECoordinatesType type = ECoordinatesType::World; + if (type_string == coordinate_type_str(ECoordinatesType::Instance)) + type = ECoordinatesType::Instance; + else if (type_string == coordinate_type_str(ECoordinatesType::Local)) + type = ECoordinatesType::Local; + + this->set_coordinates_type(type); +} +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + static const char axes[] = { 'x', 'y', 'z' }; ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, const std::string& opt_key, diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 849a443f4..d0c38cc47 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -72,6 +72,15 @@ public: static const double in_to_mm; static const double mm_to_in; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + enum class ECoordinatesType : unsigned char + { + World, + Instance, + Local + }; +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + private: struct Cache { @@ -148,8 +157,12 @@ private: Vec3d m_new_size; bool m_new_enabled {true}; bool m_uniform_scale {true}; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + ECoordinatesType m_coordinates_type{ ECoordinatesType::World }; +#else // Does the object manipulation panel work in World or Local coordinates? bool m_world_coordinates = true; +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES LockButton* m_lock_bnt{ nullptr }; choice_ctrl* m_word_local_combo { nullptr }; @@ -191,11 +204,20 @@ public: void set_uniform_scaling(const bool uniform_scale); bool get_uniform_scaling() const { return m_uniform_scale; } - // Does the object manipulation panel work in World or Local coordinates? #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + void set_coordinates_type(ECoordinatesType type); + ECoordinatesType get_coordinates_type() const { return m_coordinates_type; } + bool is_world_coordinates() const { return m_coordinates_type == ECoordinatesType::World; } + bool is_instance_coordinates() const { return m_coordinates_type == ECoordinatesType::Instance; } + bool is_local_coordinates() const { return m_coordinates_type == ECoordinatesType::Local; } +#else + // Does the object manipulation panel work in World or Local coordinates? void set_world_coordinates(const bool world_coordinates); bool get_world_coordinates() const; +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else + // Does the object manipulation panel work in World or Local coordinates? void set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); } bool get_world_coordinates() const { return m_world_coordinates; } #endif // ENABLE_WORLD_COORDINATE @@ -227,6 +249,10 @@ public: ManipulationEditor* get_focused_editor() { return m_focused_editor; } #endif // ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + static wxString coordinate_type_str(ECoordinatesType type); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + private: void reset_settings_value(); void update_settings_value(const Selection& selection); @@ -242,6 +268,10 @@ private: void change_scale_value(int axis, double value); void change_size_value(int axis, double value); void do_scale(int axis, const Vec3d &scale) const; + +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + void set_coordinates_type(const wxString& type_string); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES }; }} diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 11be47619..64d8299f5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -93,7 +93,11 @@ void GLGizmoMove3D::on_start_dragging() if (m_hover_id != -1) { m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (wxGetApp().obj_manipul()->is_world_coordinates()) +#else if (wxGetApp().obj_manipul()->get_world_coordinates()) +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; else { const Selection& selection = m_parent.get_selection(); @@ -455,7 +459,11 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { +#else if (!wxGetApp().obj_manipul()->get_world_coordinates()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } @@ -464,7 +472,11 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const void GLGizmoMove3D::calc_selection_box_and_center() { const Selection& selection = m_parent.get_selection(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (wxGetApp().obj_manipul()->is_world_coordinates()) { +#else if (wxGetApp().obj_manipul()->get_world_coordinates()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index d4200dbb8..c74493a2f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -287,7 +287,11 @@ void GLGizmoRotate::on_render_for_picking() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (wxGetApp().obj_manipul()->is_world_coordinates()) { +#else if (wxGetApp().obj_manipul()->get_world_coordinates()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } @@ -307,7 +311,11 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_snap_fine_in_radius = m_radius; m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (wxGetApp().obj_manipul()->is_world_coordinates()) +#else if (wxGetApp().obj_manipul()->get_world_coordinates()) +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_orient_matrix = Transform3d::Identity(); else m_orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 11ebf093c..70faad820 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -46,7 +46,11 @@ std::string GLGizmoScale3D::get_tooltip() const Vec3d scale = 100.0 * Vec3d::Ones(); if (selection.is_single_full_instance()) scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor(); +#if ENABLE_WORLD_COORDINATE + else if (selection.is_single_volume_or_modifier()) +#else else if (selection.is_single_modifier() || selection.is_single_volume()) +#endif // ENABLE_WORLD_COORDINATE scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor(); if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging) @@ -193,7 +197,11 @@ void GLGizmoScale3D::on_render() m_grabbers_transform = Transform3d::Identity(); m_center = Vec3d::Zero(); m_instance_center = Vec3d::Zero(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + bool world_coordinates = wxGetApp().obj_manipul()->is_world_coordinates(); +#else bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance() && !world_coordinates) { #else m_transform = Transform3d::Identity(); @@ -236,7 +244,7 @@ void GLGizmoScale3D::on_render() #endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE - else if ((selection.is_single_modifier() || selection.is_single_volume()) && !world_coordinates) { + else if (selection.is_single_volume_or_modifier() && !world_coordinates) { #else else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE @@ -276,7 +284,7 @@ void GLGizmoScale3D::on_render() #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() || selection.is_single_modifier()); + bool use_constrain = wxGetKeyState(WXK_CONTROL) && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()); m_grabbers[0].center = { -(box_half_size.x() + Offset), 0.0, 0.0 }; m_grabbers[0].color = (use_constrain && m_hover_id == 1) ? CONSTRAINED_COLOR : AXES_COLOR[0]; @@ -617,14 +625,18 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) Vec3d curr_scale = m_scale; Vec3d starting_scale = m_starting.scale; const Selection& selection = m_parent.get_selection(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + const bool world_coordinates = wxGetApp().obj_manipul()->is_world_coordinates(); +#else const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (world_coordinates) { if (selection.is_single_full_instance()) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); curr_scale = (m * curr_scale).cwiseAbs(); starting_scale = (m * starting_scale).cwiseAbs(); } - else if (selection.is_single_volume() || selection.is_single_modifier()) { + else if (selection.is_single_volume_or_modifier()) { const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); const Transform3d m = mi * mv; @@ -638,7 +650,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) if (world_coordinates) { if (selection.is_single_full_instance()) m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); - else if (selection.is_single_volume() || selection.is_single_modifier()) { + else if (selection.is_single_volume_or_modifier()) { const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); m_scale = (mv * mi * curr_scale).cwiseAbs(); @@ -680,7 +692,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) default: { m_offset = Vec3d::Zero(); break; } } - if ((selection.is_single_volume() || selection.is_single_modifier()) && !world_coordinates) { + if (selection.is_single_volume_or_modifier() && !world_coordinates) { const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); m_offset = mv * mi * m_offset; @@ -718,7 +730,11 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) m_offset.y() *= -1.0; const Selection& selection = m_parent.get_selection(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + const bool world_coordinates = wxGetApp().obj_manipul()->is_world_coordinates(); +#else const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES Vec3d center_offset = m_starting.instance_center - m_starting.center; if (selection.is_single_full_instance() && !world_coordinates) { @@ -728,7 +744,7 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) m_offset += (ratio - 1.0) * center_offset; - if ((selection.is_single_volume() || selection.is_single_modifier()) && !world_coordinates) { + if (selection.is_single_volume_or_modifier() && !world_coordinates) { const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); m_offset = mv * mi * m_offset; @@ -780,9 +796,13 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { +#else if (!wxGetApp().obj_manipul()->get_world_coordinates()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); - if (selection.is_single_volume() || selection.is_single_modifier()) + if (selection.is_single_volume_or_modifier()) orient_matrix = orient_matrix * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 30c30d2ec..94bae6708 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4448,8 +4448,12 @@ void Plater::priv::on_right_click(RBtnEvent& evt) const bool is_some_full_instances = selection.is_single_full_instance() || selection.is_single_full_object() || selection.is_multiple_full_instance(); +#if ENABLE_WORLD_COORDINATE + const bool is_part = selection.is_single_volume_or_modifier(); +#else const bool is_part = selection.is_single_volume() || selection.is_single_modifier(); - menu = is_some_full_instances ? menus.object_menu() : +#endif // ENABLE_WORLD_COORDINATE + menu = is_some_full_instances ? menus.object_menu() : is_part ? menus.part_menu() : menus.multi_selection_menu(); } } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 8a6022c3e..286d0e87b 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -594,10 +594,14 @@ bool Selection::matches(const std::vector& volume_idxs) const bool Selection::requires_uniform_scale() const { #if ENABLE_WORLD_COORDINATE - if (is_single_modifier() || is_single_volume()) + if (is_single_volume_or_modifier()) return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); else if (is_single_full_instance()) +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + return wxGetApp().obj_manipul()->is_world_coordinates() ? +#else return wxGetApp().obj_manipul()->get_world_coordinates() ? +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; return true; @@ -972,7 +976,11 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type #endif // ENABLE_WORLD_COORDINATE } } +#if ENABLE_WORLD_COORDINATE + else if (is_single_volume_or_modifier()) +#else else if (is_single_volume() || is_single_modifier()) +#endif // ENABLE_WORLD_COORDINATE v.set_volume_scaling_factor(scale); else { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); @@ -1424,7 +1432,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #else const Vec3d& center = get_bounding_box().center(); #endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { +#else if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glTranslated(center.x(), center.y(), center.z())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 8cd6cdb6f..8cf4fa1c7 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -290,6 +290,9 @@ public: bool is_from_single_object() const; bool is_sla_compliant() const; bool is_instance_mode() const { return m_mode == Instance; } +#if ENABLE_WORLD_COORDINATE + bool is_single_volume_or_modifier() const { return is_single_volume() || is_single_modifier(); } +#endif // ENABLE_WORLD_COORDINATE bool contains_volume(unsigned int volume_idx) const { return m_list.find(volume_idx) != m_list.end(); } // returns true if the selection contains all the given indices From fdc8a51d3ceb1d512bb16390211de34e1e81ac6e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 3 Nov 2021 15:01:24 +0100 Subject: [PATCH 089/169] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Fixed orientation for sidebar hints in 3D scene for part manipulation in instance and local systems Fixed conflicts during rebase with master --- src/slic3r/GUI/Plater.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 94bae6708..da167f7d3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1514,6 +1514,11 @@ void Sidebar::update_mode() wxWindowUpdateLocker noUpdates(this); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (m_mode == comSimple) + p->object_manipulation->set_coordinates_type(ObjectManipulation::ECoordinatesType::World); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + p->object_list->get_sizer()->Show(m_mode > comSimple); p->object_list->unselect_objects(); From 4f1df273092be5c9e4f052ef954830e6b322bf58 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 3 Nov 2021 15:37:43 +0100 Subject: [PATCH 090/169] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Modified part manipulation fields to show the proper values in dependence of the selected reference system --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index c0780b1fe..66741b0c5 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -689,21 +689,33 @@ void ObjectManipulation::update_settings_value(const Selection& selection) const Vec3d offset = trafo.get_offset() - volume->get_instance_offset(); #else const Vec3d& offset = trafo.get_offset(); - const Vec3d& rotation = trafo.get_rotation(); #endif // ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET // const Vec3d& mirror = trafo.get_mirror(); m_new_position = offset; - m_new_rotation = Vec3d::Zero(); + m_new_rotation = trafo.get_rotation() * (180.0 / M_PI); m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (is_local_coordinates()) { + 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()); + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size(); + m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; +#else m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size())); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #if ENABLE_WORLD_COORDINATE } #endif // ENABLE_WORLD_COORDINATE From 5e5fdc4844c692014b701184ca94bad632a8e25a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 4 Nov 2021 10:07:11 +0100 Subject: [PATCH 091/169] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Volumes translation in all reference systems using Move gizmo and part manipulator fields Fixed conflicts during rebase with master --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GUI_Geometry.cpp | 9 +++ src/slic3r/GUI/GUI_Geometry.hpp | 72 +++++++++++++++++++++++ src/slic3r/GUI/GUI_ObjectManipulation.cpp | 12 ++-- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 12 +--- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 31 ++++++++-- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 5 +- src/slic3r/GUI/Selection.cpp | 19 ++++++ src/slic3r/GUI/Selection.hpp | 9 +++ 10 files changed, 151 insertions(+), 22 deletions(-) create mode 100644 src/slic3r/GUI/GUI_Geometry.cpp create mode 100644 src/slic3r/GUI/GUI_Geometry.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 78e73ba9a..02d134b29 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -85,6 +85,8 @@ set(SLIC3R_GUI_SOURCES GUI/GUI_App.hpp GUI/GUI_Utils.cpp GUI/GUI_Utils.hpp + GUI/GUI_Geometry.cpp + GUI/GUI_Geometry.hpp GUI/I18N.cpp GUI/I18N.hpp GUI/MainFrame.cpp diff --git a/src/slic3r/GUI/GUI_Geometry.cpp b/src/slic3r/GUI/GUI_Geometry.cpp new file mode 100644 index 000000000..b0ed0e04f --- /dev/null +++ b/src/slic3r/GUI/GUI_Geometry.cpp @@ -0,0 +1,9 @@ +#include "libslic3r/libslic3r.h" +#include "GUI_Geometry.hpp" + +namespace Slic3r { +namespace GUI { + + +} // namespace Slic3r +} // namespace GUI diff --git a/src/slic3r/GUI/GUI_Geometry.hpp b/src/slic3r/GUI/GUI_Geometry.hpp new file mode 100644 index 000000000..23dd36c39 --- /dev/null +++ b/src/slic3r/GUI/GUI_Geometry.hpp @@ -0,0 +1,72 @@ +#ifndef slic3r_GUI_Geometry_hpp_ +#define slic3r_GUI_Geometry_hpp_ + +namespace Slic3r { +namespace GUI { + +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +enum class ECoordinatesType : unsigned char +{ + World, + Instance, + Local +}; + +class TransformationType +{ +public: + enum Enum { + // Transforming in a world coordinate system + World = 0, + // Transforming in a local coordinate system + Local = 1, + // Absolute transformations, allowed in local coordinate system only. + Absolute = 0, + // Relative transformations, allowed in both local and world coordinate system. + Relative = 2, + // For group selection, the transformation is performed as if the group made a single solid body. + Joint = 0, + // For group selection, the transformation is performed on each object independently. + Independent = 4, + + World_Relative_Joint = World | Relative | Joint, + World_Relative_Independent = World | Relative | Independent, + Local_Absolute_Joint = Local | Absolute | Joint, + Local_Absolute_Independent = Local | Absolute | Independent, + Local_Relative_Joint = Local | Relative | Joint, + Local_Relative_Independent = Local | Relative | Independent, + }; + + TransformationType() : m_value(World) {} + TransformationType(Enum value) : m_value(value) {} + TransformationType& operator=(Enum value) { m_value = value; return *this; } + + Enum operator()() const { return m_value; } + bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; } + + void set_world() { this->remove(Local); } + void set_local() { this->add(Local); } + void set_absolute() { this->remove(Relative); } + void set_relative() { this->add(Relative); } + void set_joint() { this->remove(Independent); } + void set_independent() { this->add(Independent); } + + bool world() const { return !this->has(Local); } + bool local() const { return this->has(Local); } + bool absolute() const { return !this->has(Relative); } + bool relative() const { return this->has(Relative); } + bool joint() const { return !this->has(Independent); } + bool independent() const { return this->has(Independent); } + +private: + void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); } + void remove(Enum v) { m_value = Enum((unsigned int)m_value & (~(unsigned int)v)); } + + Enum m_value; +}; +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + +} // namespace Slic3r +} // namespace GUI + +#endif // slic3r_GUI_Geometry_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 66741b0c5..3b644098f 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -53,10 +53,10 @@ static choice_ctrl* create_word_local_combo(wxWindow *parent) if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - temp->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::World)); - temp->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Instance)); - temp->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Local)); - temp->Select((int)ObjectManipulation::ECoordinatesType::World); + temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World)); + temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance)); + temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local)); + temp->Select((int)ECoordinatesType::World); #else temp->Append(_L("World coordinates")); temp->Append(_L("Local coordinates")); @@ -974,7 +974,7 @@ wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type) case ECoordinatesType::World: { return _L("World coordinates"); } case ECoordinatesType::Instance: { return _L("Instance coordinates"); } case ECoordinatesType::Local: { return _L("Local coordinates"); } - default: { assert(false); break; } + default: { assert(false); return _L("Unknown"); } } } #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES @@ -1004,7 +1004,7 @@ void ObjectManipulation::change_position_value(int axis, double value) selection.setup_cache(); #if ENABLE_WORLD_COORDINATE #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - selection.translate(position - m_cache.position, !is_world_coordinates()); + selection.translate(position - m_cache.position, get_coordinates_type()); #else selection.translate(position - m_cache.position, !m_world_coordinates); #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index d0c38cc47..6a074f339 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -5,6 +5,9 @@ #include "GUI_ObjectSettings.hpp" #include "GUI_ObjectList.hpp" +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#include "GUI_Geometry.hpp" +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #include "libslic3r/Point.hpp" #include @@ -72,15 +75,6 @@ public: static const double in_to_mm; static const double mm_to_in; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - enum class ECoordinatesType : unsigned char - { - World, - Instance, - Local - }; -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - private: struct Cache { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 64d8299f5..532814576 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -94,13 +94,20 @@ void GLGizmoMove3D::on_start_dragging() m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (wxGetApp().obj_manipul()->is_world_coordinates()) + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + const Selection& selection = m_parent.get_selection(); + if (coordinates_type == ECoordinatesType::World) #else if (wxGetApp().obj_manipul()->get_world_coordinates()) #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center; + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { - const Selection& selection = m_parent.get_selection(); const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; } @@ -461,25 +468,39 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!wxGetApp().obj_manipul()->is_world_coordinates()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); + if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) + orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); + glsafe(::glMultMatrixd(orient_matrix.data())); + } #else if (!wxGetApp().obj_manipul()->get_world_coordinates()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES } void GLGizmoMove3D::calc_selection_box_and_center() { const Selection& selection = m_parent.get_selection(); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (wxGetApp().obj_manipul()->is_world_coordinates()) { + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + if (coordinates_type == ECoordinatesType::World) { #else if (wxGetApp().obj_manipul()->get_world_coordinates()) { #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); + m_center = v.world_matrix() * m_bounding_box.center(); + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { m_bounding_box.reset(); const Selection::IndicesList& ids = selection.get_volume_idxs(); @@ -490,7 +511,7 @@ void GLGizmoMove3D::calc_selection_box_and_center() m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); } -} + } #endif // ENABLE_WORLD_COORDINATE } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 70faad820..905ad4d27 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -81,7 +81,7 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) Selection &selection = m_parent.get_selection(); selection.scale(get_scale(), transformation_type); - if (mouse_event.CmdDown()) selection.translate(m_offset, true); + if (mouse_event.CmdDown()) selection.translate(m_offset, ECoordinatesType::Local); } } return use_grabbers(mouse_event); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index da167f7d3..adfee1b26 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -57,6 +57,9 @@ #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectLayers.hpp" #include "GUI_Utils.hpp" +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#include "GUI_Geometry.hpp" +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #include "GUI_Factories.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" @@ -1516,7 +1519,7 @@ void Sidebar::update_mode() #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (m_mode == comSimple) - p->object_manipulation->set_coordinates_type(ObjectManipulation::ECoordinatesType::World); + p->object_manipulation->set_coordinates_type(ECoordinatesType::World); #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES p->object_list->get_sizer()->Show(m_mode > comSimple); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 286d0e87b..7c422ecb7 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -700,7 +700,11 @@ void Selection::setup_cache() set_caches(); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +void Selection::translate(const Vec3d& displacement, ECoordinatesType type) +#else void Selection::translate(const Vec3d& displacement, bool local) +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES { if (!m_valid) return; @@ -710,8 +714,19 @@ void Selection::translate(const Vec3d& displacement, bool local) for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; if (m_mode == Volume || v.is_wipe_tower) { +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (type == ECoordinatesType::Instance) +#else if (local) +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (type == ECoordinatesType::Local) { + const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Vec3d local_displacement = (volume_data.get_volume_rotation_matrix() * volume_data.get_volume_scale_matrix() * volume_data.get_volume_mirror_matrix()) * displacement; + v.set_volume_offset(volume_data.get_volume_position() + local_displacement); + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { #if ENABLE_WORLD_COORDINATE const VolumeCache& volume_data = m_cache.volumes_data[i]; @@ -726,7 +741,11 @@ void Selection::translate(const Vec3d& displacement, bool local) else if (m_mode == Instance) { #if ENABLE_WORLD_COORDINATE if (is_from_fully_selected_instance(i)) { +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (type == ECoordinatesType::Local) { +#else if (local) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const VolumeCache& volume_data = m_cache.volumes_data[i]; const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_mirror_matrix()) * displacement; v.set_instance_offset(volume_data.get_instance_position() + world_displacement); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 8cf4fa1c7..53dc71e32 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -2,6 +2,9 @@ #define slic3r_GUI_Selection_hpp_ #include "libslic3r/Geometry.hpp" +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#include "slic3r/GUI/GUI_Geometry.hpp" +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #include "GLModel.hpp" #include @@ -24,6 +27,7 @@ using ModelObjectPtrs = std::vector; namespace GUI { +#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES class TransformationType { public: @@ -76,6 +80,7 @@ private: Enum m_value; }; +#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES class Selection { @@ -326,7 +331,11 @@ public: void setup_cache(); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + void translate(const Vec3d& displacement, ECoordinatesType type = ECoordinatesType::World); +#else void translate(const Vec3d& displacement, bool local = false); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES void rotate(const Vec3d& rotation, TransformationType transformation_type); void flattening_rotate(const Vec3d& normal); void scale(const Vec3d& scale, TransformationType transformation_type); From 5767feecabc1b41b32dcc26321bcb910ed81e443 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 4 Nov 2021 10:31:24 +0100 Subject: [PATCH 092/169] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Attempt to fix build on non-Windows OSs --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 3b644098f..ad7e6d3f9 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -89,9 +89,9 @@ void msw_rescale_word_local_combo(choice_ctrl* combo) combo->SetSize(size); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - combo->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::World)); - combo->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Instance)); - combo->Append(ObjectManipulation::coordinate_type_str(ObjectManipulation::ECoordinatesType::Local)); + combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World)); + combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance)); + combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local)); #else combo->Append(_L("World coordinates")); combo->Append(_L("Local coordinates")); @@ -515,14 +515,18 @@ 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 ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES 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); +#else m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Instance), wxNullBitmap, 1); +#endif // __linux__ m_word_local_combo->Select((int)ECoordinatesType::World); - this->set_coordinates_type(m_word_local_combo->GetStringSelection()); + 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->Select((int)ECoordinatesType::World); - this->set_coordinates_type(m_word_local_combo->GetStringSelection()); + this->set_coordinates_type(m_word_local_combo->GetString(m_word_local_combo->GetSelection())); } #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else From 65adbd5b0d3b17bc9ef15e5b7c967aff15146fe8 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 4 Nov 2021 12:46:22 +0100 Subject: [PATCH 093/169] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Volumes rotation in all reference systems using Rotate gizmo and part manipulator fields Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_Geometry.hpp | 20 +++++++++++++------ src/slic3r/GUI/GUI_ObjectManipulation.cpp | 4 ++++ src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 24 ++++++++++++++++++----- src/slic3r/GUI/Selection.cpp | 21 ++++++++++++++++---- 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GUI_Geometry.hpp b/src/slic3r/GUI/GUI_Geometry.hpp index 23dd36c39..029f01fb5 100644 --- a/src/slic3r/GUI/GUI_Geometry.hpp +++ b/src/slic3r/GUI/GUI_Geometry.hpp @@ -18,19 +18,25 @@ public: enum Enum { // Transforming in a world coordinate system World = 0, + // Transforming in a instance coordinate system + Instance = 1, // Transforming in a local coordinate system - Local = 1, + Local = 2, // Absolute transformations, allowed in local coordinate system only. Absolute = 0, // Relative transformations, allowed in both local and world coordinate system. - Relative = 2, + Relative = 4, // For group selection, the transformation is performed as if the group made a single solid body. Joint = 0, // For group selection, the transformation is performed on each object independently. - Independent = 4, + Independent = 8, World_Relative_Joint = World | Relative | Joint, World_Relative_Independent = World | Relative | Independent, + Instance_Absolute_Joint = Instance | Absolute | Joint, + Instance_Absolute_Independent = Instance | Absolute | Independent, + Instance_Relative_Joint = Instance | Relative | Joint, + Instance_Relative_Independent = Instance | Relative | Independent, Local_Absolute_Joint = Local | Absolute | Joint, Local_Absolute_Independent = Local | Absolute | Independent, Local_Relative_Joint = Local | Relative | Joint, @@ -44,14 +50,16 @@ public: Enum operator()() const { return m_value; } bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; } - void set_world() { this->remove(Local); } - void set_local() { this->add(Local); } + void set_world() { this->remove(Instance); this->remove(Local); } + void set_instance() { this->remove(Local); this->add(Instance); } + void set_local() { this->remove(Instance); this->add(Local); } void set_absolute() { this->remove(Relative); } void set_relative() { this->add(Relative); } void set_joint() { this->remove(Independent); } void set_independent() { this->add(Independent); } - bool world() const { return !this->has(Local); } + bool world() const { return !this->has(Instance) && !this->has(Local); } + bool instance() const { return this->has(Instance); } bool local() const { return this->has(Local); } bool absolute() const { return !this->has(Relative); } bool relative() const { return this->has(Relative); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index ad7e6d3f9..abb28e621 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -838,7 +838,11 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_drop_to_bed = false; #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (m_coordinates_type != ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { +#else if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index c74493a2f..1c1a65869 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -288,23 +288,32 @@ void GLGizmoRotate::on_render_for_picking() void GLGizmoRotate::init_data_from_selection(const Selection& selection) { #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (wxGetApp().obj_manipul()->is_world_coordinates()) { + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + if (coordinates_type == ECoordinatesType::World) { #else if (wxGetApp().obj_manipul()->get_world_coordinates()) { #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); + m_center = v.world_matrix() * m_bounding_box.center(); + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES 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 GLVolume& v = *selection.get_volume(id); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); } + 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; @@ -312,13 +321,18 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (wxGetApp().obj_manipul()->is_world_coordinates()) + if (coordinates_type == ECoordinatesType::World) + m_orient_matrix = Transform3d::Identity(); + else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true); + } #else if (wxGetApp().obj_manipul()->get_world_coordinates()) -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_orient_matrix = Transform3d::Identity(); else m_orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES } #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 7c422ecb7..a6b67b502 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -602,7 +602,7 @@ bool Selection::requires_uniform_scale() const #else return wxGetApp().obj_manipul()->get_world_coordinates() ? #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; + !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; return true; #else @@ -829,10 +829,11 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const Vec3d new_rotation = transformation_type.world() ? Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()) : transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix() * m); - if (rot_axis_max == 2 && transformation_type.joint()) { + const Vec3d relative_instance_offset = m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center; + if (rot_axis_max == 2 && transformation_type.joint() && !relative_instance_offset.isApprox(Vec3d::Zero())) { // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); - volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * relative_instance_offset); } #else const Vec3d new_rotation = transformation_type.world() ? @@ -856,10 +857,21 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ GLVolume &v = *(*m_volumes)[i]; if (is_single_full_instance()) rotate_instance(v, i); - else if (is_single_volume() || is_single_modifier()) { #if ENABLE_WORLD_COORDINATE + else if (is_single_volume_or_modifier()) { +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (transformation_type.local()) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + v.set_volume_rotation(Geometry::extract_euler_angles(m_cache.volumes_data[i].get_volume_rotation_matrix() * m)); + } + else if (transformation_type.instance()) { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + v.set_volume_rotation(Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix())); + } +#else if (transformation_type.local()) v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); m = m * m_cache.volumes_data[i].get_instance_rotation_matrix(); @@ -868,6 +880,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ v.set_volume_rotation(Geometry::extract_euler_angles(m)); } #else + else if (is_single_volume() || is_single_modifier()) { if (transformation_type.independent()) v.set_volume_rotation(v.get_volume_rotation() + rotation); else { From f9fb7f947d12ed76ce00a4a8bedb6d14cd056aa0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 2 Jun 2022 17:44:51 +0200 Subject: [PATCH 094/169] Revamped A* algorithm with extended test suite --- src/libslic3r/AStar.hpp | 169 +++++++++------- tests/libslic3r/test_astar.cpp | 352 ++++++++++++++++++++++++++++++++- 2 files changed, 450 insertions(+), 71 deletions(-) diff --git a/src/libslic3r/AStar.hpp b/src/libslic3r/AStar.hpp index 052d0e814..b3c8b9c5f 100644 --- a/src/libslic3r/AStar.hpp +++ b/src/libslic3r/AStar.hpp @@ -1,11 +1,11 @@ #ifndef ASTAR_HPP #define ASTAR_HPP +#include + #include "libslic3r/Point.hpp" #include "libslic3r/MutablePriorityQueue.hpp" -#include - namespace Slic3r { namespace astar { // Input interface for the Astar algorithm. Specialize this struct for a @@ -34,6 +34,8 @@ template struct TracerTraits_ // Get the estimated distance heuristic from node 'n' to the destination. // This is referred to as the h value in AStar context. // If node 'n' is the goal, this function should return a negative value. + // Note that this heuristic should be admissible (never bigger than the real + // cost) in order for Astar to work. static float goal_heuristic(const T &tracer, const Node &n) { return tracer.goal_heuristic(n); @@ -81,100 +83,133 @@ size_t unique_id(const T &tracer, const TracerNodeT &n) } // namespace astar_detail +constexpr size_t UNASSIGNED = size_t(-1); + +template +struct QNode // Queue node. Keeps track of scores g, and h +{ + TracerNodeT node; // The actual node itself + size_t queue_id; // Position in the open queue or UNASSIGNED if closed + size_t parent; // unique id of the parent or UNASSIGNED + + float g, h; + float f() const { return g + h; } + + QNode(TracerNodeT n = {}, + size_t p = UNASSIGNED, + float gval = std::numeric_limits::infinity(), + float hval = 0.f) + : node{std::move(n)}, parent{p}, queue_id{UNASSIGNED}, g{gval}, h{hval} + {} +}; + // Run the AStar algorithm on a tracer implementation. // The 'tracer' argument encapsulates the domain (grid, point cloud, etc...) // The 'source' argument is the starting node. // The 'out' argument is the output iterator into which the output nodes are -// written. -// Note that no destination node is given. The tracer's goal_heuristic() method -// should return a negative value if a node is a destination node. -template -bool search_route(const Tracer &tracer, const TracerNodeT &source, It out) +// written. For performance reasons, the order is reverse, from the destination +// to the source -- (destination included, source is not). +// The 'cached_nodes' argument is an optional associative container to hold a +// QNode entry for each visited node. Any compatible container can be used +// (like std::map or maps with different allocators, even a sufficiently large +// std::vector). +// +// Note that no destination node is given in the signature. The tracer's +// goal_heuristic() method should return a negative value if a node is a +// destination node. +template>> +bool search_route(const Tracer &tracer, + const TracerNodeT &source, + It out, + NodeMap &&cached_nodes = {}) { using namespace detail; - using Node = TracerNodeT; - enum class QueueType { Open, Closed, None }; + using Node = TracerNodeT; + using QNode = QNode; - struct QNode // Queue node. Keeps track of scores g, and h - { - Node node; // The actual node itself - QueueType qtype = QueueType::None; // Which queue holds this node - - float g = 0.f, h = 0.f; - float f() const { return g + h; } - }; - - // TODO: apply a linear memory allocator - using QMap = std::unordered_map; - - // The traversed nodes are stored here encapsulated in QNodes - QMap cached_nodes; - - struct LessPred { // Comparison functor needed by MutablePriorityQueue - QMap &m; + struct LessPred { // Comparison functor needed by the priority queue + NodeMap &m; bool operator ()(size_t node_a, size_t node_b) { - auto ait = m.find(node_a); - auto bit = m.find(node_b); - assert (ait != m.end() && bit != m.end()); - - return ait->second.f() < bit->second.f(); + return m[node_a].f() < m[node_b].f(); } }; - auto qopen = - make_mutable_priority_queue([](size_t, size_t){}, - LessPred{cached_nodes}); + auto qopen = make_mutable_priority_queue( + [&cached_nodes](size_t el, size_t qidx) { + cached_nodes[el].queue_id = qidx; + }, + LessPred{cached_nodes}); - auto qclosed = - make_mutable_priority_queue([](size_t, size_t){}, - LessPred{cached_nodes}); + QNode initial{source, /*parent = */ UNASSIGNED, /*g = */0.f}; + size_t source_id = unique_id(tracer, source); + cached_nodes[source_id] = initial; + qopen.push(source_id); - QNode initial{source, QueueType::Open}; - cached_nodes.insert({unique_id(tracer, source), initial}); - qopen.push(unique_id(tracer, source)); + size_t goal_id = goal_heuristic(tracer, source) < 0.f ? source_id : + UNASSIGNED; - bool goal_reached = false; - - while (!goal_reached && !qopen.empty()) { + while (goal_id == UNASSIGNED && !qopen.empty()) { size_t q_id = qopen.top(); qopen.pop(); - QNode q = cached_nodes.at(q_id); + QNode &q = cached_nodes[q_id]; - foreach_reachable(tracer, q.node, [&](const Node &nd) { - if (goal_reached) return goal_reached; + // This should absolutely be initialized in the cache already + assert(!std::isinf(q.g)); + + foreach_reachable(tracer, q.node, [&](const Node &succ_nd) { + if (goal_id != UNASSIGNED) + return true; + + float h = goal_heuristic(tracer, succ_nd); + float dst = trace_distance(tracer, q.node, succ_nd); + size_t succ_id = unique_id(tracer, succ_nd); + QNode qsucc_nd{succ_nd, q_id, q.g + dst, h}; - float h = goal_heuristic(tracer, nd); if (h < 0.f) { - goal_reached = true; + goal_id = succ_id; + cached_nodes[succ_id] = qsucc_nd; } else { - float dst = trace_distance(tracer, q.node, nd); - QNode qnd{nd, QueueType::None, q.g + dst, h}; - size_t qnd_id = unique_id(tracer, nd); + // If succ_id is not in cache, it gets created with g = infinity + QNode &prev_nd = cached_nodes[succ_id]; - auto it = cached_nodes.find(qnd_id); + if (qsucc_nd.g < prev_nd.g) { + // new route is better, apply it: - if (it == cached_nodes.end() || - (it->second.qtype != QueueType::None && qnd.f() < it->second.f())) { - qnd.qtype = QueueType::Open; - cached_nodes.insert_or_assign(qnd_id, qnd); - qopen.push(qnd_id); + // Save the old queue id, it would be lost after the next line + size_t queue_id = prev_nd.queue_id; + + // The cache needs to be updated either way + prev_nd = qsucc_nd; + + if (queue_id == UNASSIGNED) + // was in closed or unqueued, rescheduling + qopen.push(succ_id); + else // was in open, updating + qopen.update(queue_id); } } - return goal_reached; + return goal_id != UNASSIGNED; }); - - q.qtype = QueueType::Closed; - cached_nodes.insert_or_assign(q_id, q); - qclosed.push(q_id); - - // write the output - *out = q.node; - ++out; } - return goal_reached; + // Write the output, do not reverse. Clients can do so if they need to. + if (goal_id != UNASSIGNED) { + const QNode *q = &cached_nodes[goal_id]; + while (!std::isinf(q->g) && q->parent != UNASSIGNED) { + *out = q->node; + ++out; + q = &cached_nodes[q->parent]; + } + + if (std::isinf(q->g)) // Something went wrong + goal_id = UNASSIGNED; + } + + return goal_id != UNASSIGNED; } }} // namespace Slic3r::astar diff --git a/tests/libslic3r/test_astar.cpp b/tests/libslic3r/test_astar.cpp index f673ad9fe..6c0f7ab42 100644 --- a/tests/libslic3r/test_astar.cpp +++ b/tests/libslic3r/test_astar.cpp @@ -7,12 +7,42 @@ using namespace Slic3r; -struct PointGridTracer { +TEST_CASE("Testing basic invariants of AStar", "[AStar]") { + struct DummyTracer { + using Node = int; + + int goal = 0; + + float distance(int a, int b) const { return a - b; } + + float goal_heuristic(int n) const { return n == goal ? -1.f : 0.f; } + + size_t unique_id(int n) const { return n; } + + void foreach_reachable(int, std::function) const {} + }; + + std::vector out; + + SECTION("Output is empty when source is also the destination") { + bool found = astar::search_route(DummyTracer{}, 0, std::back_inserter(out)); + REQUIRE(out.empty()); + REQUIRE(found); + } + + SECTION("Return false when there is no route to destination") { + bool found = astar::search_route(DummyTracer{}, 1, std::back_inserter(out)); + REQUIRE(!found); + REQUIRE(out.empty()); + } +} + +struct PointGridTracer3D { using Node = size_t; const PointGrid &grid; size_t final; - PointGridTracer(const PointGrid &g, size_t goal) : + PointGridTracer3D(const PointGrid &g, size_t goal) : grid{g}, final{goal} {} template @@ -49,14 +79,328 @@ struct PointGridTracer { size_t unique_id(size_t n) const { return n; } }; +template> +bool has_duplicates(const std::vector &res, Cmp cmp = {}) +{ + auto cpy = res; + std::sort(cpy.begin(), cpy.end(), cmp); + auto it = std::unique(cpy.begin(), cpy.end()); + return it != cpy.end(); +} + TEST_CASE("astar algorithm test over 3D point grid", "[AStar]") { auto vol = BoundingBox3Base{{0.f, 0.f, 0.f}, {1.f, 1.f, 1.f}}; auto pgrid = point_grid(ex_seq, vol, {0.1f, 0.1f, 0.1f}); - PointGridTracer pgt{pgrid, pgrid.point_count() - 1}; + size_t target = pgrid.point_count() - 1; + + PointGridTracer3D pgt{pgrid, target}; std::vector out; - bool found = astar::search_route(pgt, size_t(0), std::back_inserter(out)); + bool found = astar::search_route(pgt, 0, std::back_inserter(out)); REQUIRE(found); + REQUIRE(!out.empty()); + REQUIRE(out.front() == target); + +#ifndef NDEBUG + std::cout << "Route taken: "; + for (auto it = out.rbegin(); it != out.rend(); ++it) { + std::cout << "(" << pgrid.get_coord(*it).transpose() << ") "; + } + std::cout << std::endl; +#endif + + REQUIRE(!has_duplicates(out)); // No duplicates in output +} + +enum CellValue {ON, OFF}; + +struct CellGridTracer2D_AllDirs { + using Node = Vec2i; + + static constexpr auto Cols = size_t(5); + static constexpr auto Rows = size_t(8); + static constexpr size_t GridSize = Cols * Rows; + + const std::array, Rows> &grid; + Vec2i goal; + + CellGridTracer2D_AllDirs(const std::array, Rows> &g, + const Vec2i &goal_) + : grid{g}, goal{goal_} + {} + + template + void foreach_reachable(const Vec2i &src, Fn &&fn) const + { + auto is_inside = [](const Vec2i& v) { return v.x() >= 0 && v.x() < Cols && v.y() >= 0 && v.y() < Rows; }; + if (Vec2i crd = src + Vec2i{0, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{1, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{0, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{-1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{-1, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{1, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{-1, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + } + + float distance(const Vec2i & a, const Vec2i & b) const { return (a - b).squaredNorm(); } + + float goal_heuristic(const Vec2i & n) const { return n == goal ? -1.f : (n - goal).squaredNorm(); } + + size_t unique_id(const Vec2i & n) const { return n.y() * Cols + n.x(); } +}; + +struct CellGridTracer2D_Axis { + using Node = Vec2i; + + static constexpr auto Cols = size_t(5); + static constexpr auto Rows = size_t(8); + static constexpr size_t GridSize = Cols * Rows; + + const std::array, Rows> &grid; + Vec2i goal; + + CellGridTracer2D_Axis( + const std::array, Rows> &g, + const Vec2i &goal_) + : grid{g}, goal{goal_} + {} + + template + void foreach_reachable(const Vec2i &src, Fn &&fn) const + { + auto is_inside = [](const Vec2i& v) { return v.x() >= 0 && v.x() < Cols && v.y() >= 0 && v.y() < Rows; }; + if (Vec2i crd = src + Vec2i{0, 1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{0, -1}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + if (Vec2i crd = src + Vec2i{-1, 0}; is_inside(crd) && grid[crd.y()] [crd.x()] == ON) fn(crd); + } + + float distance(const Vec2i & a, const Vec2i & b) const { return (a - b).squaredNorm(); } + + float goal_heuristic(const Vec2i &n) const + { + int manhattan_dst = std::abs(n.x() - goal.x()) + + std::abs(n.y() - goal.y()); + + return n == goal ? -1.f : manhattan_dst; + } + + size_t unique_id(const Vec2i & n) const { return n.y() * Cols + n.x(); } +}; + +using TestClasses = std::tuple< CellGridTracer2D_AllDirs, CellGridTracer2D_Axis >; + +TEMPLATE_LIST_TEST_CASE("Astar should avoid simple barrier", "[AStar]", TestClasses) { + + std::array, 8> grid = {{ + {ON , ON , ON , ON , ON}, + {ON , ON , ON , ON , ON}, + {ON , ON , ON , ON , ON}, + {ON , ON , ON , ON , ON}, + {ON , ON , ON , ON , ON}, + {ON , OFF, OFF, OFF, ON}, + {ON , ON , ON , ON , ON}, + {ON , ON , ON , ON , ON} + }}; + + Vec2i dst = {2, 0}; + TestType cgt{grid, dst}; + + std::vector out; + bool found = astar::search_route(cgt, {2, 7}, std::back_inserter(out)); + + REQUIRE(found); + REQUIRE(!out.empty()); + REQUIRE(out.front() == dst); + REQUIRE(!has_duplicates(out, [](const Vec2i &a, const Vec2i &b) { + return a.x() == b.x() ? a.y() < b.y() : a.x() < b.x(); + })); + +#ifndef NDEBUG + std::cout << "Route taken: "; + for (auto it = out.rbegin(); it != out.rend(); ++it) { + std::cout << "(" << it->transpose() << ") "; + } + std::cout << std::endl; +#endif +} + +TEMPLATE_LIST_TEST_CASE("Astar should manage to avoid arbitrary barriers", "[AStar]", TestClasses) { + + std::array, 8> grid = {{ + {ON , ON , ON , ON , ON}, + {ON , ON , ON , OFF, ON}, + {OFF, OFF, ON , OFF, ON}, + {ON , ON , ON , OFF, ON}, + {ON , OFF, ON , OFF, ON}, + {ON , OFF, ON , ON , ON}, + {ON , OFF, ON , OFF, ON}, + {ON , ON , ON , ON , ON} + }}; + + Vec2i dst = {0, 0}; + TestType cgt{grid, dst}; + + std::vector out; + bool found = astar::search_route(cgt, {0, 7}, std::back_inserter(out)); + + REQUIRE(found); + REQUIRE(!out.empty()); + REQUIRE(out.front() == dst); + REQUIRE(!has_duplicates(out, [](const Vec2i &a, const Vec2i &b) { + return a.x() == b.x() ? a.y() < b.y() : a.x() < b.x(); + })); + +#ifndef NDEBUG + std::cout << "Route taken: "; + for (auto it = out.rbegin(); it != out.rend(); ++it) { + std::cout << "(" << it->transpose() << ") "; + } + std::cout << std::endl; +#endif +} + +TEMPLATE_LIST_TEST_CASE("Astar should find the way out of a labyrinth", "[AStar]", TestClasses) { + + std::array, 8> grid = {{ + {ON , ON , ON , ON , ON }, + {ON , OFF, OFF, OFF, OFF}, + {ON , ON , ON , ON , ON }, + {OFF, OFF, OFF, OFF, ON }, + {ON , ON , ON , ON , ON }, + {ON , OFF, OFF, OFF, OFF}, + {ON , ON , ON , ON , ON }, + {OFF, OFF, OFF, OFF, ON } + }}; + + Vec2i dst = {4, 0}; + TestType cgt{grid, dst}; + + std::vector out; + bool found = astar::search_route(cgt, {4, 7}, std::back_inserter(out)); + + REQUIRE(found); + REQUIRE(!out.empty()); + REQUIRE(out.front() == dst); + REQUIRE(!has_duplicates(out, [](const Vec2i &a, const Vec2i &b) { + return a.x() == b.x() ? a.y() < b.y() : a.x() < b.x(); + })); + +#ifndef NDEBUG + std::cout << "Route taken: "; + for (auto it = out.rbegin(); it != out.rend(); ++it) { + std::cout << "(" << it->transpose() << ") "; + } + std::cout << std::endl; +#endif +} + +TEST_CASE("Zero heuristic function should result in dijsktra's algo", "[AStar]") +{ + struct GraphTracer { + using Node = size_t; + using QNode = astar::QNode; + + struct Edge + { + size_t to_id = size_t(-1); + float cost = 0.f; + bool operator <(const Edge &e) const { return to_id < e.to_id; } + }; + + struct ENode: public QNode { + std::vector edges; + + ENode(size_t node_id, std::initializer_list edgelist) + : QNode{node_id}, edges(edgelist) + {} + + ENode &operator=(const QNode &q) + { + assert(node == q.node); + g = q.g; + h = q.h; + parent = q.parent; + queue_id = q.queue_id; + + return *this; + } + }; + + // Example graph from + // https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/?ref=lbp + std::vector nodes = { + {0, {{1, 4.f}, {7, 8.f}}}, + {1, {{0, 4.f}, {2, 8.f}, {7, 11.f}}}, + {2, {{1, 8.f}, {3, 7.f}, {5, 4.f}, {8, 2.f}}}, + {3, {{2, 7.f}, {4, 9.f}, {5, 14.f}}}, + {4, {{3, 9.f}, {5, 10.f}}}, + {5, {{2, 4.f}, {3, 14.f}, {4, 10.f}, {6, 2.f}}}, + {6, {{5, 2.f}, {7, 1.f}, {8, 6.f}}}, + {7, {{0, 8.f}, {1, 11.f}, {6, 1.f}, {8, 7.f}}}, + {8, {{2, 2.f}, {6, 6.f}, {7, 7.f}}} + }; + + float distance(size_t a, size_t b) const { + float ret = std::numeric_limits::infinity(); + if (a < nodes.size()) { + auto it = std::lower_bound(nodes[a].edges.begin(), + nodes[a].edges.end(), + Edge{b, 0.f}); + + if (it != nodes[a].edges.end()) { + ret = it->cost; + } + } + + return ret; + } + + float goal_heuristic(size_t) const { return 0.f; } + + size_t unique_id(size_t n) const { return n; } + + void foreach_reachable(size_t n, std::function fn) const + { + if (n < nodes.size()) { + for (const Edge &e : nodes[n].edges) + fn(e.to_id); + } + } + } graph; + + std::vector out; + + // 'graph.nodes' is able to be a node cache (it simulates an associative container) + bool found = astar::search_route(graph, size_t(0), std::back_inserter(out), graph.nodes); + + // But should not crash or loop infinitely. + REQUIRE(!found); + + // Without a destination, there is no output. But the algorithm should halt. + REQUIRE(out.empty()); + + // Source node should have it's parent unset + REQUIRE(graph.nodes[0].parent == astar::UNASSIGNED); + + // All other nodes should have their parents set + for (size_t i = 1; i < graph.nodes.size(); ++i) + REQUIRE(graph.nodes[i].parent != astar::UNASSIGNED); + + std::array ref_distances = {0.f, 4.f, 12.f, 19.f, 21.f, + 11.f, 9.f, 8.f, 14.f}; + + // Try to trace each node back to the source node. Each of them should + // arrive to the source within less hops than the full number of nodes. + for (size_t i = 0, k = 0; i < graph.nodes.size(); ++i, k = 0) { + GraphTracer::QNode *q = &graph.nodes[i]; + REQUIRE(q->g == Approx(ref_distances[i])); + while (k++ < graph.nodes.size() && q->parent != astar::UNASSIGNED) + q = &graph.nodes[q->parent]; + + REQUIRE(q->parent == astar::UNASSIGNED); + } } From fb31bcd0f0128be24e092582583569715934388d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 11 Nov 2021 15:25:37 +0100 Subject: [PATCH 095/169] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Volumes scaling in all reference systems using Scale gizmo and part manipulator fields Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 25 +++++++++ src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 55 +++++++++++++++++--- src/slic3r/GUI/Selection.cpp | 62 +++++++++++++++++++++-- src/slic3r/GUI/Selection.hpp | 12 +++++ 5 files changed, 144 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index abb28e621..28d5953f9 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -788,10 +788,35 @@ void ObjectManipulation::update_if_dirty() update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + Selection::EUniformScaleRequiredReason reason; + if (selection.requires_uniform_scale(&reason)) { +#else if (selection.requires_uniform_scale()) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_lock_bnt->SetLock(true); #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + wxString tooltip; + if (selection.is_single_volume_or_modifier()) { + if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) + tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the instance local axes"); + else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_World) + tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the printer axes"); + } + else if (selection.is_single_full_instance()) { + if (reason == Selection::EUniformScaleRequiredReason::InstanceNotAxisAligned_World) + tooltip = _L("You cannot use non-uniform scaling mode for instances non aligned with the printer axes"); + else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) + tooltip = _L("You cannot use non-uniform scaling mode for instances containing non locally axis-aligned parts"); + } + else + tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection"); + + m_lock_bnt->SetToolTip(tooltip); +#else m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection or non axis-aligned objects/parts")); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 532814576..f024bcbe4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -93,9 +93,9 @@ void GLGizmoMove3D::on_start_dragging() if (m_hover_id != -1) { m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE + const Selection& selection = m_parent.get_selection(); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); - const Selection& selection = m_parent.get_selection(); if (coordinates_type == ECoordinatesType::World) #else if (wxGetApp().obj_manipul()->get_world_coordinates()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 905ad4d27..2f2b4b153 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -198,11 +198,11 @@ void GLGizmoScale3D::on_render() m_center = Vec3d::Zero(); m_instance_center = Vec3d::Zero(); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - bool world_coordinates = wxGetApp().obj_manipul()->is_world_coordinates(); + if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { #else bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance() && !world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else m_transform = Transform3d::Identity(); // Transforms grabbers' offsets to world refefence system @@ -244,19 +244,39 @@ void GLGizmoScale3D::on_render() #endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) { +#else else if (selection.is_single_volume_or_modifier() && !world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, false, false, true))); + Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true)); +#else m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true) * v.get_volume_transformation().get_matrix(true, false, true)); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES trafo.set_offset(v.world_matrix().translation()); m_grabbers_transform = trafo.get_matrix(); m_center = v.world_matrix() * m_bounding_box.center(); m_instance_center = m_center; } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); + Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true)); + trafo.set_offset(v.world_matrix().translation()); + m_grabbers_transform = trafo.get_matrix(); + m_center = v.world_matrix() * m_bounding_box.center(); + m_instance_center = m_center; + } +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { m_bounding_box = selection.get_bounding_box(); m_grabbers_transform = Geometry::assemble_transform(m_bounding_box.center()); @@ -626,11 +646,12 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) Vec3d starting_scale = m_starting.scale; const Selection& selection = m_parent.get_selection(); #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - const bool world_coordinates = wxGetApp().obj_manipul()->is_world_coordinates(); + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + if (coordinates_type == ECoordinatesType::World) { #else const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance()) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); curr_scale = (m * curr_scale).cwiseAbs(); @@ -647,7 +668,11 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) curr_scale(axis) = starting_scale(axis) * ratio; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (coordinates_type == ECoordinatesType::World) { +#else if (world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance()) m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); else if (selection.is_single_volume_or_modifier()) { @@ -677,7 +702,11 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) #if ENABLE_WORLD_COORDINATE Vec3d center_offset = m_starting.instance_center - m_starting.center; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (selection.is_single_full_instance() && coordinates_type != ECoordinatesType::World) { +#else if (selection.is_single_full_instance() && !world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); center_offset = m * center_offset; } @@ -692,11 +721,13 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) default: { m_offset = Vec3d::Zero(); break; } } +#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_volume_or_modifier() && !world_coordinates) { const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); m_offset = mv * mi * m_offset; } +#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else Vec3d local_offset_vec; switch (axis) @@ -730,25 +761,29 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) m_offset.y() *= -1.0; const Selection& selection = m_parent.get_selection(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - const bool world_coordinates = wxGetApp().obj_manipul()->is_world_coordinates(); -#else +#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES Vec3d center_offset = m_starting.instance_center - m_starting.center; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { +#else if (selection.is_single_full_instance() && !world_coordinates) { +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); center_offset = m * center_offset; } m_offset += (ratio - 1.0) * center_offset; +#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_volume_or_modifier() && !world_coordinates) { const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); m_offset = mv * mi * m_offset; } +#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES } else #endif // ENABLE_WORLD_COORDINATE @@ -802,7 +837,11 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const if (!wxGetApp().obj_manipul()->get_world_coordinates()) { #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) +#else if (selection.is_single_volume_or_modifier()) +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES orient_matrix = orient_matrix * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index a6b67b502..033f79869 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -591,18 +591,74 @@ bool Selection::matches(const std::vector& volume_idxs) const return count == (unsigned int)m_list.size(); } +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +bool Selection::requires_uniform_scale(EUniformScaleRequiredReason* reason) const +#else bool Selection::requires_uniform_scale() const +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES { #if ENABLE_WORLD_COORDINATE +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + ECoordinatesType coord_type = wxGetApp().obj_manipul()->get_coordinates_type(); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (is_single_volume_or_modifier()) +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + { + if (coord_type == ECoordinatesType::World) { + if (!Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation())) { + if (reason != nullptr) + *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_World; + return true; + } + } + else if (coord_type == ECoordinatesType::Instance) { + if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_volume_rotation())) { + if (reason != nullptr) + *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; + return true; + } + } + return false; + } +#else return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (is_single_full_instance()) #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - return wxGetApp().obj_manipul()->is_world_coordinates() ? + if (coord_type == ECoordinatesType::World) { + if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation())) { + if (reason != nullptr) + *reason = EUniformScaleRequiredReason::InstanceNotAxisAligned_World; + return true; + } + else { + for (unsigned int i : m_list) { + if (!Geometry::is_rotation_ninety_degrees((*m_volumes)[i]->get_volume_rotation())) { + if (reason != nullptr) + *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; + return true; + } + } + } + return false; + } + else { + for (unsigned int i : m_list) { + if (!Geometry::is_rotation_ninety_degrees((*m_volumes)[i]->get_volume_rotation())) { + if (reason != nullptr) + *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; + return true; + } + } + return false; + } + + if (reason != nullptr) + *reason = EUniformScaleRequiredReason::MultipleSelection; #else return wxGetApp().obj_manipul()->get_world_coordinates() ? + !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; return true; #else @@ -723,7 +779,7 @@ void Selection::translate(const Vec3d& displacement, bool local) #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (type == ECoordinatesType::Local) { const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d local_displacement = (volume_data.get_volume_rotation_matrix() * volume_data.get_volume_scale_matrix() * volume_data.get_volume_mirror_matrix()) * displacement; + const Vec3d local_displacement = (volume_data.get_volume_rotation_matrix() * volume_data.get_volume_mirror_matrix()) * displacement; v.set_volume_offset(volume_data.get_volume_position() + local_displacement); } #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 53dc71e32..2ac690e5e 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -307,7 +307,19 @@ public: // returns true if the selection contains all and only the given indices bool matches(const std::vector& volume_idxs) const; +#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + enum class EUniformScaleRequiredReason : unsigned char + { + NotRequired, + InstanceNotAxisAligned_World, + VolumeNotAxisAligned_World, + VolumeNotAxisAligned_Instance, + MultipleSelection, + }; + bool requires_uniform_scale(EUniformScaleRequiredReason* reason = nullptr) const; +#else bool requires_uniform_scale() const; +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES // Returns the the object id if the selection is from a single object, otherwise is -1 int get_object_idx() const; From 6e92b4fc3b5304812e691d1df63598aed2db17b0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 15 Nov 2021 13:00:12 +0100 Subject: [PATCH 096/169] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - Mirror transform in local system for volumes and a few fixes in rotation Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 4 ++++ src/slic3r/GUI/Selection.cpp | 6 ++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 28d5953f9..8a100f66c 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -926,7 +926,7 @@ void ObjectManipulation::update_mirror_buttons_visibility() std::array new_states = {mbHidden, mbHidden, mbHidden}; #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (!is_world_coordinates()) { + if (is_local_coordinates()) { #else if (!m_world_coordinates) { #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 1c1a65869..38a09976d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -327,6 +327,10 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true); } + else { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); + } #else if (wxGetApp().obj_manipul()->get_world_coordinates()) m_orient_matrix = Transform3d::Identity(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 033f79869..2ee0086ab 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -779,7 +779,7 @@ void Selection::translate(const Vec3d& displacement, bool local) #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (type == ECoordinatesType::Local) { const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d local_displacement = (volume_data.get_volume_rotation_matrix() * volume_data.get_volume_mirror_matrix()) * displacement; + const Vec3d local_displacement = volume_data.get_volume_rotation_matrix() * displacement; v.set_volume_offset(volume_data.get_volume_position() + local_displacement); } #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES @@ -799,11 +799,13 @@ void Selection::translate(const Vec3d& displacement, bool local) if (is_from_fully_selected_instance(i)) { #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (type == ECoordinatesType::Local) { + const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Vec3d world_displacement = volume_data.get_instance_rotation_matrix() * displacement; #else if (local) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const VolumeCache& volume_data = m_cache.volumes_data[i]; const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_mirror_matrix()) * displacement; +#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES v.set_instance_offset(volume_data.get_instance_position() + world_displacement); } else From cd4704e493171f2dd1066044ca1073e6d078d457 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 14 Jan 2022 14:14:16 +0100 Subject: [PATCH 097/169] Fixed warnings Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 2 -- src/slic3r/GUI/Selection.cpp | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index f024bcbe4..bed9f8d11 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -153,8 +153,6 @@ void GLGizmoMove3D::on_render() glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); - const Selection& selection = m_parent.get_selection(); - #if ENABLE_WORLD_COORDINATE glsafe(::glPushMatrix()); calc_selection_box_and_center(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 2ee0086ab..9ec69001f 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -623,7 +623,7 @@ bool Selection::requires_uniform_scale() const #else return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - else if (is_single_full_instance()) + else if (is_single_full_instance()) { #if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (coord_type == ECoordinatesType::World) { if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation())) { @@ -652,12 +652,14 @@ bool Selection::requires_uniform_scale() const } return false; } + } if (reason != nullptr) *reason = EUniformScaleRequiredReason::MultipleSelection; #else return wxGetApp().obj_manipul()->get_world_coordinates() ? - !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; + !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; + } #endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES return true; From 90e54e58218826db950924a475f9d5495ff6fe06 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 14 Feb 2022 11:06:16 +0100 Subject: [PATCH 098/169] Tech ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES merged into tech ENABLE_WORLD_COORDINATE Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 2 - src/slic3r/GUI/GUI_Geometry.hpp | 5 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 129 ++++++---------------- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 22 ++-- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 21 +--- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 13 --- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 66 +---------- src/slic3r/GUI/Plater.cpp | 8 +- src/slic3r/GUI/Selection.cpp | 100 +++++------------ src/slic3r/GUI/Selection.hpp | 16 +-- 10 files changed, 85 insertions(+), 297 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 92212baef..40abad362 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -73,8 +73,6 @@ #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them #define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (1 && ENABLE_WORLD_COORDINATE) -// Enable editing instance coordinates of volumes -#define ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/GUI_Geometry.hpp b/src/slic3r/GUI/GUI_Geometry.hpp index 029f01fb5..0d6cf7f4b 100644 --- a/src/slic3r/GUI/GUI_Geometry.hpp +++ b/src/slic3r/GUI/GUI_Geometry.hpp @@ -4,7 +4,7 @@ namespace Slic3r { namespace GUI { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE enum class ECoordinatesType : unsigned char { World, @@ -72,7 +72,8 @@ private: Enum m_value; }; -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES + +#endif // ENABLE_WORLD_COORDINATE } // namespace Slic3r } // namespace GUI diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 8a100f66c..849f4884d 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -52,7 +52,7 @@ static choice_ctrl* create_word_local_combo(wxWindow *parent) temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World)); temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance)); temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local)); @@ -62,7 +62,7 @@ static choice_ctrl* create_word_local_combo(wxWindow *parent) temp->Append(_L("Local coordinates")); temp->SetSelection(0); temp->SetValue(temp->GetString(0)); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE temp->SetToolTip(_L("Select coordinate space, in which the transformation will be performed.")); return temp; @@ -88,14 +88,14 @@ void msw_rescale_word_local_combo(choice_ctrl* combo) // Set rescaled size combo->SetSize(size); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World)); combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance)); combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local)); #else combo->Append(_L("World coordinates")); combo->Append(_L("Local coordinates")); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE combo->SetValue(selection); #else @@ -171,12 +171,12 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // Add world local combobox m_word_local_combo = create_word_local_combo(parent); m_word_local_combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent& evt) { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE this->set_coordinates_type(evt.GetString()); #else this->set_world_coordinates(evt.GetSelection() != 1); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - }), m_word_local_combo->GetId()); +#endif // ENABLE_WORLD_COORDINATE + }), m_word_local_combo->GetId()); // Small trick to correct layouting in different view_mode : // Show empty string of a same height as a m_word_local_combo, when m_word_local_combo is hidden @@ -353,11 +353,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : if (selection.is_single_volume_or_modifier()) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const double min_z = get_volume_min_z(*volume); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!is_world_coordinates()) { -#else - if (!m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); @@ -383,11 +379,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : else if (selection.is_single_full_instance()) { #if ENABLE_WORLD_COORDINATE const double min_z = selection.get_scaled_instance_bounding_box().min.z(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!is_world_coordinates()) { -#else - if (!m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); @@ -513,7 +505,6 @@ void ObjectManipulation::Show(const bool show) #if ENABLE_WORLD_COORDINATE const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES 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); @@ -528,7 +519,6 @@ void ObjectManipulation::Show(const bool show) m_word_local_combo->Select((int)ECoordinatesType::World); this->set_coordinates_type(m_word_local_combo->GetString(m_word_local_combo->GetSelection())); } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else bool show_world_local_combo = wxGetApp().plater()->canvas3D()->get_selection().is_single_full_instance() && wxGetApp().get_mode() != comSimple; #endif // ENABLE_WORLD_COORDINATE @@ -609,10 +599,10 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_rotate_label_string = L("Rotation"); m_new_scale_label_string = L("Scale factors"); -#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if !ENABLE_WORLD_COORDINATE if (wxGetApp().get_mode() == comSimple) m_world_coordinates = true; -#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // !ENABLE_WORLD_COORDINATE ObjectList* obj_list = wxGetApp().obj_list(); if (selection.is_single_full_instance()) { @@ -623,29 +613,25 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // !ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE if (is_world_coordinates() && !m_uniform_scale && #else if (m_world_coordinates && ! m_uniform_scale && -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE ! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { // Manipulating an instance in the world coordinate system, rotation is not multiples of ninety degrees, therefore enforce uniform scaling. m_uniform_scale = true; m_lock_bnt->SetLock(true); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE if (is_world_coordinates()) { -#else - if (m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES -#if ENABLE_WORLD_COORDINATE m_new_position = volume->get_instance_offset(); -#endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); -#if ENABLE_WORLD_COORDINATE m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); #else + if (m_world_coordinates) { + m_new_rotate_label_string = L("Rotate"); m_new_rotation = Vec3d::Zero(); #endif // ENABLE_WORLD_COORDINATE m_new_size = selection.get_scaled_instance_bounding_box().size(); @@ -682,11 +668,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) // the selection contains a single volume const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (is_world_coordinates()) { -#else - if (m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Geometry::Transformation trafo(volume->world_matrix()); #if ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET @@ -701,27 +683,23 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (is_local_coordinates()) { 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()); } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - m_new_size = volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix()).size(); - m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; +#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.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; + } #else m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_instance_scaling_factor().cwiseProduct(volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size())); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES -#if ENABLE_WORLD_COORDINATE - } #endif // ENABLE_WORLD_COORDINATE m_new_enabled = true; } @@ -788,15 +766,14 @@ void ObjectManipulation::update_if_dirty() update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE Selection::EUniformScaleRequiredReason reason; if (selection.requires_uniform_scale(&reason)) { #else if (selection.requires_uniform_scale()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE m_lock_bnt->SetLock(true); #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES wxString tooltip; if (selection.is_single_volume_or_modifier()) { if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) @@ -814,9 +791,6 @@ void ObjectManipulation::update_if_dirty() tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection"); m_lock_bnt->SetToolTip(tooltip); -#else - m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection or non axis-aligned objects/parts")); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); #endif // ENABLE_WORLD_COORDINATE @@ -828,13 +802,13 @@ void ObjectManipulation::update_if_dirty() m_lock_bnt->enable(); } -#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if !ENABLE_WORLD_COORDINATE { int new_selection = m_world_coordinates ? 0 : 1; if (m_word_local_combo->GetSelection() != new_selection) m_word_local_combo->SetSelection(new_selection); } -#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // !ENABLE_WORLD_COORDINATE if (m_new_enabled) m_og->enable(); @@ -863,11 +837,7 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_drop_to_bed = false; #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (m_coordinates_type != ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { -#else - if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); @@ -925,11 +895,11 @@ void ObjectManipulation::update_mirror_buttons_visibility() Selection& selection = canvas->get_selection(); std::array new_states = {mbHidden, mbHidden, mbHidden}; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE if (is_local_coordinates()) { #else if (!m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { #else @@ -999,7 +969,7 @@ void ObjectManipulation::update_warning_icon_state(const MeshErrorsInfo& warning m_fix_throught_netfab_bitmap->SetToolTip(tooltip); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type) { switch (type) @@ -1010,7 +980,7 @@ wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type) default: { assert(false); return _L("Unknown"); } } } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE void ObjectManipulation::reset_settings_value() { @@ -1036,11 +1006,7 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.setup_cache(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES selection.translate(position - m_cache.position, get_coordinates_type()); -#else - selection.translate(position - m_cache.position, !m_world_coordinates); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else selection.translate(position - m_cache.position, selection.requires_local_axes()); #endif // ENABLE_WORLD_COORDINATE @@ -1067,11 +1033,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value) if (selection.is_single_full_instance()) transformation_type.set_independent(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!is_world_coordinates()) { -#else - if (!m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES //FIXME Selection::rotate() does not process absolute rotations correctly: It does not recognize the axis index, which was changed. // transformation_type.set_absolute(); transformation_type.set_local(); @@ -1144,11 +1106,11 @@ void ObjectManipulation::change_size_value(int axis, double value) ref_size = Vec3d::Ones(); } else if (selection.is_single_full_instance()) -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE ref_size = is_world_coordinates() ? #else ref_size = m_world_coordinates ? -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE selection.get_unscaled_instance_bounding_box().size() : wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); @@ -1168,21 +1130,13 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!is_world_coordinates()) -#else - if (!m_world_coordinates) -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES transformation_type.set_local(); bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!uniform_scale && is_world_coordinates()) { -#else - if (!uniform_scale && m_world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance()) scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); else if (selection.is_single_volume_or_modifier()) { @@ -1245,11 +1199,11 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double void ObjectManipulation::set_uniform_scaling(const bool new_value) { const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance() && is_world_coordinates() && !new_value) { #else if (selection.is_single_full_instance() && m_world_coordinates && !new_value) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); @@ -1282,7 +1236,6 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) } #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES void ObjectManipulation::set_coordinates_type(ECoordinatesType type) { if (wxGetApp().get_mode() == comSimple) @@ -1298,24 +1251,6 @@ void ObjectManipulation::set_coordinates_type(ECoordinatesType type) canvas->set_as_dirty(); canvas->request_extra_frame(); } -#else -void ObjectManipulation::set_world_coordinates(const bool world_coordinates) -{ - m_world_coordinates = world_coordinates; - this->UpdateAndShow(true); - GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); - canvas->get_gizmos_manager().update_data(); - canvas->set_as_dirty(); - canvas->request_extra_frame(); -} - -bool ObjectManipulation::get_world_coordinates() const -{ - const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); - return wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier()) ? - m_world_coordinates : true; -} -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #endif // ENABLE_WORLD_COORDINATE void ObjectManipulation::msw_rescale() @@ -1381,7 +1316,7 @@ void ObjectManipulation::sys_color_changed() m_mirror_buttons[id].first->msw_rescale(); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE void ObjectManipulation::set_coordinates_type(const wxString& type_string) { ECoordinatesType type = ECoordinatesType::World; @@ -1392,7 +1327,7 @@ void ObjectManipulation::set_coordinates_type(const wxString& type_string) this->set_coordinates_type(type); } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE static const char axes[] = { 'x', 'y', 'z' }; ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 6a074f339..19d2c1808 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -5,9 +5,9 @@ #include "GUI_ObjectSettings.hpp" #include "GUI_ObjectList.hpp" -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE #include "GUI_Geometry.hpp" -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE #include "libslic3r/Point.hpp" #include @@ -151,12 +151,12 @@ private: Vec3d m_new_size; bool m_new_enabled {true}; bool m_uniform_scale {true}; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE ECoordinatesType m_coordinates_type{ ECoordinatesType::World }; #else // Does the object manipulation panel work in World or Local coordinates? bool m_world_coordinates = true; -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE LockButton* m_lock_bnt{ nullptr }; choice_ctrl* m_word_local_combo { nullptr }; @@ -199,17 +199,11 @@ public: void set_uniform_scaling(const bool uniform_scale); bool get_uniform_scaling() const { return m_uniform_scale; } #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES void set_coordinates_type(ECoordinatesType type); ECoordinatesType get_coordinates_type() const { return m_coordinates_type; } bool is_world_coordinates() const { return m_coordinates_type == ECoordinatesType::World; } bool is_instance_coordinates() const { return m_coordinates_type == ECoordinatesType::Instance; } bool is_local_coordinates() const { return m_coordinates_type == ECoordinatesType::Local; } -#else - // Does the object manipulation panel work in World or Local coordinates? - void set_world_coordinates(const bool world_coordinates); - bool get_world_coordinates() const; -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else // Does the object manipulation panel work in World or Local coordinates? void set_world_coordinates(const bool world_coordinates) { m_world_coordinates = world_coordinates; this->UpdateAndShow(true); } @@ -243,9 +237,9 @@ public: ManipulationEditor* get_focused_editor() { return m_focused_editor; } #endif // ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE static wxString coordinate_type_str(ECoordinatesType type); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE private: void reset_settings_value(); @@ -263,9 +257,9 @@ private: void change_size_value(int axis, double value); void do_scale(int axis, const Vec3d &scale) const; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE void set_coordinates_type(const wxString& type_string); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE }; }} diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index bed9f8d11..14c41ce54 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -94,19 +94,13 @@ void GLGizmoMove3D::on_start_dragging() m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE const Selection& selection = m_parent.get_selection(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (coordinates_type == ECoordinatesType::World) -#else - if (wxGetApp().obj_manipul()->get_world_coordinates()) -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center; } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; @@ -464,7 +458,6 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!wxGetApp().obj_manipul()->is_world_coordinates()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); @@ -472,33 +465,21 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } -#else - if (!wxGetApp().obj_manipul()->get_world_coordinates()) { - const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); - glsafe(::glMultMatrixd(orient_matrix.data())); - } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES } void GLGizmoMove3D::calc_selection_box_and_center() { const Selection& selection = m_parent.get_selection(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (coordinates_type == ECoordinatesType::World) { -#else - if (wxGetApp().obj_manipul()->get_world_coordinates()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); m_center = v.world_matrix() * m_bounding_box.center(); } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { m_bounding_box.reset(); const Selection::IndicesList& ids = selection.get_volume_idxs(); @@ -509,7 +490,7 @@ void GLGizmoMove3D::calc_selection_box_and_center() m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); } - } +} #endif // ENABLE_WORLD_COORDINATE } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 38a09976d..5a0c99b76 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -287,22 +287,16 @@ void GLGizmoRotate::on_render_for_picking() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (coordinates_type == ECoordinatesType::World) { -#else - if (wxGetApp().obj_manipul()->get_world_coordinates()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); m_center = v.world_matrix() * m_bounding_box.center(); } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { m_bounding_box.reset(); const Selection::IndicesList& ids = selection.get_volume_idxs(); @@ -320,7 +314,6 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_snap_fine_in_radius = m_radius; m_snap_fine_out_radius = m_snap_fine_in_radius + m_radius * ScaleLongTooth; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (coordinates_type == ECoordinatesType::World) m_orient_matrix = Transform3d::Identity(); else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { @@ -331,12 +324,6 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); } -#else - if (wxGetApp().obj_manipul()->get_world_coordinates()) - m_orient_matrix = Transform3d::Identity(); - else - m_orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES } #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 2f2b4b153..36e5b0217 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -197,12 +197,7 @@ void GLGizmoScale3D::on_render() m_grabbers_transform = Transform3d::Identity(); m_center = Vec3d::Zero(); m_instance_center = Vec3d::Zero(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { -#else - bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); - if (selection.is_single_full_instance() && !world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else m_transform = Transform3d::Identity(); // Transforms grabbers' offsets to world refefence system @@ -233,6 +228,8 @@ void GLGizmoScale3D::on_render() m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center()); m_center = selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(false, false, true, false) * 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(); @@ -241,32 +238,18 @@ void GLGizmoScale3D::on_render() // 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; -#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) { -#else - else if (selection.is_single_volume_or_modifier() && !world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES -#else else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, false, false, true))); Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true)); -#else - m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); - Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true) * v.get_volume_transformation().get_matrix(true, false, true)); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES trafo.set_offset(v.world_matrix().translation()); m_grabbers_transform = trafo.get_matrix(); m_center = v.world_matrix() * m_bounding_box.center(); m_instance_center = m_center; } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); @@ -276,7 +259,6 @@ void GLGizmoScale3D::on_render() m_center = v.world_matrix() * m_bounding_box.center(); m_instance_center = m_center; } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { m_bounding_box = selection.get_bounding_box(); m_grabbers_transform = Geometry::assemble_transform(m_bounding_box.center()); @@ -645,13 +627,8 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) Vec3d curr_scale = m_scale; Vec3d starting_scale = m_starting.scale; const Selection& selection = m_parent.get_selection(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (coordinates_type == ECoordinatesType::World) { -#else - const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); - if (world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance()) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); curr_scale = (m * curr_scale).cwiseAbs(); @@ -668,11 +645,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) curr_scale(axis) = starting_scale(axis) * ratio; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (coordinates_type == ECoordinatesType::World) { -#else - if (world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance()) m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); else if (selection.is_single_volume_or_modifier()) { @@ -702,11 +675,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) #if ENABLE_WORLD_COORDINATE Vec3d center_offset = m_starting.instance_center - m_starting.center; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance() && coordinates_type != ECoordinatesType::World) { -#else - if (selection.is_single_full_instance() && !world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); center_offset = m * center_offset; } @@ -720,14 +689,6 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) case Z: { m_offset = local_offset * Vec3d::UnitZ(); break; } default: { m_offset = Vec3d::Zero(); break; } } - -#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (selection.is_single_volume_or_modifier() && !world_coordinates) { - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); - m_offset = mv * mi * m_offset; - } -#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #else Vec3d local_offset_vec; switch (axis) @@ -761,29 +722,14 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) m_offset.y() *= -1.0; const Selection& selection = m_parent.get_selection(); -#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - const bool world_coordinates = wxGetApp().obj_manipul()->get_world_coordinates(); -#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES Vec3d center_offset = m_starting.instance_center - m_starting.center; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { -#else - if (selection.is_single_full_instance() && !world_coordinates) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); center_offset = m * center_offset; } m_offset += (ratio - 1.0) * center_offset; - -#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (selection.is_single_volume_or_modifier() && !world_coordinates) { - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor()).inverse(); - m_offset = mv * mi * m_offset; - } -#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES } else #endif // ENABLE_WORLD_COORDINATE @@ -831,17 +777,9 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (!wxGetApp().obj_manipul()->is_world_coordinates()) { -#else - if (!wxGetApp().obj_manipul()->get_world_coordinates()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) -#else - if (selection.is_single_volume_or_modifier()) -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES orient_matrix = orient_matrix * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index adfee1b26..cd9f221b4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -57,9 +57,9 @@ #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectLayers.hpp" #include "GUI_Utils.hpp" -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE #include "GUI_Geometry.hpp" -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE #include "GUI_Factories.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" @@ -1517,10 +1517,10 @@ void Sidebar::update_mode() wxWindowUpdateLocker noUpdates(this); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE if (m_mode == comSimple) p->object_manipulation->set_coordinates_type(ECoordinatesType::World); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE p->object_list->get_sizer()->Show(m_mode > comSimple); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 9ec69001f..87d651aa1 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -591,19 +591,15 @@ bool Selection::matches(const std::vector& volume_idxs) const return count == (unsigned int)m_list.size(); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE bool Selection::requires_uniform_scale(EUniformScaleRequiredReason* reason) const #else bool Selection::requires_uniform_scale() const -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE { #if ENABLE_WORLD_COORDINATE -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES ECoordinatesType coord_type = wxGetApp().obj_manipul()->get_coordinates_type(); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - if (is_single_volume_or_modifier()) -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES - { + if (is_single_volume_or_modifier()) { if (coord_type == ECoordinatesType::World) { if (!Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation())) { if (reason != nullptr) @@ -620,11 +616,7 @@ bool Selection::requires_uniform_scale() const } return false; } -#else - return !Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation()); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else if (is_single_full_instance()) { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (coord_type == ECoordinatesType::World) { if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation())) { if (reason != nullptr) @@ -656,11 +648,6 @@ bool Selection::requires_uniform_scale() const if (reason != nullptr) *reason = EUniformScaleRequiredReason::MultipleSelection; -#else - return wxGetApp().obj_manipul()->get_world_coordinates() ? - !Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation()) : false; - } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES return true; #else @@ -758,11 +745,11 @@ void Selection::setup_cache() set_caches(); } -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE void Selection::translate(const Vec3d& displacement, ECoordinatesType type) #else void Selection::translate(const Vec3d& displacement, bool local) -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE { if (!m_valid) return; @@ -772,19 +759,19 @@ void Selection::translate(const Vec3d& displacement, bool local) for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; if (m_mode == Volume || v.is_wipe_tower) { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE if (type == ECoordinatesType::Instance) #else if (local) -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE else if (type == ECoordinatesType::Local) { const VolumeCache& volume_data = m_cache.volumes_data[i]; const Vec3d local_displacement = volume_data.get_volume_rotation_matrix() * displacement; v.set_volume_offset(volume_data.get_volume_position() + local_displacement); } -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE else { #if ENABLE_WORLD_COORDINATE const VolumeCache& volume_data = m_cache.volumes_data[i]; @@ -799,15 +786,9 @@ void Selection::translate(const Vec3d& displacement, bool local) else if (m_mode == Instance) { #if ENABLE_WORLD_COORDINATE if (is_from_fully_selected_instance(i)) { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (type == ECoordinatesType::Local) { const VolumeCache& volume_data = m_cache.volumes_data[i]; const Vec3d world_displacement = volume_data.get_instance_rotation_matrix() * displacement; -#else - if (local) { - const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d world_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_mirror_matrix()) * displacement; -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES v.set_instance_offset(volume_data.get_instance_position() + world_displacement); } else @@ -919,7 +900,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ rotate_instance(v, i); #if ENABLE_WORLD_COORDINATE else if (is_single_volume_or_modifier()) { -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (transformation_type.local()) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); v.set_volume_rotation(Geometry::extract_euler_angles(m_cache.volumes_data[i].get_volume_rotation_matrix() * m)); @@ -928,10 +908,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); v.set_volume_rotation(Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix())); } -#else - if (transformation_type.local()) - v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES else { Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); m = m * m_cache.volumes_data[i].get_instance_rotation_matrix(); @@ -1524,11 +1500,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #else const Vec3d& center = get_bounding_box().center(); #endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES if (is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { -#else - if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glTranslated(center.x(), center.y(), center.z())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES @@ -1749,8 +1721,7 @@ std::vector Selection::get_unselected_volume_idxs_from(const std:: { std::vector idxs; - for (unsigned int i : volume_idxs) - { + for (unsigned int i : volume_idxs) { if (m_list.find(i) == m_list.end()) idxs.push_back(i); } @@ -1768,8 +1739,7 @@ void Selection::update_type() m_cache.content.clear(); m_type = Mixed; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { const GLVolume* volume = (*m_volumes)[i]; int obj_idx = volume->object_idx(); int inst_idx = volume->instance_idx(); @@ -1784,27 +1754,22 @@ void Selection::update_type() if (!m_valid) m_type = Invalid; - else - { + else { if (m_list.empty()) m_type = Empty; - else if (m_list.size() == 1) - { + else if (m_list.size() == 1) { const GLVolume* first = (*m_volumes)[*m_list.begin()]; if (first->is_wipe_tower) m_type = WipeTower; - else if (first->is_modifier) - { + else if (first->is_modifier) { m_type = SingleModifier; requires_disable = true; } - else - { + else { const ModelObject* model_object = m_model->objects[first->object_idx()]; unsigned int volumes_count = (unsigned int)model_object->volumes.size(); unsigned int instances_count = (unsigned int)model_object->instances.size(); - if (volumes_count * instances_count == 1) - { + if (volumes_count * instances_count == 1) { m_type = SingleFullObject; // ensures the correct mode is selected m_mode = Instance; @@ -1815,15 +1780,13 @@ void Selection::update_type() // ensures the correct mode is selected m_mode = Instance; } - else - { + else { m_type = SingleVolume; requires_disable = true; } } } - else - { + else { unsigned int sla_volumes_count = 0; // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is for (unsigned int i : m_list) { @@ -1838,25 +1801,20 @@ void Selection::update_type() unsigned int instances_count = (unsigned int)model_object->instances.size(); unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); - if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) - { + if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) { m_type = SingleFullObject; // ensures the correct mode is selected m_mode = Instance; } - else if (selected_instances_count == 1) - { - if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) - { + else if (selected_instances_count == 1) { + if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) { m_type = SingleFullInstance; // ensures the correct mode is selected m_mode = Instance; } - else - { + else { unsigned int modifiers_count = 0; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { if ((*m_volumes)[i]->is_modifier) ++modifiers_count; } @@ -1869,25 +1827,21 @@ void Selection::update_type() requires_disable = true; } } - else if ((selected_instances_count > 1) && (selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size())) - { + else if (selected_instances_count > 1 && selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) { m_type = MultipleFullInstance; // ensures the correct mode is selected m_mode = Instance; } } - else - { + else { unsigned int sels_cntr = 0; - for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) - { + for (ObjectIdxsToInstanceIdxsMap::iterator it = m_cache.content.begin(); it != m_cache.content.end(); ++it) { const ModelObject* model_object = m_model->objects[it->first]; unsigned int volumes_count = (unsigned int)model_object->volumes.size(); unsigned int instances_count = (unsigned int)model_object->instances.size(); sels_cntr += volumes_count * instances_count; } - if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) - { + if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) { m_type = MultipleFullObject; // ensures the correct mode is selected m_mode = Instance; diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 2ac690e5e..9d036d3c6 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -2,9 +2,9 @@ #define slic3r_GUI_Selection_hpp_ #include "libslic3r/Geometry.hpp" -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE #include "slic3r/GUI/GUI_Geometry.hpp" -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE #include "GLModel.hpp" #include @@ -27,7 +27,7 @@ using ModelObjectPtrs = std::vector; namespace GUI { -#if !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if !ENABLE_WORLD_COORDINATE class TransformationType { public: @@ -80,7 +80,7 @@ private: Enum m_value; }; -#endif // !ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // !ENABLE_WORLD_COORDINATE class Selection { @@ -307,7 +307,7 @@ public: // returns true if the selection contains all and only the given indices bool matches(const std::vector& volume_idxs) const; -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE enum class EUniformScaleRequiredReason : unsigned char { NotRequired, @@ -319,7 +319,7 @@ public: bool requires_uniform_scale(EUniformScaleRequiredReason* reason = nullptr) const; #else bool requires_uniform_scale() const; -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE // Returns the the object id if the selection is from a single object, otherwise is -1 int get_object_idx() const; @@ -343,11 +343,11 @@ public: void setup_cache(); -#if ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#if ENABLE_WORLD_COORDINATE void translate(const Vec3d& displacement, ECoordinatesType type = ECoordinatesType::World); #else void translate(const Vec3d& displacement, bool local = false); -#endif // ENABLE_INSTANCE_COORDINATES_FOR_VOLUMES +#endif // ENABLE_WORLD_COORDINATE void rotate(const Vec3d& rotation, TransformationType transformation_type); void flattening_rotate(const Vec3d& normal); void scale(const Vec3d& scale, TransformationType transformation_type); From c29bb039a6e11a43dbe793e52dcb13b70acf3a64 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 14 Feb 2022 12:06:11 +0100 Subject: [PATCH 099/169] Tech ENABLE_WORLD_COORDINATE - Revisited rotation of single instance --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 24 +++++++++-------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 849f4884d..935264d69 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -420,17 +420,14 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : Selection& selection = canvas->get_selection(); #if ENABLE_WORLD_COORDINATE - if (selection.is_single_volume_or_modifier()) { + if (selection.is_single_volume_or_modifier()) #else - if (selection.is_single_volume() || selection.is_single_modifier()) { + if (selection.is_single_volume() || selection.is_single_modifier()) #endif // ENABLE_WORLD_COORDINATE - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); - volume->set_volume_rotation(Vec3d::Zero()); - } + const_cast(selection.get_volume(*selection.get_volume_idxs().begin()))->set_volume_rotation(Vec3d::Zero()); else if (selection.is_single_full_instance()) { for (unsigned int idx : selection.get_volume_idxs()) { - GLVolume* volume = const_cast(selection.get_volume(idx)); - volume->set_instance_rotation(Vec3d::Zero()); + const_cast(selection.get_volume(idx))->set_instance_rotation(Vec3d::Zero()); } } else @@ -628,22 +625,20 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (is_world_coordinates()) { m_new_position = volume->get_instance_offset(); m_new_rotate_label_string = L("Rotate"); - m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); #else if (m_world_coordinates) { m_new_rotate_label_string = L("Rotate"); - m_new_rotation = Vec3d::Zero(); #endif // ENABLE_WORLD_COORDINATE + m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; } else { #if ENABLE_WORLD_COORDINATE + m_new_move_label_string = L("Translate"); m_new_position = Vec3d::Zero(); - m_new_rotation = Vec3d::Zero(); -#else - m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); #endif // ENABLE_WORLD_COORDINATE + 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()); m_new_scale = volume->get_instance_scaling_factor() * 100.0; } @@ -837,7 +832,7 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_drop_to_bed = false; #if ENABLE_WORLD_COORDINATE - if (m_coordinates_type != ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { + if (m_coordinates_type == ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); @@ -1034,9 +1029,8 @@ void ObjectManipulation::change_rotation_value(int axis, double value) transformation_type.set_independent(); if (!is_world_coordinates()) { - //FIXME Selection::rotate() does not process absolute rotations correctly: It does not recognize the axis index, which was changed. - // transformation_type.set_absolute(); transformation_type.set_local(); + transformation_type.set_absolute(); } #else if (selection.is_single_full_instance() || selection.requires_local_axes()) From d9ed45be56e2ef5b9836817e98ef7c074e0568b9 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 3 Jun 2022 10:08:11 +0200 Subject: [PATCH 100/169] Apply remarks from code review with additional cosmethics --- src/libslic3r/AStar.hpp | 40 +++++++++++++++++----------------- tests/libslic3r/test_astar.cpp | 8 +++---- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/libslic3r/AStar.hpp b/src/libslic3r/AStar.hpp index b3c8b9c5f..4a652a107 100644 --- a/src/libslic3r/AStar.hpp +++ b/src/libslic3r/AStar.hpp @@ -1,6 +1,7 @@ #ifndef ASTAR_HPP #define ASTAR_HPP +#include // std::isinf() is here #include #include "libslic3r/Point.hpp" @@ -83,23 +84,23 @@ size_t unique_id(const T &tracer, const TracerNodeT &n) } // namespace astar_detail -constexpr size_t UNASSIGNED = size_t(-1); +constexpr size_t Unassigned = size_t(-1); template struct QNode // Queue node. Keeps track of scores g, and h { - TracerNodeT node; // The actual node itself - size_t queue_id; // Position in the open queue or UNASSIGNED if closed - size_t parent; // unique id of the parent or UNASSIGNED + TracerNodeT node; // The actual node itself + size_t queue_id; // Position in the open queue or Unassigned if closed + size_t parent; // unique id of the parent or Unassigned - float g, h; + float g, h; float f() const { return g + h; } - QNode(TracerNodeT n = {}, - size_t p = UNASSIGNED, + QNode(TracerNodeT n = {}, + size_t p = Unassigned, float gval = std::numeric_limits::infinity(), float hval = 0.f) - : node{std::move(n)}, parent{p}, queue_id{UNASSIGNED}, g{gval}, h{hval} + : node{std::move(n)}, parent{p}, queue_id{Unassigned}, g{gval}, h{hval} {} }; @@ -143,15 +144,15 @@ bool search_route(const Tracer &tracer, }, LessPred{cached_nodes}); - QNode initial{source, /*parent = */ UNASSIGNED, /*g = */0.f}; + QNode initial{source, /*parent = */ Unassigned, /*g = */0.f}; size_t source_id = unique_id(tracer, source); cached_nodes[source_id] = initial; qopen.push(source_id); size_t goal_id = goal_heuristic(tracer, source) < 0.f ? source_id : - UNASSIGNED; + Unassigned; - while (goal_id == UNASSIGNED && !qopen.empty()) { + while (goal_id == Unassigned && !qopen.empty()) { size_t q_id = qopen.top(); qopen.pop(); QNode &q = cached_nodes[q_id]; @@ -160,7 +161,7 @@ bool search_route(const Tracer &tracer, assert(!std::isinf(q.g)); foreach_reachable(tracer, q.node, [&](const Node &succ_nd) { - if (goal_id != UNASSIGNED) + if (goal_id != Unassigned) return true; float h = goal_heuristic(tracer, succ_nd); @@ -184,7 +185,7 @@ bool search_route(const Tracer &tracer, // The cache needs to be updated either way prev_nd = qsucc_nd; - if (queue_id == UNASSIGNED) + if (queue_id == decltype(qopen)::invalid_id()) // was in closed or unqueued, rescheduling qopen.push(succ_id); else // was in open, updating @@ -192,24 +193,23 @@ bool search_route(const Tracer &tracer, } } - return goal_id != UNASSIGNED; + return goal_id != Unassigned; }); } // Write the output, do not reverse. Clients can do so if they need to. - if (goal_id != UNASSIGNED) { + if (goal_id != Unassigned) { const QNode *q = &cached_nodes[goal_id]; - while (!std::isinf(q->g) && q->parent != UNASSIGNED) { + while (q->parent != Unassigned) { + assert(!std::isinf(q->g)); // Uninitialized nodes are NOT allowed + *out = q->node; ++out; q = &cached_nodes[q->parent]; } - - if (std::isinf(q->g)) // Something went wrong - goal_id = UNASSIGNED; } - return goal_id != UNASSIGNED; + return goal_id != Unassigned; } }} // namespace Slic3r::astar diff --git a/tests/libslic3r/test_astar.cpp b/tests/libslic3r/test_astar.cpp index 6c0f7ab42..867f5be47 100644 --- a/tests/libslic3r/test_astar.cpp +++ b/tests/libslic3r/test_astar.cpp @@ -384,11 +384,11 @@ TEST_CASE("Zero heuristic function should result in dijsktra's algo", "[AStar]") REQUIRE(out.empty()); // Source node should have it's parent unset - REQUIRE(graph.nodes[0].parent == astar::UNASSIGNED); + REQUIRE(graph.nodes[0].parent == astar::Unassigned); // All other nodes should have their parents set for (size_t i = 1; i < graph.nodes.size(); ++i) - REQUIRE(graph.nodes[i].parent != astar::UNASSIGNED); + REQUIRE(graph.nodes[i].parent != astar::Unassigned); std::array ref_distances = {0.f, 4.f, 12.f, 19.f, 21.f, 11.f, 9.f, 8.f, 14.f}; @@ -398,9 +398,9 @@ TEST_CASE("Zero heuristic function should result in dijsktra's algo", "[AStar]") for (size_t i = 0, k = 0; i < graph.nodes.size(); ++i, k = 0) { GraphTracer::QNode *q = &graph.nodes[i]; REQUIRE(q->g == Approx(ref_distances[i])); - while (k++ < graph.nodes.size() && q->parent != astar::UNASSIGNED) + while (k++ < graph.nodes.size() && q->parent != astar::Unassigned) q = &graph.nodes[q->parent]; - REQUIRE(q->parent == astar::UNASSIGNED); + REQUIRE(q->parent == astar::Unassigned); } } From 1d3c8ac5b4eec299786e3b4bb87ff03593d32d69 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 3 Jun 2022 10:17:05 +0200 Subject: [PATCH 101/169] Further refactor Reduce code size for astar --- src/libslic3r/AStar.hpp | 53 +++++++++-------------------------------- 1 file changed, 11 insertions(+), 42 deletions(-) diff --git a/src/libslic3r/AStar.hpp b/src/libslic3r/AStar.hpp index 4a652a107..61c82157b 100644 --- a/src/libslic3r/AStar.hpp +++ b/src/libslic3r/AStar.hpp @@ -53,37 +53,6 @@ template struct TracerTraits_ template using TracerNodeT = typename TracerTraits_>::Node; -namespace detail { -// Helper functions dispatching calls through the TracerTraits_ interface - -template using TracerTraits = TracerTraits_>; - -template -void foreach_reachable(const T &tracer, const TracerNodeT &from, Fn &&fn) -{ - TracerTraits::foreach_reachable(tracer, from, fn); -} - -template -float trace_distance(const T &tracer, const TracerNodeT &a, const TracerNodeT &b) -{ - return TracerTraits::distance(tracer, a, b); -} - -template -float goal_heuristic(const T &tracer, const TracerNodeT &n) -{ - return TracerTraits::goal_heuristic(tracer, n); -} - -template -size_t unique_id(const T &tracer, const TracerNodeT &n) -{ - return TracerTraits::unique_id(tracer, n); -} - -} // namespace astar_detail - constexpr size_t Unassigned = size_t(-1); template @@ -126,10 +95,9 @@ bool search_route(const Tracer &tracer, It out, NodeMap &&cached_nodes = {}) { - using namespace detail; - - using Node = TracerNodeT; - using QNode = QNode; + using Node = TracerNodeT; + using QNode = QNode; + using TracerTraits = TracerTraits_>; struct LessPred { // Comparison functor needed by the priority queue NodeMap &m; @@ -145,12 +113,13 @@ bool search_route(const Tracer &tracer, LessPred{cached_nodes}); QNode initial{source, /*parent = */ Unassigned, /*g = */0.f}; - size_t source_id = unique_id(tracer, source); + size_t source_id = TracerTraits::unique_id(tracer, source); cached_nodes[source_id] = initial; qopen.push(source_id); - size_t goal_id = goal_heuristic(tracer, source) < 0.f ? source_id : - Unassigned; + size_t goal_id = TracerTraits::goal_heuristic(tracer, source) < 0.f ? + source_id : + Unassigned; while (goal_id == Unassigned && !qopen.empty()) { size_t q_id = qopen.top(); @@ -160,13 +129,13 @@ bool search_route(const Tracer &tracer, // This should absolutely be initialized in the cache already assert(!std::isinf(q.g)); - foreach_reachable(tracer, q.node, [&](const Node &succ_nd) { + TracerTraits::foreach_reachable(tracer, q.node, [&](const Node &succ_nd) { if (goal_id != Unassigned) return true; - float h = goal_heuristic(tracer, succ_nd); - float dst = trace_distance(tracer, q.node, succ_nd); - size_t succ_id = unique_id(tracer, succ_nd); + float h = TracerTraits::goal_heuristic(tracer, succ_nd); + float dst = TracerTraits::distance(tracer, q.node, succ_nd); + size_t succ_id = TracerTraits::unique_id(tracer, succ_nd); QNode qsucc_nd{succ_nd, q_id, q.g + dst, h}; if (h < 0.f) { From 9706f16e69b0f9cfb97862520fb69593e9cf8b3e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 14 Feb 2022 12:48:51 +0100 Subject: [PATCH 102/169] Tech ENABLE_WORLD_COORDINATE - Revisited rotation of single volume Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 16 ++++++++-------- src/slic3r/GUI/Selection.cpp | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 935264d69..dd7256d2a 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -563,8 +563,7 @@ void ObjectManipulation::update_ui_from_settings() } m_check_inch->SetValue(m_imperial_units); - if (m_use_colors != (wxGetApp().app_config->get("color_mapinulation_panel") == "1")) - { + if (m_use_colors != (wxGetApp().app_config->get("color_mapinulation_panel") == "1")) { m_use_colors = wxGetApp().app_config->get("color_mapinulation_panel") == "1"; // update colors for edit-boxes int axis_id = 0; @@ -624,11 +623,10 @@ 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_rotate_label_string = L("Rotate"); #else if (m_world_coordinates) { - m_new_rotate_label_string = L("Rotate"); #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.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; @@ -671,23 +669,25 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #else const Vec3d& offset = trafo.get_offset(); #endif // ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET -// const Vec3d& mirror = trafo.get_mirror(); m_new_position = offset; - m_new_rotation = trafo.get_rotation() * (180.0 / M_PI); + m_new_rotate_label_string = L("Rotate"); + m_new_rotation = Vec3d::Zero(); m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; } else if (is_local_coordinates()) { + m_new_move_label_string = L("Translate"); m_new_position = Vec3d::Zero(); - m_new_rotation = Vec3d::Zero(); + m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()); } else { #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); - m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); + 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.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 87d651aa1..4931448ba 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -902,7 +902,8 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ else if (is_single_volume_or_modifier()) { if (transformation_type.local()) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - v.set_volume_rotation(Geometry::extract_euler_angles(m_cache.volumes_data[i].get_volume_rotation_matrix() * m)); + const Vec3d new_rotation = transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_volume_rotation_matrix() * m); + v.set_volume_rotation(new_rotation); } else if (transformation_type.instance()) { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); From 912d7814464a331f51c59f6d9b9d03a7ab2be3d3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 15 Feb 2022 09:10:41 +0100 Subject: [PATCH 103/169] Tech ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX - Render the selection bounding box in the current reference system Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 2 ++ src/slic3r/GUI/Selection.cpp | 63 +++++++++++++++++++++++++++++++++- src/slic3r/GUI/Selection.hpp | 4 +++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 40abad362..303243966 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -73,6 +73,8 @@ #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them #define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (1 && ENABLE_WORLD_COORDINATE) +// Enable rendering the selection bounding box in the current reference system +#define ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 4931448ba..cc37ebaa7 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -919,7 +919,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ #else else if (is_single_volume() || is_single_modifier()) { if (transformation_type.independent()) - v.set_volume_rotation(v.get_volume_rotation() + rotation); + v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); else { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); @@ -1409,7 +1409,33 @@ void Selection::render(float scale_factor) m_scale_factor = scale_factor; // render cumulative bounding box of selected volumes #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + BoundingBoxf3 box; + Transform3d trafo; + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + if (coordinates_type == ECoordinatesType::World) { + box = get_bounding_box(); + trafo = Transform3d::Identity(); + } + else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) { + const GLVolume& v = *get_volume(*get_volume_idxs().begin()); + box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); + trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false); + } + else { + const Selection::IndicesList& ids = get_volume_idxs(); + for (unsigned int id : ids) { + const GLVolume& v = *get_volume(id); + box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); + } + box = box.transformed(get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); + trafo = get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false); + } + + render_bounding_box(box, trafo, ColorRGB::WHITE()); +#else render_bounding_box(get_bounding_box(), ColorRGB::WHITE()); +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX #else render_selected_volumes(); #endif // ENABLE_LEGACY_OPENGL_REMOVAL @@ -2020,6 +2046,12 @@ void Selection::render_synchronized_volumes() float color[3] = { 1.0f, 1.0f, 0.0f }; #endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + BoundingBoxf3 box; + Transform3d trafo; +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + for (unsigned int i : m_list) { const GLVolume& volume = *(*m_volumes)[i]; int object_idx = volume.object_idx(); @@ -2033,7 +2065,23 @@ void Selection::render_synchronized_volumes() continue; #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + if (coordinates_type == ECoordinatesType::World) { + box = v.transformed_convex_hull_bounding_box(); + trafo = Transform3d::Identity(); + } + else if (coordinates_type == ECoordinatesType::Local) { + box = v.bounding_box(); + trafo = v.world_matrix(); + } + else { + box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix()); + trafo = v.get_instance_transformation().get_matrix(); + } + render_bounding_box(box, trafo, ColorRGB::YELLOW()); +#else render_bounding_box(v.transformed_convex_hull_bounding_box(), ColorRGB::YELLOW()); +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX #else render_bounding_box(v.transformed_convex_hull_bounding_box(), color); #endif // ENABLE_LEGACY_OPENGL_REMOVAL @@ -2042,7 +2090,11 @@ void Selection::render_synchronized_volumes() } #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +void Selection::render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const ColorRGB& color) +#else void Selection::render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color) +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX { #else void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) const @@ -2146,6 +2198,11 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con if (shader == nullptr) return; +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo.data())); +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); @@ -2155,6 +2212,10 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con m_box.set_color(to_rgba(color)); m_box.render(); shader->stop_using(); + +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + glsafe(::glPopMatrix()); +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX #else ::glBegin(GL_LINES); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 9d036d3c6..4030a0585 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -395,7 +395,11 @@ private: void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); } void render_synchronized_volumes(); #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + void render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const ColorRGB& color); +#else void render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color); +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX #else void render_selected_volumes() const; void render_bounding_box(const BoundingBoxf3& box, float* color) const; From 558bccec48ab18797b347aaa334e01214d11db17 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 16 Feb 2022 12:36:41 +0100 Subject: [PATCH 104/169] Tech ENABLE_WORLD_COORDINATE_SHOW_AXES - Show axes of the current reference system when sidebar hints are active for non-world reference systems Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 2 + src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/3DBed.cpp | 10 +++++ src/slic3r/GUI/3DBed.hpp | 10 +++++ src/slic3r/GUI/CoordAxes.cpp | 79 ++++++++++++++++++++++++++++++++++ src/slic3r/GUI/CoordAxes.hpp | 59 +++++++++++++++++++++++++ src/slic3r/GUI/Selection.cpp | 6 +++ src/slic3r/GUI/Selection.hpp | 13 +++++- 8 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 src/slic3r/GUI/CoordAxes.cpp create mode 100644 src/slic3r/GUI/CoordAxes.hpp diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 303243966..ada35b5ec 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -75,6 +75,8 @@ #define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (1 && ENABLE_WORLD_COORDINATE) // Enable rendering the selection bounding box in the current reference system #define ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX (1 && ENABLE_WORLD_COORDINATE) +// Enable showing the axes of the current reference system when sidebar hints are active +#define ENABLE_WORLD_COORDINATE_SHOW_AXES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 02d134b29..29b8b7e73 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -131,6 +131,8 @@ set(SLIC3R_GUI_SOURCES GUI/2DBed.hpp GUI/3DBed.cpp GUI/3DBed.hpp + GUI/CoordAxes.cpp + GUI/CoordAxes.hpp GUI/Camera.cpp GUI/Camera.hpp GUI/wxExtensions.cpp diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 1ce1af741..132245b4d 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -102,6 +102,7 @@ const float* GeometryBuffer::get_vertices_data() const } #endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#if !ENABLE_WORLD_COORDINATE_SHOW_AXES const float Bed3D::Axes::DefaultStemRadius = 0.5f; const float Bed3D::Axes::DefaultStemLength = 25.0f; const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius; @@ -179,6 +180,7 @@ void Bed3D::Axes::render() glsafe(::glDisable(GL_DEPTH_TEST)); } +#endif // !ENABLE_WORLD_COORDINATE_SHOW_AXES bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) { @@ -341,7 +343,11 @@ BoundingBoxf3 Bed3D::calc_extended_bounding_box() const out.max.z() = 0.0; // extend to contain axes out.merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones()); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + out.merge(out.min + Vec3d(-m_axes.get_tip_radius(), -m_axes.get_tip_radius(), out.max.z())); +#else out.merge(out.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, out.max.z())); +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES // extend to contain model, if any BoundingBoxf3 model_bb = m_model.get_bounding_box(); if (model_bb.defined) { @@ -539,7 +545,11 @@ std::tuple Bed3D::detect_type(const Point void Bed3D::render_axes() { if (m_build_volume.valid()) +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + m_axes.render(0.25f); +#else m_axes.render(); +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES } #if ENABLE_GL_SHADERS_ATTRIBUTES diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 708d186a4..61f01f021 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -3,7 +3,11 @@ #include "GLTexture.hpp" #include "3DScene.hpp" +#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#include "CoordAxes.hpp" +#else #include "GLModel.hpp" +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES #include "libslic3r/BuildVolume.hpp" #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -44,6 +48,7 @@ public: class Bed3D { +#if !ENABLE_WORLD_COORDINATE_SHOW_AXES class Axes { public: @@ -67,6 +72,7 @@ class Bed3D float get_total_length() const { return m_stem_length + DefaultTipLength; } void render(); }; +#endif // !ENABLE_WORLD_COORDINATE_SHOW_AXES public: enum class Type : unsigned char @@ -107,7 +113,11 @@ private: #if !ENABLE_LEGACY_OPENGL_REMOVAL unsigned int m_vbo_id{ 0 }; #endif // !ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + CoordAxes m_axes; +#else Axes m_axes; +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES float m_scale_factor{ 1.0f }; diff --git a/src/slic3r/GUI/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp new file mode 100644 index 000000000..c3cced2f9 --- /dev/null +++ b/src/slic3r/GUI/CoordAxes.cpp @@ -0,0 +1,79 @@ +#include "libslic3r/libslic3r.h" + +#include "CoordAxes.hpp" +#include "GUI_App.hpp" +#include "3DScene.hpp" + +#include + +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + +namespace Slic3r { +namespace GUI { + +const float CoordAxes::DefaultStemRadius = 0.5f; +const float CoordAxes::DefaultStemLength = 25.0f; +const float CoordAxes::DefaultTipRadius = 2.5f * CoordAxes::DefaultStemRadius; +const float CoordAxes::DefaultTipLength = 5.0f; + +void CoordAxes::render(float emission_factor) +{ + auto render_axis = [this](const Transform3f& transform) { + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixf(transform.data())); + m_arrow.render(); + glsafe(::glPopMatrix()); + }; + + if (!m_arrow.is_initialized()) + m_arrow.init_from(stilized_arrow(16, m_tip_radius, m_tip_length, m_stem_radius, m_stem_length)); + + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + bool shader_differs = (curr_shader == nullptr || curr_shader->get_name() != "gouraud_light"); + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + if (shader_differs) { + if (curr_shader != nullptr) + curr_shader->stop_using(); + shader->start_using(); + } + shader->set_uniform("emission_factor", emission_factor); + + // x axis +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_arrow.set_color(ColorRGBA::X()); +#else + m_arrow.set_color(-1, ColorRGBA::X()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast()); + + // y axis +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_arrow.set_color(ColorRGBA::Y()); +#else + m_arrow.set_color(-1, ColorRGBA::Y()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast()); + + // z axis +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_arrow.set_color(ColorRGBA::Z()); +#else + m_arrow.set_color(-1, ColorRGBA::Z()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + render_axis(Geometry::assemble_transform(m_origin).cast()); + + if (shader_differs) { + shader->stop_using(); + if (curr_shader != nullptr) + curr_shader->start_using(); + } +} + +} // GUI +} // Slic3r + +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES diff --git a/src/slic3r/GUI/CoordAxes.hpp b/src/slic3r/GUI/CoordAxes.hpp new file mode 100644 index 000000000..de5472b13 --- /dev/null +++ b/src/slic3r/GUI/CoordAxes.hpp @@ -0,0 +1,59 @@ +#ifndef slic3r_CoordAxes_hpp_ +#define slic3r_CoordAxes_hpp_ + +#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#include "GLModel.hpp" + +namespace Slic3r { +namespace GUI { + +class CoordAxes +{ +public: + static const float DefaultStemRadius; + static const float DefaultStemLength; + static const float DefaultTipRadius; + static const float DefaultTipLength; + +private: + Vec3d m_origin{ Vec3d::Zero() }; + float m_stem_radius{ DefaultStemRadius }; + float m_stem_length{ DefaultStemLength }; + float m_tip_radius{ DefaultTipRadius }; + float m_tip_length{ DefaultTipLength }; + GLModel m_arrow; + +public: + const Vec3d& get_origin() const { return m_origin; } + void set_origin(const Vec3d& origin) { m_origin = origin; } + void set_stem_radius(float radius) { + m_stem_radius = radius; + m_arrow.reset(); + } + void set_stem_length(float length) { + m_stem_length = length; + m_arrow.reset(); + } + void set_tip_radius(float radius) { + m_tip_radius = radius; + m_arrow.reset(); + } + void set_tip_length(float length) { + m_tip_length = length; + m_arrow.reset(); + } + + float get_stem_radius() const { return m_stem_radius; } + float get_stem_length() const { return m_stem_length; } + float get_tip_radius() const { return m_tip_radius; } + float get_tip_length() const { return m_tip_length; } + float get_total_length() const { return m_stem_length + m_tip_length; } + void render(float emission_factor = 0.0f); +}; + +} // GUI +} // Slic3r + +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES + +#endif // slic3r_CoordAxes_hpp_ diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index cc37ebaa7..b1e48ffe4 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -117,6 +117,12 @@ Selection::Selection() , m_scale_factor(1.0f) { this->set_bounding_boxes_dirty(); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + m_axes.set_stem_radius(0.15f); + m_axes.set_stem_length(3.0f); + m_axes.set_tip_radius(0.45f); + m_axes.set_tip_length(1.5f); +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES } diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 4030a0585..a6dffb033 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -3,9 +3,15 @@ #include "libslic3r/Geometry.hpp" #if ENABLE_WORLD_COORDINATE -#include "slic3r/GUI/GUI_Geometry.hpp" -#endif // ENABLE_WORLD_COORDINATE +#include "GUI_Geometry.hpp" +#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#include "CoordAxes.hpp" +#else #include "GLModel.hpp" +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#else +#include "GLModel.hpp" +#endif // ENABLE_WORLD_COORDINATE #include #include @@ -221,6 +227,9 @@ private: GLModel m_vbo_sphere; #endif // ENABLE_RENDER_SELECTION_CENTER +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + CoordAxes m_axes; +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES GLModel m_arrow; GLModel m_curved_arrow; #if ENABLE_LEGACY_OPENGL_REMOVAL From 622796e9e3fcda63f9d6ec7ea97e43707883500f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 18 Feb 2022 08:57:26 +0100 Subject: [PATCH 105/169] Tech ENABLE_WORLD_COORDINATE_SCALE_REVISITED - Alternate implementation of manipulating scale for instances and volumes using gizmo scale and sidebar object manipulator fields - 1st installment Fixed conflicts during rebase with master --- src/libslic3r/Model.cpp | 2 + src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 111 ++++++++++++++++++++-- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 5 +- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 6 ++ src/slic3r/GUI/Gizmos/GLGizmosManager.hpp | 2 +- src/slic3r/GUI/Selection.cpp | 73 +++++++++++++- src/slic3r/GUI/Selection.hpp | 9 ++ src/slic3r/GUI/wxExtensions.cpp | 4 + 9 files changed, 199 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3376cc888..0f48f2bb2 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1412,9 +1412,11 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) assert(instance_idx < this->instances.size()); const Geometry::Transformation reference_trafo = this->instances[instance_idx]->get_transformation(); +#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED if (Geometry::is_rotation_ninety_degrees(reference_trafo.get_rotation())) // nothing to do, scaling in the world coordinate space is possible in the representation of Geometry::Transformation. return; +#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED bool left_handed = reference_trafo.is_left_handed(); bool has_mirrorring = ! reference_trafo.get_mirror().isApprox(Vec3d(1., 1., 1.)); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index ada35b5ec..6a73aaf1e 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -77,6 +77,8 @@ #define ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX (1 && ENABLE_WORLD_COORDINATE) // Enable showing the axes of the current reference system when sidebar hints are active #define ENABLE_WORLD_COORDINATE_SHOW_AXES (1 && ENABLE_WORLD_COORDINATE) +// Enable alternate implementation of manipulating scale for instances and volumes +#define ENABLE_WORLD_COORDINATE_SCALE_REVISITED (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index dd7256d2a..60609b7eb 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -627,10 +627,17 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (m_world_coordinates) { #endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale_label_string = L("Scale"); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); - m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; - } +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale = Vec3d(100.0, 100.0, 100.0); +#else + m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + } else { #if ENABLE_WORLD_COORDINATE m_new_move_label_string = L("Translate"); @@ -672,9 +679,16 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = offset; m_new_rotate_label_string = L("Rotate"); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale_label_string = L("Scale"); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_rotation = Vec3d::Zero(); m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale = Vec3d(100.0, 100.0, 100.0); +#else m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED } else if (is_local_coordinates()) { m_new_move_label_string = L("Translate"); @@ -687,10 +701,17 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); m_new_rotate_label_string = L("Rotate"); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale_label_string = L("Scale"); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED 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(); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale = Vec3d(100.0, 100.0, 100.0); +#else m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED } #else m_new_scale = volume->get_volume_scaling_factor() * 100.0; @@ -767,20 +788,26 @@ void ObjectManipulation::update_if_dirty() #else if (selection.requires_uniform_scale()) { #endif // ENABLE_WORLD_COORDINATE +#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_lock_bnt->SetLock(true); +#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED #if ENABLE_WORLD_COORDINATE wxString tooltip; if (selection.is_single_volume_or_modifier()) { +#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the instance local axes"); else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_World) tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the printer axes"); +#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED } else if (selection.is_single_full_instance()) { +#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED if (reason == Selection::EUniformScaleRequiredReason::InstanceNotAxisAligned_World) tooltip = _L("You cannot use non-uniform scaling mode for instances non aligned with the printer axes"); else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) tooltip = _L("You cannot use non-uniform scaling mode for instances containing non locally axis-aligned parts"); +#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED } else tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection"); @@ -789,7 +816,9 @@ void ObjectManipulation::update_if_dirty() #else m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); #endif // ENABLE_WORLD_COORDINATE +#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_lock_bnt->disable(); +#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED } else { m_lock_bnt->SetLock(m_uniform_scale); @@ -1108,7 +1137,11 @@ void ObjectManipulation::change_size_value(int axis, double value) selection.get_unscaled_instance_bounding_box().size() : wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + this->do_size(axis, size.cwiseQuotient(ref_size)); +#else this->do_scale(axis, size.cwiseQuotient(ref_size)); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_cache.size = size; m_cache.size_rounded(axis) = DBL_MAX; @@ -1124,8 +1157,18 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + if (is_local_coordinates()) + transformation_type.set_local(); + else if (is_instance_coordinates()) + transformation_type.set_instance(); + + if (!is_local_coordinates()) + transformation_type.set_relative(); +#else if (!is_world_coordinates()) transformation_type.set_local(); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; @@ -1156,6 +1199,36 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); } +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +void ObjectManipulation::do_size(int axis, const Vec3d& scale) const +{ + Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + + TransformationType transformation_type; + if (is_local_coordinates()) + transformation_type.set_local(); + else if (is_instance_coordinates()) + transformation_type.set_instance(); + + bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); + Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; + + if (!uniform_scale && is_world_coordinates()) { + if (selection.is_single_full_instance()) + scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); + else if (selection.is_single_volume_or_modifier()) { + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); + } + } + + selection.setup_cache(); + selection.scale(scaling_factor, transformation_type); + wxGetApp().plater()->canvas3D()->do_scale(L("Set Size")); +} +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + void ObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value) { if (!m_cache.is_valid()) @@ -1190,21 +1263,37 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double } } -void ObjectManipulation::set_uniform_scaling(const bool new_value) +void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) { const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); -#if ENABLE_WORLD_COORDINATE - if (selection.is_single_full_instance() && is_world_coordinates() && !new_value) { +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + if (!use_uniform_scale) { + int res = selection.bake_transform_if_needed(false); + if (res == -1) { + // Enforce uniform scaling. + m_lock_bnt->SetLock(true); + return; + } + else if (res == 0) { + // Recalculate cached values at this panel, refresh the screen. + this->UpdateAndShow(true); + } + } + + m_uniform_scale = use_uniform_scale; #else - if (selection.is_single_full_instance() && m_world_coordinates && !new_value) { +#if ENABLE_WORLD_COORDINATE + if (selection.is_single_full_instance() && is_world_coordinates() && !use_uniform_scale) { +#else + if (selection.is_single_full_instance() && m_world_coordinates && !use_uniform_scale) { #endif // ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); // Is the angle close to a multiple of 90 degrees? - if (! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { + if (!Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { // Cannot apply scaling in the world coordinate system. - //wxMessageDialog dlg(GUI::wxGetApp().mainframe, + //wxMessageDialog dlg(GUI::wxGetApp().mainframe, MessageDialog dlg(GUI::wxGetApp().mainframe, _L("The currently manipulated object is tilted (rotation angles are not multiples of 90°).\n" "Non-uniform scaling of tilted objects is only possible in the World coordinate system,\n" @@ -1212,7 +1301,7 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) _L("This operation is irreversible.\n" "Do you want to proceed?"), SLIC3R_APP_NAME, - wxYES_NO | wxCANCEL | wxCANCEL_DEFAULT | wxICON_QUESTION); + wxYES_NO | wxCANCEL | wxCANCEL_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() != wxID_YES) { // Enforce uniform scaling. m_lock_bnt->SetLock(true); @@ -1226,7 +1315,9 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value) this->UpdateAndShow(true); } } - m_uniform_scale = new_value; + + m_uniform_scale = use_uniform_scale; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED } #if ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 19d2c1808..289485dad 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -196,7 +196,7 @@ public: // Called from the App to update the UI if dirty. void update_if_dirty(); - void set_uniform_scaling(const bool uniform_scale); + void set_uniform_scaling(const bool use_uniform_scale); bool get_uniform_scaling() const { return m_uniform_scale; } #if ENABLE_WORLD_COORDINATE void set_coordinates_type(ECoordinatesType type); @@ -256,6 +256,9 @@ private: void change_scale_value(int axis, double value); void change_size_value(int axis, double value); void do_scale(int axis, const Vec3d &scale) const; +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + void do_size(int axis, const Vec3d& scale) const; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED #if ENABLE_WORLD_COORDINATE void set_coordinates_type(const wxString& type_string); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index afaf6cdcd..e279e53e2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -69,6 +69,12 @@ public: const Vec3d& get_scale() const { return m_scale; } void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; } +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + const Vec3d& get_starting_scale() const { return m_starting.scale; } +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + + const Vec3d& get_offset() const { return m_offset; } + std::string get_tooltip() const override; /// diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index 187afd889..2e9e6bb65 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -205,7 +205,7 @@ public: bool handle_shortcut(int key); bool is_dragging() const; - + ClippingPlane get_clipping_plane() const; bool wants_reslice_supports_on_undo() const; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index b1e48ffe4..dfd543a82 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -7,9 +7,15 @@ #include "GUI.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectList.hpp" -#include "Gizmos/GLGizmoBase.hpp" #include "Camera.hpp" #include "Plater.hpp" + +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#include "MsgDialog.hpp" +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + +#include "Gizmos/GLGizmoBase.hpp" + #include "slic3r/Utils/UndoRedo.hpp" #include "libslic3r/LocalesUtils.hpp" @@ -604,6 +610,9 @@ bool Selection::requires_uniform_scale() const #endif // ENABLE_WORLD_COORDINATE { #if ENABLE_WORLD_COORDINATE + if (is_empty()) + return false; + ECoordinatesType coord_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (is_single_volume_or_modifier()) { if (coord_type == ECoordinatesType::World) { @@ -638,7 +647,6 @@ bool Selection::requires_uniform_scale() const } } } - return false; } else { for (unsigned int i : m_list) { @@ -648,8 +656,8 @@ bool Selection::requires_uniform_scale() const return true; } } - return false; } + return false; } if (reason != nullptr) @@ -1302,6 +1310,65 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co this->set_bounding_boxes_dirty(); } +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +int Selection::bake_transform_if_needed(bool apply_scale) const +{ + if ((is_single_full_instance() && wxGetApp().obj_manipul()->is_world_coordinates()) || + (is_single_volume_or_modifier() && !wxGetApp().obj_manipul()->is_local_coordinates())) { + // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. + // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one + const GLVolume& volume = *get_volume(*get_volume_idxs().begin()); + bool needs_baking = false; + if (is_single_full_instance()) { + // Is the instance angle close to a multiple of 90 degrees? + needs_baking |= !Geometry::is_rotation_ninety_degrees(volume.get_instance_rotation()); + // Are all volumes angles close to a multiple of 90 degrees? + for (unsigned int id : get_volume_idxs()) { + if (needs_baking) + break; + needs_baking |= !Geometry::is_rotation_ninety_degrees(get_volume(id)->get_volume_rotation()); + } + } + else if (is_single_volume_or_modifier()) { + // is the volume angle close to a multiple of 90 degrees? + needs_baking |= !Geometry::is_rotation_ninety_degrees(volume.get_volume_rotation()); + if (wxGetApp().obj_manipul()->is_world_coordinates()) + // Is the instance angle close to a multiple of 90 degrees? + needs_baking |= !Geometry::is_rotation_ninety_degrees(volume.get_instance_rotation()); + } + + if (needs_baking) { + MessageDialog dlg((wxWindow*)wxGetApp().mainframe, + _L("The currently manipulated object is tilted or contains tilted parts (rotation angles are not multiples of 90°). " + "Non-uniform scaling of tilted objects is only possible in non-local coordinate systems, " + "once the rotation is embedded into the object coordinates.") + "\n" + + _L("This operation is irreversible.") + "\n" + + _L("Do you want to proceed?"), + SLIC3R_APP_NAME, + wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); + if (dlg.ShowModal() != wxID_YES) + return -1; + + if (apply_scale) { + wxGetApp().plater()->get_current_canvas3D()->do_scale(L("Scale + Bake transform")); + Plater::SuppressSnapshots suppress(wxGetApp().plater()); + wxGetApp().plater()->take_snapshot(_("Bake transform")); + } + else + wxGetApp().plater()->take_snapshot(_("Bake transform")); + + // Bake the rotation into the meshes of the object. + wxGetApp().model().objects[volume.composite_id.object_id]->bake_xy_rotation_into_meshes(volume.composite_id.instance_id); + // Update the 3D scene, selections etc. + wxGetApp().plater()->update(); + return 0; + } + } + + return 1; +} +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + void Selection::erase() { if (!m_valid) diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index a6dffb033..5e9b504be 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -366,6 +366,15 @@ public: void translate(unsigned int object_idx, const Vec3d& displacement); void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + // returns: + // -1 if the user refused to proceed with baking when asked + // 0 if the baking was performed + // 1 if no baking was needed + // if apply_scale == true the scaling of the current selection is applied + int bake_transform_if_needed(bool apply_scale) const; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + void erase(); void render(float scale_factor = 1.0); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index dc177ade3..bacdb1f9c 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -581,8 +581,12 @@ void LockButton::OnButton(wxCommandEvent& event) if (m_disabled) return; +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + SetLock(!m_is_pushed); +#else m_is_pushed = !m_is_pushed; update_button_bitmaps(); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED event.Skip(); } From e86cbf0d8c0631b94586d3c98a2b085c9ef4060e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 18 Feb 2022 14:40:43 +0100 Subject: [PATCH 106/169] Tech ENABLE_WORLD_COORDINATE_SCALE_REVISITED - Detection of required transformation baking done on mouse dragging event in place of mouse up event for gizmo scale Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 5 ++--- src/slic3r/GUI/Selection.cpp | 10 ++-------- src/slic3r/GUI/Selection.hpp | 3 +-- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 60609b7eb..48934e165 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1268,16 +1268,15 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED if (!use_uniform_scale) { - int res = selection.bake_transform_if_needed(false); + int res = selection.bake_transform_if_needed(); if (res == -1) { // Enforce uniform scaling. m_lock_bnt->SetLock(true); return; } - else if (res == 0) { + else if (res == 0) // Recalculate cached values at this panel, refresh the screen. this->UpdateAndShow(true); - } } m_uniform_scale = use_uniform_scale; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index dfd543a82..685e0af8c 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1311,7 +1311,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co } #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED -int Selection::bake_transform_if_needed(bool apply_scale) const +int Selection::bake_transform_if_needed() const { if ((is_single_full_instance() && wxGetApp().obj_manipul()->is_world_coordinates()) || (is_single_volume_or_modifier() && !wxGetApp().obj_manipul()->is_local_coordinates())) { @@ -1349,13 +1349,7 @@ int Selection::bake_transform_if_needed(bool apply_scale) const if (dlg.ShowModal() != wxID_YES) return -1; - if (apply_scale) { - wxGetApp().plater()->get_current_canvas3D()->do_scale(L("Scale + Bake transform")); - Plater::SuppressSnapshots suppress(wxGetApp().plater()); - wxGetApp().plater()->take_snapshot(_("Bake transform")); - } - else - wxGetApp().plater()->take_snapshot(_("Bake transform")); + wxGetApp().plater()->take_snapshot(_("Bake transform")); // Bake the rotation into the meshes of the object. wxGetApp().model().objects[volume.composite_id.object_id]->bake_xy_rotation_into_meshes(volume.composite_id.instance_id); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 5e9b504be..95a73f8f7 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -371,8 +371,7 @@ public: // -1 if the user refused to proceed with baking when asked // 0 if the baking was performed // 1 if no baking was needed - // if apply_scale == true the scaling of the current selection is applied - int bake_transform_if_needed(bool apply_scale) const; + int bake_transform_if_needed() const; #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED void erase(); From 2b002da8ce98d1eb2c861e411f07bb6c639b82c2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 23 Feb 2022 15:51:56 +0100 Subject: [PATCH 107/169] Tech ENABLE_WORLD_COORDINATE - Fixed rotation performed from sidebar manipulation fields --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 48934e165..f5abe805c 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1057,10 +1057,13 @@ void ObjectManipulation::change_rotation_value(int axis, double value) if (selection.is_single_full_instance()) transformation_type.set_independent(); - if (!is_world_coordinates()) { + if (is_local_coordinates()) { transformation_type.set_local(); transformation_type.set_absolute(); } + + if (is_instance_coordinates()) + transformation_type.set_instance(); #else if (selection.is_single_full_instance() || selection.requires_local_axes()) transformation_type.set_independent(); From 45335ee26bbf059c1243e038e612f27d82d3ae3f Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 24 Feb 2022 14:09:43 +0100 Subject: [PATCH 108/169] Tech ENABLE_OBJECT_MANIPULATOR_FOCUS - Fixed kill focus handling for sidebar object manipulator fields --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index f5abe805c..8a0d52bbb 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1457,8 +1457,8 @@ ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, parent->set_focused_editor(nullptr); #if ENABLE_OBJECT_MANIPULATOR_FOCUS - // if the widgets exchanging focus are both manipulator fields, call kill_focus - if (dynamic_cast(e.GetEventObject()) != nullptr && dynamic_cast(e.GetWindow()) != nullptr) + // if the widgets loosing focus is a manipulator field, call kill_focus + if (dynamic_cast(e.GetEventObject()) != nullptr) #else if (!m_enter_pressed) #endif // ENABLE_OBJECT_MANIPULATOR_FOCUS From 47a1989fdcd2551bd2ad1c6b2c25107a1ca76c38 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 25 Feb 2022 12:12:23 +0100 Subject: [PATCH 109/169] Tech ENABLE_WORLD_COORDINATE - ObjectManipulation::update_reset_buttons_visibility() changed logic for drop to bed button --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 8a0d52bbb..96429c924 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -861,6 +861,14 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_drop_to_bed = false; #if ENABLE_WORLD_COORDINATE + if ((m_coordinates_type == ECoordinatesType::World && selection.is_single_full_instance()) || + (m_coordinates_type == ECoordinatesType::Instance && selection.is_single_volume_or_modifier())) { + const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() : + get_volume_min_z(*selection.get_volume(*selection.get_volume_idxs().begin())); + + show_drop_to_bed = std::abs(min_z) > EPSILON; + } + if (m_coordinates_type == ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation = Vec3d::Zero(); @@ -870,26 +878,28 @@ void ObjectManipulation::update_reset_buttons_visibility() const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); Vec3d rotation; Vec3d scale; -#endif // ENABLE_WORLD_COORDINATE double min_z = 0.0; +#endif // ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance()) { rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); +#if !ENABLE_WORLD_COORDINATE min_z = selection.get_scaled_instance_bounding_box().min.z(); +#endif // !ENABLE_WORLD_COORDINATE } else { rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); +#if !ENABLE_WORLD_COORDINATE min_z = get_volume_min_z(*volume); +#endif // !ENABLE_WORLD_COORDINATE } show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); -#if ENABLE_WORLD_COORDINATE - show_drop_to_bed = std::abs(min_z) > EPSILON; -#else +#if !ENABLE_WORLD_COORDINATE show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; -#endif // ENABLE_WORLD_COORDINATE +#endif // !ENABLE_WORLD_COORDINATE } wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] { From 9acf181dda31c2a793c4e9368af547f258a13337 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Mar 2022 13:56:33 +0100 Subject: [PATCH 110/169] Tech ENABLE_GL_SHADERS_ATTRIBUTES - Fixed rendering of bed axes --- src/slic3r/GUI/CoordAxes.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/slic3r/GUI/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp index c3cced2f9..2ec303c61 100644 --- a/src/slic3r/GUI/CoordAxes.cpp +++ b/src/slic3r/GUI/CoordAxes.cpp @@ -3,6 +3,10 @@ #include "CoordAxes.hpp" #include "GUI_App.hpp" #include "3DScene.hpp" +#if ENABLE_GL_SHADERS_ATTRIBUTES +#include "Plater.hpp" +#include "Camera.hpp" +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #include @@ -18,20 +22,38 @@ const float CoordAxes::DefaultTipLength = 5.0f; void CoordAxes::render(float emission_factor) { +#if ENABLE_GL_SHADERS_ATTRIBUTES + auto render_axis = [this](GLShaderProgram& shader, const Transform3d& transform) { + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d matrix = camera.get_view_matrix() * transform; + shader.set_uniform("view_model_matrix", matrix); + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); + shader.set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); + m_arrow.render(); +#else auto render_axis = [this](const Transform3f& transform) { glsafe(::glPushMatrix()); glsafe(::glMultMatrixf(transform.data())); m_arrow.render(); glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES }; if (!m_arrow.is_initialized()) m_arrow.init_from(stilized_arrow(16, m_tip_radius, m_tip_length, m_stem_radius, m_stem_length)); GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + bool shader_differs = (curr_shader == nullptr || curr_shader->get_name() != "gouraud_light_attr"); +#else bool shader_differs = (curr_shader == nullptr || curr_shader->get_name() != "gouraud_light"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -48,7 +70,11 @@ void CoordAxes::render(float emission_factor) #else m_arrow.set_color(-1, ColorRGBA::X()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_axis(*shader, Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 })); +#else render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES // y axis #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -56,7 +82,11 @@ void CoordAxes::render(float emission_factor) #else m_arrow.set_color(-1, ColorRGBA::Y()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_axis(*shader, Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 })); +#else render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES // z axis #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -64,7 +94,11 @@ void CoordAxes::render(float emission_factor) #else m_arrow.set_color(-1, ColorRGBA::Z()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_axis(*shader, Geometry::assemble_transform(m_origin)); +#else render_axis(Geometry::assemble_transform(m_origin).cast()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader_differs) { shader->stop_using(); From 155c3e88954f4075a08d51157d2fd029b92f5d1a Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Mar 2022 14:03:50 +0100 Subject: [PATCH 111/169] Tech ENABLE_GL_SHADERS_ATTRIBUTES - Fixed rendering of selection bounding box Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 126 ++++++++++++++++++++++++++++++----- 1 file changed, 111 insertions(+), 15 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 685e0af8c..50d4677c0 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1516,7 +1516,11 @@ void Selection::render_center(bool gizmo_is_dragging) return; #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -1561,7 +1565,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) return; #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat_attr" : "gouraud_light_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat" : "gouraud_light"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -1576,28 +1584,51 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) shader->start_using(); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - } +} #endif // ENABLE_LEGACY_OPENGL_REMOVAL glsafe(::glEnable(GL_DEPTH_TEST)); #if ENABLE_GL_SHADERS_ATTRIBUTES const Transform3d base_matrix = Geometry::assemble_transform(get_bounding_box().center()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES Transform3d orient_matrix = Transform3d::Identity(); #else glsafe(::glPushMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES + +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + const Vec3d center = get_bounding_box().center(); + Vec3d axes_center = center; +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES if (!boost::starts_with(sidebar_field, "layer")) { #if ENABLE_GL_SHADERS_ATTRIBUTES shader->set_uniform("emission_factor", 0.05f); -#else - const Vec3d& center = get_bounding_box().center(); #endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES + const Vec3d& center = get_bounding_box().center(); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE if (is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES +#else + if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { +#endif // ENABLE_WORLD_COORDINATE +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES glsafe(::glTranslated(center.x(), center.y(), center.z())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES + Transform3d orient_matrix = Transform3d::Identity(); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); +#else + glsafe(::glMultMatrixd(orient_matrix.data())); +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#else if (!boost::starts_with(sidebar_field, "position")) { #if !ENABLE_GL_SHADERS_ATTRIBUTES Transform3d orient_matrix = Transform3d::Identity(); @@ -1618,24 +1649,56 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glMultMatrixd(orient_matrix.data())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } + } +#endif // ENABLE_WORLD_COORDINATE } +#if ENABLE_WORLD_COORDINATE + else if (is_single_volume_or_modifier()) { +#else else if (is_single_volume() || is_single_modifier()) { +#endif // ENABLE_WORLD_COORDINATE +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES + glsafe(::glTranslated(center.x(), center.y(), center.z())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES + Transform3d orient_matrix = Transform3d::Identity(); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES + if (wxGetApp().obj_manipul()->is_local_coordinates()) { + const GLVolume* v = (*m_volumes)[*m_list.begin()]; + orient_matrix = v->get_instance_transformation().get_matrix(true, false, true, true) * v->get_volume_transformation().get_matrix(true, false, true, true); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + axes_center = (*m_volumes)[*m_list.begin()]->world_matrix().translation(); +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES + } + else { + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); + } +#else + } +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glMultMatrixd(orient_matrix.data())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES + } +#else #if ENABLE_GL_SHADERS_ATTRIBUTES orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); #else - glsafe(::glTranslated(center.x(), center.y(), center.z())); Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); #endif // ENABLE_GL_SHADERS_ATTRIBUTES if (!boost::starts_with(sidebar_field, "position")) orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true); - #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glMultMatrixd(orient_matrix.data())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#endif // ENABLE_WORLD_COORDINATE } else { -#if ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES if (requires_local_axes()) orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); #else @@ -1644,15 +1707,24 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } -#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES + } } - } #if ENABLE_LEGACY_OPENGL_REMOVAL if (!boost::starts_with(sidebar_field, "layer")) glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + if (!boost::starts_with(sidebar_field, "layer")) { + shader->set_uniform("emission_factor", 0.1f); + glsafe(::glPushMatrix()); + glsafe(::glTranslated(center.x(), center.y(), center.z())); + glsafe(::glMultMatrixd(orient_matrix.data())); + } +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES + #if ENABLE_GL_SHADERS_ATTRIBUTES if (boost::starts_with(sidebar_field, "position")) render_sidebar_position_hints(sidebar_field, *shader, base_matrix * orient_matrix); @@ -1672,9 +1744,23 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field); - glsafe(::glPopMatrix()); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + if (!boost::starts_with(sidebar_field, "layer")) { + glsafe(::glPopMatrix()); + glsafe(::glPushMatrix()); + glsafe(::glTranslated(axes_center.x(), axes_center.y(), axes_center.z())); + glsafe(::glMultMatrixd(orient_matrix.data())); + if (!wxGetApp().obj_manipul()->is_world_coordinates()) + m_axes.render(0.25f); + glsafe(::glPopMatrix()); + } +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES #endif // ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES + #if !ENABLE_LEGACY_OPENGL_REMOVAL if (!boost::starts_with(sidebar_field, "layer")) #endif // !ENABLE_LEGACY_OPENGL_REMOVAL @@ -2259,21 +2345,29 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glLineWidth(2.0f * m_scale_factor)); - +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; #if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo.data())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES #endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * trafo); +#else shader->set_uniform("view_model_matrix", camera.get_view_matrix()); +#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES m_box.set_color(to_rgba(color)); @@ -2281,7 +2375,9 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con shader->stop_using(); #if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES #endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX #else ::glBegin(GL_LINES); From 8c95f47d4a509b6d520f89d4b99d453d16a6611b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 10 Mar 2022 14:42:05 +0100 Subject: [PATCH 112/169] Tech ENABLE_GL_SHADERS_ATTRIBUTES - Fixed rendering of sidebar hints reference system Fixed conflicts during rebase with master --- src/slic3r/GUI/3DBed.cpp | 4 ++++ src/slic3r/GUI/CoordAxes.cpp | 10 +++++++--- src/slic3r/GUI/CoordAxes.hpp | 5 +++++ src/slic3r/GUI/Selection.cpp | 18 +++++++++++++++--- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 132245b4d..7b34d59bf 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -546,7 +546,11 @@ void Bed3D::render_axes() { if (m_build_volume.valid()) #if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_GL_SHADERS_ATTRIBUTES + m_axes.render(Transform3d::Identity(), 0.25f); +#else m_axes.render(0.25f); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #else m_axes.render(); #endif // ENABLE_WORLD_COORDINATE_SHOW_AXES diff --git a/src/slic3r/GUI/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp index 2ec303c61..75038e23c 100644 --- a/src/slic3r/GUI/CoordAxes.cpp +++ b/src/slic3r/GUI/CoordAxes.cpp @@ -20,7 +20,11 @@ const float CoordAxes::DefaultStemLength = 25.0f; const float CoordAxes::DefaultTipRadius = 2.5f * CoordAxes::DefaultStemRadius; const float CoordAxes::DefaultTipLength = 5.0f; +#if ENABLE_GL_SHADERS_ATTRIBUTES +void CoordAxes::render(const Transform3d& trafo, float emission_factor) +#else void CoordAxes::render(float emission_factor) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES { #if ENABLE_GL_SHADERS_ATTRIBUTES auto render_axis = [this](GLShaderProgram& shader, const Transform3d& transform) { @@ -71,7 +75,7 @@ void CoordAxes::render(float emission_factor) m_arrow.set_color(-1, ColorRGBA::X()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_GL_SHADERS_ATTRIBUTES - render_axis(*shader, Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 })); + render_axis(*shader, trafo * Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 })); #else render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -83,7 +87,7 @@ void CoordAxes::render(float emission_factor) m_arrow.set_color(-1, ColorRGBA::Y()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_GL_SHADERS_ATTRIBUTES - render_axis(*shader, Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 })); + render_axis(*shader, trafo * Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 })); #else render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -95,7 +99,7 @@ void CoordAxes::render(float emission_factor) m_arrow.set_color(-1, ColorRGBA::Z()); #endif // ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_GL_SHADERS_ATTRIBUTES - render_axis(*shader, Geometry::assemble_transform(m_origin)); + render_axis(*shader, trafo * Geometry::assemble_transform(m_origin)); #else render_axis(Geometry::assemble_transform(m_origin).cast()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES diff --git a/src/slic3r/GUI/CoordAxes.hpp b/src/slic3r/GUI/CoordAxes.hpp index de5472b13..d2e38e184 100644 --- a/src/slic3r/GUI/CoordAxes.hpp +++ b/src/slic3r/GUI/CoordAxes.hpp @@ -48,7 +48,12 @@ public: float get_tip_radius() const { return m_tip_radius; } float get_tip_length() const { return m_tip_length; } float get_total_length() const { return m_stem_length + m_tip_length; } + +#if ENABLE_GL_SHADERS_ATTRIBUTES + void render(const Transform3d& trafo, float emission_factor = 0.0f); +#else void render(float emission_factor = 0.0f); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES }; } // GUI diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 50d4677c0..3bb2db1ac 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1584,7 +1584,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) shader->start_using(); glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); -} + } #endif // ENABLE_LEGACY_OPENGL_REMOVAL glsafe(::glEnable(GL_DEPTH_TEST)); @@ -1719,9 +1719,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #if ENABLE_WORLD_COORDINATE_SHOW_AXES if (!boost::starts_with(sidebar_field, "layer")) { shader->set_uniform("emission_factor", 0.1f); +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPushMatrix()); glsafe(::glTranslated(center.x(), center.y(), center.z())); glsafe(::glMultMatrixd(orient_matrix.data())); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES } #endif // ENABLE_WORLD_COORDINATE_SHOW_AXES @@ -1734,6 +1736,12 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) render_sidebar_scale_hints(sidebar_field, *shader, base_matrix * orient_matrix); else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field, *shader); +#if ENABLE_WORLD_COORDINATE_SHOW_AXES + if (!boost::starts_with(sidebar_field, "layer")) { + if (!wxGetApp().obj_manipul()->is_world_coordinates()) + m_axes.render(Geometry::assemble_transform(axes_center) * orient_matrix, 0.25f); + } +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES #else if (boost::starts_with(sidebar_field, "position")) render_sidebar_position_hints(sidebar_field); @@ -1757,9 +1765,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #endif // ENABLE_WORLD_COORDINATE_SHOW_AXES #endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES #if !ENABLE_LEGACY_OPENGL_REMOVAL if (!boost::starts_with(sidebar_field, "layer")) @@ -2345,6 +2355,8 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glLineWidth(2.0f * m_scale_factor)); + #if ENABLE_GL_SHADERS_ATTRIBUTES GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); #else From a4c0d99616579058d70904022cd6585904d71e8d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 11 Mar 2022 08:43:07 +0100 Subject: [PATCH 113/169] Tech ENABLE_GL_SHADERS_ATTRIBUTES - Fixed rendering of gizmo rotate Fixed conflicts during rebase with master --- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 26 +++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 5a0c99b76..c9947ee18 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -190,7 +190,11 @@ void GLGizmoRotate::on_render() glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader != nullptr) { shader->start_using(); @@ -652,7 +656,11 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick const double size = m_dragging ? double(m_grabbers.front().get_dragging_half_size(mean_size)) : double(m_grabbers.front().get_half_size(mean_size)); #if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat_attr" : "gouraud_light_attr"); +#else GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light"); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -747,10 +755,14 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const } } +#if ENABLE_WORLD_COORDINATE + return Geometry::assemble_transform(m_center) * m_orient_matrix * ret; +#else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) ret = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true) * ret; return Geometry::assemble_transform(m_center) * ret; +#endif // ENABLE_WORLD_COORDINATE } #else void GLGizmoRotate::transform_to_local(const Selection& selection) const @@ -844,8 +856,18 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) { if (mouse_event.Dragging() && m_dragging) { // Apply new temporary rotations - TransformationType transformation_type( - TransformationType::World_Relative_Joint); +#if ENABLE_WORLD_COORDINATE + TransformationType transformation_type; + switch (wxGetApp().obj_manipul()->get_coordinates_type()) + { + default: + case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; } + case ECoordinatesType::Instance: { transformation_type = TransformationType::Instance_Relative_Joint; break; } + case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; } + } +#else + TransformationType transformation_type(TransformationType::World_Relative_Joint); +#endif // ENABLE_WORLD_COORDINATE if (mouse_event.AltDown()) transformation_type.set_independent(); m_parent.get_selection().rotate(get_rotation(), transformation_type); } From 7e729632936836cf38d59910e5e11c1244cd6ee5 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 29 Apr 2022 13:51:58 +0200 Subject: [PATCH 114/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - 1st installment. Geometry::Transformation modified to store data in a single matrix, without store the matrix components Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 182 ++ src/libslic3r/Geometry.hpp | 116 +- src/libslic3r/Model.cpp | 69 +- src/libslic3r/Model.hpp | 2466 +++++++-------- src/libslic3r/Point.hpp | 1134 +++---- src/libslic3r/PrintApply.cpp | 2897 +++++++++--------- src/libslic3r/Technologies.hpp | 2 + src/slic3r/GUI/3DScene.hpp | 1594 +++++----- src/slic3r/GUI/GUI_ObjectList.cpp | 8 + src/slic3r/GUI/GUI_ObjectManipulation.cpp | 8 + src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 878 +++--- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 954 +++--- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 1980 ++++++------ src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 134 +- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 4 + src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 2790 ++++++++--------- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 30 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 63 +- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 4 + src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 2744 ++++++++--------- src/slic3r/GUI/MeshUtils.cpp | 748 ++--- src/slic3r/GUI/Plater.cpp | 10 + src/slic3r/GUI/Selection.cpp | 48 +- 23 files changed, 9768 insertions(+), 9095 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 58c90d9bc..fba1b2378 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -313,6 +313,34 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation, return transform; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void rotation_transform(Transform3d& transform, const Vec3d& rotation) +{ + transform = Transform3d::Identity(); + transform.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX())); +} + +Transform3d rotation_transform(const Vec3d& rotation) +{ + Transform3d transform; + rotation_transform(transform, rotation); + return transform; +} + +void scale_transform(Transform3d& transform, const Vec3d& scale) +{ + transform = Transform3d::Identity(); + transform.scale(scale); +} + +Transform3d scale_transform(const Vec3d& scale) +{ + Transform3d transform; + scale_transform(transform, scale); + return transform; +} +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix) { // reference: http://eecs.qmul.ac.uk/~gslabaugh/publications/euler.pdf @@ -363,6 +391,46 @@ Vec3d extract_euler_angles(const Transform3d& transform) return extract_euler_angles(m); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +Transform3d Transformation::get_offset_matrix() const +{ + return assemble_transform(get_offset()); +} + +static Transform3d extract_rotation(const Transform3d& trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return Transform3d(rotation); +} + +static Transform3d extract_scale(const Transform3d& trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return Transform3d(scale); +} + +static std::pair extract_rotation_scale(const Transform3d& trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return { Transform3d(rotation), Transform3d(scale) }; +} + +Vec3d Transformation::get_rotation() const +{ + return extract_euler_angles(extract_rotation(m_matrix)); +} + +Transform3d Transformation::get_rotation_matrix() const +{ + return extract_rotation(m_matrix); +} +#else bool Transformation::Flags::needs_update(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const { return (this->dont_translate != dont_translate) || (this->dont_rotate != dont_rotate) || (this->dont_scale != dont_scale) || (this->dont_mirror != dont_mirror); @@ -400,12 +468,19 @@ void Transformation::set_offset(Axis axis, double offset) m_dirty = true; } } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void Transformation::set_rotation(const Vec3d& rotation) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Vec3d offset = get_offset(); + m_matrix = rotation_transform(rotation) * extract_scale(m_matrix); + m_matrix.translation() = offset; +#else set_rotation(X, rotation.x()); set_rotation(Y, rotation.y()); set_rotation(Z, rotation.z()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } void Transformation::set_rotation(Axis axis, double rotation) @@ -414,32 +489,106 @@ void Transformation::set_rotation(Axis axis, double rotation) if (is_approx(std::abs(rotation), 2.0 * double(PI))) rotation = 0.0; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + auto [curr_rotation, scale] = extract_rotation_scale(m_matrix); + Vec3d angles = extract_euler_angles(curr_rotation); + angles[axis] = rotation; + + const Vec3d offset = get_offset(); + m_matrix = rotation_transform(angles) * scale; + m_matrix.translation() = offset; +#else if (m_rotation(axis) != rotation) { m_rotation(axis) = rotation; m_dirty = true; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +Vec3d Transformation::get_scaling_factor() const +{ + const Transform3d scale = extract_scale(m_matrix); + return { scale(0, 0), scale(1, 1), scale(2, 2) }; +} + +Transform3d Transformation::get_scaling_factor_matrix() const +{ + return extract_scale(m_matrix); +} +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + void Transformation::set_scaling_factor(const Vec3d& scaling_factor) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + assert(scaling_factor.x() > 0.0 && scaling_factor.y() > 0.0 && scaling_factor.z() > 0.0); + + const Vec3d offset = get_offset(); + m_matrix = extract_rotation(m_matrix) * scale_transform(scaling_factor); + m_matrix.translation() = offset; +#else set_scaling_factor(X, scaling_factor.x()); set_scaling_factor(Y, scaling_factor.y()); set_scaling_factor(Z, scaling_factor.z()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } void Transformation::set_scaling_factor(Axis axis, double scaling_factor) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + assert(scaling_factor > 0.0); + auto [rotation, scale] = extract_rotation_scale(m_matrix); + scale(axis, axis) = scaling_factor; + + const Vec3d offset = get_offset(); + m_matrix = rotation * scale; + m_matrix.translation() = offset; +#else if (m_scaling_factor(axis) != std::abs(scaling_factor)) { m_scaling_factor(axis) = std::abs(scaling_factor); m_dirty = true; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +Vec3d Transformation::get_mirror() const +{ + const Transform3d scale = extract_scale(m_matrix); + return { scale(0, 0) / std::abs(scale(0, 0)), scale(1, 1) / std::abs(scale(1, 1)), scale(2, 2) / std::abs(scale(2, 2)) }; +} + +Transform3d Transformation::get_mirror_matrix() const +{ + const Vec3d scale = get_scaling_factor(); + return scale_transform({ scale.x() / std::abs(scale.x()), scale.y() / std::abs(scale.y()), scale.z() / std::abs(scale.z()) }); +} +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + void Transformation::set_mirror(const Vec3d& mirror) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d copy(mirror); + const Vec3d abs_mirror = copy.cwiseAbs(); + for (int i = 0; i < 3; ++i) { + if (abs_mirror(i) == 0.0) + copy(i) = 1.0; + else if (abs_mirror(i) != 1.0) + copy(i) /= abs_mirror(i); + } + + const Vec3d curr_scale = get_scaling_factor(); + const Vec3d signs = curr_scale.cwiseProduct(copy); + set_scaling_factor({ + signs.x() < 0.0 ? std::abs(curr_scale.x()) * copy.x() : curr_scale.x(), + signs.y() < 0.0 ? std::abs(curr_scale.y()) * copy.y() : curr_scale.y(), + signs.z() < 0.0 ? std::abs(curr_scale.z()) * copy.z() : curr_scale.z() + }); +#else set_mirror(X, mirror.x()); set_mirror(Y, mirror.y()); set_mirror(Z, mirror.z()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } void Transformation::set_mirror(Axis axis, double mirror) @@ -450,12 +599,19 @@ void Transformation::set_mirror(Axis axis, double mirror) else if (abs_mirror != 1.0) mirror /= abs_mirror; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const double curr_scale = get_scaling_factor(axis); + const double sign = curr_scale * mirror; + set_scaling_factor(axis, sign < 0.0 ? std::abs(curr_scale) * mirror : curr_scale); +#else if (m_mirror(axis) != mirror) { m_mirror(axis) = mirror; m_dirty = true; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES void Transformation::set_from_transform(const Transform3d& transform) { // offset @@ -493,17 +649,38 @@ void Transformation::set_from_transform(const Transform3d& transform) // if (!m_matrix.isApprox(transform)) // std::cout << "something went wrong in extracting data from matrix" << std::endl; } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES void Transformation::reset() { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES m_offset = Vec3d::Zero(); m_rotation = Vec3d::Zero(); m_scaling_factor = Vec3d::Ones(); m_mirror = Vec3d::Ones(); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_matrix = Transform3d::Identity(); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES m_dirty = false; +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +Transform3d Transformation::get_matrix_no_offset() const +{ + Transformation copy(*this); + copy.reset_offset(); + return copy.get_matrix(); +} + +Transform3d Transformation::get_matrix_no_scaling_factor() const +{ + Transformation copy(*this); + copy.reset_scaling_factor(); + copy.reset_mirror(); + return copy.get_matrix(); +} +#else const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const { if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror)) { @@ -520,6 +697,7 @@ const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rot return m_matrix; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Transformation Transformation::operator * (const Transformation& other) const { @@ -533,7 +711,11 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation if (instance_transformation.is_scaling_uniform()) { // No need to run the non-linear least squares fitting for uniform scaling. // Just set the inverse. +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + out = instance_transformation.get_matrix_no_offset().inverse(); +#else out.set_from_transform(instance_transformation.get_matrix(true).inverse()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) { // Anisotropic scaling, rotation by multiples of ninety degrees. diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 2ca4ef884..ffe96cd3a 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -334,6 +334,26 @@ void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d // 6) translate Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +// Sets the given transform by assembling the given rotations in the following order: +// 1) rotate X +// 2) rotate Y +// 3) rotate Z +void rotation_transform(Transform3d& transform, const Vec3d& rotation); + +// Returns the transform obtained by assembling the given rotations in the following order: +// 1) rotate X +// 2) rotate Y +// 3) rotate Z +Transform3d rotation_transform(const Vec3d& rotation); + +// Sets the given transform by assembling the given scale factors +void scale_transform(Transform3d& transform, const Vec3d& scale); + +// Returns the transform obtained by assembling the given scale factors +Transform3d scale_transform(const Vec3d& scale); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + // Returns the euler angles extracted from the given rotation matrix // Warning -> The matrix should not contain any scale or shear !!! Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix); @@ -344,6 +364,9 @@ Vec3d extract_euler_angles(const Transform3d& transform); class Transformation { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d m_matrix{ Transform3d::Identity() }; +#else struct Flags { bool dont_translate{ true }; @@ -363,42 +386,104 @@ class Transformation mutable Transform3d m_matrix{ Transform3d::Identity() }; mutable Flags m_flags; mutable bool m_dirty{ false }; +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES public: +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transformation() = default; + explicit Transformation(const Transform3d & transform) : m_matrix(transform) {} +#else Transformation(); - explicit Transformation(const Transform3d& transform); + explicit Transformation(const Transform3d & transform); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transformation& operator = (const Transform3d& transform) { m_matrix = transform; return *this; } + + Vec3d get_offset() const { return m_matrix.translation(); } + double get_offset(Axis axis) const { return get_offset()[axis]; } + + Transform3d get_offset_matrix() const; + + void set_offset(const Vec3d& offset) { m_matrix.translation() = offset; } + void set_offset(Axis axis, double offset) { m_matrix.translation()[axis] = offset; } +#else const Vec3d& get_offset() const { return m_offset; } double get_offset(Axis axis) const { return m_offset(axis); } void set_offset(const Vec3d& offset); void set_offset(Axis axis, double offset); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_rotation() const; + double get_rotation(Axis axis) const { return get_rotation()[axis]; } + + Transform3d get_rotation_matrix() const; +#else const Vec3d& get_rotation() const { return m_rotation; } double get_rotation(Axis axis) const { return m_rotation(axis); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void set_rotation(const Vec3d& rotation); void set_rotation(Axis axis, double rotation); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_scaling_factor() const; + double get_scaling_factor(Axis axis) const { return get_scaling_factor()[axis]; } + + Transform3d get_scaling_factor_matrix() const; + + bool is_scaling_uniform() const { + const Vec3d scale = get_scaling_factor(); + return std::abs(scale.x() - scale.y()) < 1e-8 && std::abs(scale.x() - scale.z()) < 1e-8; + } +#else const Vec3d& get_scaling_factor() const { return m_scaling_factor; } double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void set_scaling_factor(const Vec3d& scaling_factor); void set_scaling_factor(Axis axis, double scaling_factor); + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_mirror() const; + double get_mirror(Axis axis) const { return get_mirror()[axis]; } + + Transform3d get_mirror_matrix() const; + + bool is_left_handed() const { + const Vec3d mirror = get_mirror(); + return mirror.x() * mirror.y() * mirror.z() < 0.0; + } +#else bool is_scaling_uniform() const { return std::abs(m_scaling_factor.x() - m_scaling_factor.y()) < 1e-8 && std::abs(m_scaling_factor.x() - m_scaling_factor.z()) < 1e-8; } const Vec3d& get_mirror() const { return m_mirror; } double get_mirror(Axis axis) const { return m_mirror(axis); } bool is_left_handed() const { return m_mirror.x() * m_mirror.y() * m_mirror.z() < 0.; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void set_mirror(const Vec3d& mirror); void set_mirror(Axis axis, double mirror); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES void set_from_transform(const Transform3d& transform); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES void reset(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void reset_offset() { set_offset(Vec3d::Zero()); } + void reset_rotation() { set_rotation(Vec3d::Zero()); } + void reset_scaling_factor() { set_scaling_factor(Vec3d::Ones()); } + void reset_mirror() { set_mirror(Vec3d::Ones()); } + const Transform3d& get_matrix() const { return m_matrix; } + Transform3d get_matrix_no_offset() const; + Transform3d get_matrix_no_scaling_factor() const; +#else const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Transformation operator * (const Transformation& other) const; @@ -408,15 +493,26 @@ public: static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox); private: - friend class cereal::access; - template void serialize(Archive & ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); } - explicit Transformation(int) : m_dirty(true) {} - template static void load_and_construct(Archive &ar, cereal::construct &construct) - { - // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. - construct(1); - ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror); - } + friend class cereal::access; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + template void serialize(Archive& ar) { ar(m_matrix); } + explicit Transformation(int) {} + template static void load_and_construct(Archive& ar, cereal::construct& construct) + { + // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. + construct(1); + ar(construct.ptr()->m_matrix); + } +#else + template void serialize(Archive& ar) { ar(m_offset, m_rotation, m_scaling_factor, m_mirror); } + explicit Transformation(int) : m_dirty(true) {} + template static void load_and_construct(Archive& ar, cereal::construct& construct) + { + // Calling a private constructor with special "int" parameter to indicate that no construction is necessary. + construct(1); + ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror); + } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES }; // For parsing a transformation matrix from 3MF / AMF. diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 0f48f2bb2..7fb7ed9f1 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -945,7 +945,11 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const if (this->instances.empty()) throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances"); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d inst_matrix = this->instances.front()->get_transformation().get_matrix_no_offset(); +#else const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES for (const ModelVolume *v : this->volumes) if (v->is_model_part()) m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); @@ -957,9 +961,15 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_translate) const { BoundingBoxf3 bb; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d inst_matrix = dont_translate ? + this->instances[instance_idx]->get_transformation().get_matrix_no_offset() : + this->instances[instance_idx]->get_transformation().get_matrix(); + +#else const Transform3d& inst_matrix = this->instances[instance_idx]->get_transformation().get_matrix(dont_translate); - for (ModelVolume *v : this->volumes) - { +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + for (ModelVolume *v : this->volumes) { if (v->is_model_part()) bb.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); } @@ -1368,9 +1378,12 @@ void ModelObject::split(ModelObjectPtrs* new_objects) new_object->add_instance(*model_instance); ModelVolume* new_vol = new_object->add_volume(*volume, std::move(mesh)); - for (ModelInstance* model_instance : new_object->instances) - { + for (ModelInstance* model_instance : new_object->instances) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d shift = model_instance->get_transformation().get_matrix_no_offset() * new_vol->get_offset(); +#else Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES model_instance->set_offset(model_instance->get_offset() + shift); } @@ -1434,8 +1447,18 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) // Adjust the meshes. // Transformation to be applied to the meshes. +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Geometry::Transformation reference_trafo_mod = reference_trafo; + reference_trafo_mod.reset_offset(); + if (uniform_scaling) + reference_trafo_mod.reset_scaling_factor(); + if (!has_mirrorring) + reference_trafo_mod.reset_mirror(); + Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo_mod.get_matrix().matrix().block<3, 3>(0, 0); +#else Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0); - Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix(); for (ModelVolume *model_volume : this->volumes) { const Geometry::Transformation volume_trafo = model_volume->get_transformation(); bool volume_left_handed = volume_trafo.is_left_handed(); @@ -1444,7 +1467,17 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) std::abs(volume_trafo.get_scaling_factor().x() - volume_trafo.get_scaling_factor().z()) < EPSILON; double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.; // Transform the mesh. - Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Geometry::Transformation volume_trafo_mod = volume_trafo; + volume_trafo_mod.reset_offset(); + if (volume_uniform_scaling) + volume_trafo_mod.reset_scaling_factor(); + if (!volume_has_mirrorring) + volume_trafo_mod.reset_mirror(); + Eigen::Matrix3d volume_trafo_3x3 = volume_trafo_mod.get_matrix().matrix().block<3, 3>(0, 0); +#else + Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Following method creates a new shared_ptr model_volume->transform_this_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed); // Reset the rotation, scaling and mirroring. @@ -1491,7 +1524,11 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const double min_z = DBL_MAX; const ModelInstance* inst = instances[instance_idx]; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d& mi = inst->get_matrix_no_offset(); +#else const Transform3d& mi = inst->get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES for (const ModelVolume* v : volumes) { if (!v->is_model_part()) @@ -1512,7 +1549,11 @@ double ModelObject::get_instance_max_z(size_t instance_idx) const double max_z = -DBL_MAX; const ModelInstance* inst = instances[instance_idx]; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d& mi = inst->get_matrix_no_offset(); +#else const Transform3d& mi = inst->get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES for (const ModelVolume* v : volumes) { if (!v->is_model_part()) @@ -1938,14 +1979,22 @@ void ModelVolume::convert_from_meters() void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + mesh->transform(dont_translate ? get_matrix_no_offset() : get_matrix()); +#else mesh->transform(get_matrix(dont_translate)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate) const { // Rotate around mesh origin. TriangleMesh copy(mesh); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + copy.transform(get_transformation().get_rotation_matrix()); +#else copy.transform(get_matrix(true, false, true, true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES BoundingBoxf3 bbox = copy.bounding_box(); if (!empty(bbox)) { @@ -1970,12 +2019,20 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mes BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + return bbox.transformed(dont_translate ? get_matrix_no_offset() : get_matrix()); +#else return bbox.transformed(get_matrix(dont_translate)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + return dont_translate ? get_matrix_no_offset() * v : get_matrix() * v; +#else return get_matrix(dont_translate) * v; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } void ModelInstance::transform_polygon(Polygon* polygon) const diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 0c95d98c0..f2844abcb 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1,1212 +1,1254 @@ -#ifndef slic3r_Model_hpp_ -#define slic3r_Model_hpp_ - -#include "libslic3r.h" -#include "enum_bitmask.hpp" -#include "Geometry.hpp" -#include "ObjectID.hpp" -#include "Point.hpp" -#include "PrintConfig.hpp" -#include "Slicing.hpp" -#include "SLA/SupportPoint.hpp" -#include "SLA/Hollowing.hpp" -#include "TriangleMesh.hpp" -#include "Arrange.hpp" -#include "CustomGCode.hpp" -#include "enum_bitmask.hpp" - -#include -#include -#include -#include -#include - -namespace cereal { - class BinaryInputArchive; - class BinaryOutputArchive; - template void load_optional(BinaryInputArchive &ar, std::shared_ptr &ptr); - template void save_optional(BinaryOutputArchive &ar, const std::shared_ptr &ptr); - template void load_by_value(BinaryInputArchive &ar, T &obj); - template void save_by_value(BinaryOutputArchive &ar, const T &obj); -} - -namespace Slic3r { -enum class ConversionType; - -class BuildVolume; -class Model; -class ModelInstance; -class ModelMaterial; -class ModelObject; -class ModelVolume; -class ModelWipeTower; -class Print; -class SLAPrint; -class TriangleSelector; - -namespace UndoRedo { - class StackImpl; -} - -class ModelConfigObject : public ObjectBase, public ModelConfig -{ -private: - friend class cereal::access; - friend class UndoRedo::StackImpl; - friend class ModelObject; - friend class ModelVolume; - friend class ModelMaterial; - - // Constructors to be only called by derived classes. - // Default constructor to assign a unique ID. - explicit ModelConfigObject() = default; - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - explicit ModelConfigObject(int) : ObjectBase(-1) {} - // Copy constructor copies the ID. - explicit ModelConfigObject(const ModelConfigObject &cfg) = default; - // Move constructor copies the ID. - explicit ModelConfigObject(ModelConfigObject &&cfg) = default; - - Timestamp timestamp() const throw() override { return this->ModelConfig::timestamp(); } - bool object_id_and_timestamp_match(const ModelConfigObject &rhs) const throw() { return this->id() == rhs.id() && this->timestamp() == rhs.timestamp(); } - - // called by ModelObject::assign_copy() - ModelConfigObject& operator=(const ModelConfigObject &rhs) = default; - ModelConfigObject& operator=(ModelConfigObject &&rhs) = default; - - template void serialize(Archive &ar) { - ar(cereal::base_class(this)); - } -}; - -namespace Internal { - template - class StaticSerializationWrapper - { - public: - StaticSerializationWrapper(T &wrap) : wrapped(wrap) {} - private: - friend class cereal::access; - friend class UndoRedo::StackImpl; - template void load(Archive &ar) { cereal::load_by_value(ar, wrapped); } - template void save(Archive &ar) const { cereal::save_by_value(ar, wrapped); } - T& wrapped; - }; -} - -typedef std::string t_model_material_id; -typedef std::string t_model_material_attribute; -typedef std::map t_model_material_attributes; - -typedef std::map ModelMaterialMap; -typedef std::vector ModelObjectPtrs; -typedef std::vector ModelVolumePtrs; -typedef std::vector ModelInstancePtrs; - -#define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ - /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \ - /* to make a private copy for background processing. */ \ - static TYPE* new_copy(const TYPE &rhs) { auto *ret = new TYPE(rhs); assert(ret->id() == rhs.id()); return ret; } \ - static TYPE* new_copy(TYPE &&rhs) { auto *ret = new TYPE(std::move(rhs)); assert(ret->id() == rhs.id()); return ret; } \ - static TYPE make_copy(const TYPE &rhs) { TYPE ret(rhs); assert(ret.id() == rhs.id()); return ret; } \ - static TYPE make_copy(TYPE &&rhs) { TYPE ret(std::move(rhs)); assert(ret.id() == rhs.id()); return ret; } \ - TYPE& assign_copy(const TYPE &rhs); \ - TYPE& assign_copy(TYPE &&rhs); \ - /* Copy a TYPE, generate new IDs. The front end will use this call. */ \ - static TYPE* new_clone(const TYPE &rhs) { \ - /* Default constructor assigning an invalid ID. */ \ - auto obj = new TYPE(-1); \ - obj->assign_clone(rhs); \ - assert(obj->id().valid() && obj->id() != rhs.id()); \ - return obj; \ - } \ - TYPE make_clone(const TYPE &rhs) { \ - /* Default constructor assigning an invalid ID. */ \ - TYPE obj(-1); \ - obj.assign_clone(rhs); \ - assert(obj.id().valid() && obj.id() != rhs.id()); \ - return obj; \ - } \ - TYPE& assign_clone(const TYPE &rhs) { \ - this->assign_copy(rhs); \ - assert(this->id().valid() && this->id() == rhs.id()); \ - this->assign_new_unique_ids_recursive(); \ - assert(this->id().valid() && this->id() != rhs.id()); \ - return *this; \ - } - -// Material, which may be shared across multiple ModelObjects of a single Model. -class ModelMaterial final : public ObjectBase -{ -public: - // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. - t_model_material_attributes attributes; - // Dynamic configuration storage for the object specific configuration values, overriding the global configuration. - ModelConfigObject config; - - Model* get_model() const { return m_model; } - void apply(const t_model_material_attributes &attributes) - { this->attributes.insert(attributes.begin(), attributes.end()); } - -private: - // Parent, owning this material. - Model *m_model; - - // To be accessed by the Model. - friend class Model; - // Constructor, which assigns a new unique ID to the material and to its config. - ModelMaterial(Model *model) : m_model(model) { assert(this->id().valid()); } - // Copy constructor copies the IDs of the ModelMaterial and its config, and m_model! - ModelMaterial(const ModelMaterial &rhs) = default; - void set_model(Model *model) { m_model = model; } - void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } - - // To be accessed by the serialization and Undo/Redo code. - friend class cereal::access; - friend class UndoRedo::StackImpl; - // Create an object for deserialization, don't allocate IDs for ModelMaterial and its config. - ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); } - template void serialize(Archive &ar) { - assert(this->id().invalid()); assert(this->config.id().invalid()); - Internal::StaticSerializationWrapper config_wrapper(config); - ar(attributes, config_wrapper); - // assert(this->id().valid()); assert(this->config.id().valid()); - } - - // Disabled methods. - ModelMaterial(ModelMaterial &&rhs) = delete; - ModelMaterial& operator=(const ModelMaterial &rhs) = delete; - ModelMaterial& operator=(ModelMaterial &&rhs) = delete; -}; - -class LayerHeightProfile final : public ObjectWithTimestamp { -public: - // Assign the content if the timestamp differs, don't assign an ObjectID. - void assign(const LayerHeightProfile &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } - void assign(LayerHeightProfile &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } - - std::vector get() const throw() { return m_data; } - bool empty() const throw() { return m_data.empty(); } - void set(const std::vector &data) { if (m_data != data) { m_data = data; this->touch(); } } - void set(std::vector &&data) { if (m_data != data) { m_data = std::move(data); this->touch(); } } - void clear() { m_data.clear(); this->touch(); } - - template void serialize(Archive &ar) - { - ar(cereal::base_class(this), m_data); - } - -private: - // Constructors to be only called by derived classes. - // Default constructor to assign a unique ID. - explicit LayerHeightProfile() = default; - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - explicit LayerHeightProfile(int) : ObjectWithTimestamp(-1) {} - // Copy constructor copies the ID. - explicit LayerHeightProfile(const LayerHeightProfile &rhs) = default; - // Move constructor copies the ID. - explicit LayerHeightProfile(LayerHeightProfile &&rhs) = default; - - // called by ModelObject::assign_copy() - LayerHeightProfile& operator=(const LayerHeightProfile &rhs) = default; - LayerHeightProfile& operator=(LayerHeightProfile &&rhs) = default; - - std::vector m_data; - - // to access set_new_unique_id() when copy / pasting an object - friend class ModelObject; -}; - -// Declared outside of ModelVolume, so it could be forward declared. -enum class ModelVolumeType : int { - INVALID = -1, - MODEL_PART = 0, - NEGATIVE_VOLUME, - PARAMETER_MODIFIER, - SUPPORT_BLOCKER, - SUPPORT_ENFORCER, -}; - -enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower }; -using ModelObjectCutAttributes = enum_bitmask; -ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); - -// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), -// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. -// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, -// different rotation and different uniform scaling. -class ModelObject final : public ObjectBase -{ -public: - std::string name; - std::string input_file; // XXX: consider fs::path - // Instances of this ModelObject. Each instance defines a shift on the print bed, rotation around the Z axis and a uniform scaling. - // Instances are owned by this ModelObject. - ModelInstancePtrs instances; - // Printable and modifier volumes, each with its material ID and a set of override parameters. - // ModelVolumes are owned by this ModelObject. - ModelVolumePtrs volumes; - // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings. - ModelConfigObject config; - // Variation of a layer thickness for spans of Z coordinates + optional parameter overrides. - t_layer_config_ranges layer_config_ranges; - // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. - // The pairs of are packed into a 1D array. - LayerHeightProfile layer_height_profile; - // Whether or not this object is printable - bool printable; - - // This vector holds position of selected support points for SLA. The data are - // saved in mesh coordinates to allow using them for several instances. - // The format is (x, y, z, point_size, supports_island) - sla::SupportPoints sla_support_points; - // To keep track of where the points came from (used for synchronization between - // the SLA gizmo and the backend). - sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints; - - // Holes to be drilled into the object so resin can flow out - sla::DrainHoles sla_drain_holes; - - /* This vector accumulates the total translation applied to the object by the - center_around_origin() method. Callers might want to apply the same translation - to new volumes before adding them to this object in order to preserve alignment - when user expects that. */ - Vec3d origin_translation; - - Model* get_model() { return m_model; } - const Model* get_model() const { return m_model; } - - ModelVolume* add_volume(const TriangleMesh &mesh); - ModelVolume* add_volume(TriangleMesh &&mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART); - ModelVolume* add_volume(const ModelVolume &volume, ModelVolumeType type = ModelVolumeType::INVALID); - ModelVolume* add_volume(const ModelVolume &volume, TriangleMesh &&mesh); - void delete_volume(size_t idx); - void clear_volumes(); - void sort_volumes(bool full_sort); - bool is_multiparts() const { return volumes.size() > 1; } - // Checks if any of object volume is painted using the fdm support painting gizmo. - bool is_fdm_support_painted() const; - // Checks if any of object volume is painted using the seam painting gizmo. - bool is_seam_painted() const; - // Checks if any of object volume is painted using the multi-material painting gizmo. - bool is_mm_painted() const; - - ModelInstance* add_instance(); - ModelInstance* add_instance(const ModelInstance &instance); - ModelInstance* add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation, const Vec3d &mirror); - void delete_instance(size_t idx); - void delete_last_instance(); - void clear_instances(); - - // Returns the bounding box of the transformed instances. - // This bounding box is approximate and not snug. - // This bounding box is being cached. - const BoundingBoxf3& bounding_box() const; - void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } - - // A mesh containing all transformed instances of this object. - TriangleMesh mesh() const; - // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. - // Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater. - TriangleMesh raw_mesh() const; - // The same as above, but producing a lightweight indexed_triangle_set. - indexed_triangle_set raw_indexed_triangle_set() const; - // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. - // This bounding box is only used for the actual slicing. - const BoundingBoxf3& raw_bounding_box() const; - // A snug bounding box around the transformed non-modifier object volumes. - BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; - // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. - const BoundingBoxf3& raw_mesh_bounding_box() const; - // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. - BoundingBoxf3 full_raw_mesh_bounding_box() const; - - // Calculate 2D convex hull of of a projection of the transformed printable volumes into the XY plane. - // This method is cheap in that it does not make any unnecessary copy of the volume meshes. - // This method is used by the auto arrange function. - Polygon convex_hull_2d(const Transform3d &trafo_instance) const; - - void center_around_origin(bool include_modifiers = true); - void ensure_on_bed(bool allow_negative_z = false); - - void translate_instances(const Vec3d& vector); - void translate_instance(size_t instance_idx, const Vec3d& vector); - void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); } - void translate(double x, double y, double z); - void scale(const Vec3d &versor); - void scale(const double s) { this->scale(Vec3d(s, s, s)); } - void scale(double x, double y, double z) { this->scale(Vec3d(x, y, z)); } - /// Scale the current ModelObject to fit by altering the scaling factor of ModelInstances. - /// It operates on the total size by duplicating the object according to all the instances. - /// \param size Sizef3 the size vector - void scale_to_fit(const Vec3d &size); - void rotate(double angle, Axis axis); - void rotate(double angle, const Vec3d& axis); - void mirror(Axis axis); - - // This method could only be called before the meshes of this ModelVolumes are not shared! - void scale_mesh_after_creation(const float scale); - void convert_units(ModelObjectPtrs&new_objects, ConversionType conv_type, std::vector volume_idxs); - - size_t materials_count() const; - size_t facets_count() const; - size_t parts_count() const; - ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); - void split(ModelObjectPtrs* new_objects); - void merge(); - // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, - // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. - // This situation is solved by baking in the instance transformation into the mesh vertices. - // Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well. - void bake_xy_rotation_into_meshes(size_t instance_idx); - - double get_min_z() const; - double get_max_z() const; - double get_instance_min_z(size_t instance_idx) const; - double get_instance_max_z(size_t instance_idx) const; - - // Print object statistics to console. - void print_info() const; - - std::string get_export_filename() const; - - // Get full stl statistics for all object's meshes - TriangleMeshStats get_object_stl_stats() const; - // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) - int get_repaired_errors_count(const int vol_idx = -1) const; - -private: - friend class Model; - // This constructor assigns new ID to this ModelObject and its config. - explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()), - m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) - { - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->layer_height_profile.id().valid()); - } - explicit ModelObject(int) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) - { - assert(this->id().invalid()); - assert(this->config.id().invalid()); - assert(this->layer_height_profile.id().invalid()); - } - ~ModelObject(); - void assign_new_unique_ids_recursive() override; - - // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" - // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). - ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(rhs.m_model) { - assert(this->id().invalid()); - assert(this->config.id().invalid()); - assert(this->layer_height_profile.id().invalid()); - assert(rhs.id() != rhs.config.id()); - assert(rhs.id() != rhs.layer_height_profile.id()); - this->assign_copy(rhs); - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->layer_height_profile.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->layer_height_profile.id()); - assert(this->id() == rhs.id()); - assert(this->config.id() == rhs.config.id()); - assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); - } - explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1) { - assert(this->id().invalid()); - assert(this->config.id().invalid()); - assert(this->layer_height_profile.id().invalid()); - assert(rhs.id() != rhs.config.id()); - assert(rhs.id() != rhs.layer_height_profile.id()); - this->assign_copy(std::move(rhs)); - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->layer_height_profile.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->layer_height_profile.id()); - assert(this->id() == rhs.id()); - assert(this->config.id() == rhs.config.id()); - assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); - } - ModelObject& operator=(const ModelObject &rhs) { - this->assign_copy(rhs); - m_model = rhs.m_model; - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->layer_height_profile.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->layer_height_profile.id()); - assert(this->id() == rhs.id()); - assert(this->config.id() == rhs.config.id()); - assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); - return *this; - } - ModelObject& operator=(ModelObject &&rhs) { - this->assign_copy(std::move(rhs)); - m_model = rhs.m_model; - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->layer_height_profile.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->layer_height_profile.id()); - assert(this->id() == rhs.id()); - assert(this->config.id() == rhs.config.id()); - assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); - return *this; - } - void set_new_unique_id() { - ObjectBase::set_new_unique_id(); - this->config.set_new_unique_id(); - this->layer_height_profile.set_new_unique_id(); - } - - OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) - - // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized. - Model *m_model = nullptr; - - // Bounding box, cached. - mutable BoundingBoxf3 m_bounding_box; - mutable bool m_bounding_box_valid; - mutable BoundingBoxf3 m_raw_bounding_box; - mutable bool m_raw_bounding_box_valid; - mutable BoundingBoxf3 m_raw_mesh_bounding_box; - mutable bool m_raw_mesh_bounding_box_valid; - - // Called by Print::apply() to set the model pointer after making a copy. - friend class Print; - friend class SLAPrint; - void set_model(Model *model) { m_model = model; } - - // Undo / Redo through the cereal serialization library - friend class cereal::access; - friend class UndoRedo::StackImpl; - // Used for deserialization -> Don't allocate any IDs for the ModelObject or its config. - ModelObject() : - ObjectBase(-1), config(-1), layer_height_profile(-1), - m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) { - assert(this->id().invalid()); - assert(this->config.id().invalid()); - assert(this->layer_height_profile.id().invalid()); - } - template void serialize(Archive &ar) { - ar(cereal::base_class(this)); - Internal::StaticSerializationWrapper config_wrapper(config); - Internal::StaticSerializationWrapper layer_heigth_profile_wrapper(layer_height_profile); - ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper, - sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, - m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); - } - - // Called by Print::validate() from the UI thread. - unsigned int update_instances_print_volume_state(const BuildVolume &build_volume); -}; - -enum class EnforcerBlockerType : int8_t { - // Maximum is 3. The value is serialized in TriangleSelector into 2 bits. - NONE = 0, - ENFORCER = 1, - BLOCKER = 2, - // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. - Extruder1 = ENFORCER, - Extruder2 = BLOCKER, - Extruder3, - Extruder4, - Extruder5, - Extruder6, - Extruder7, - Extruder8, - Extruder9, - Extruder10, - Extruder11, - Extruder12, - Extruder13, - Extruder14, - Extruder15, -}; - -enum class ConversionType : int { - CONV_TO_INCH, - CONV_FROM_INCH, - CONV_TO_METER, - CONV_FROM_METER, -}; - -class FacetsAnnotation final : public ObjectWithTimestamp { -public: - // Assign the content if the timestamp differs, don't assign an ObjectID. - void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } - void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } - const std::pair>, std::vector>& get_data() const throw() { return m_data; } - bool set(const TriangleSelector& selector); - indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; - indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const; - bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const; - bool empty() const { return m_data.first.empty(); } - - // Following method clears the config and increases its timestamp, so the deleted - // state is considered changed from perspective of the undo/redo stack. - void reset(); - - // Serialize triangle into string, for serialization into 3MF/AMF. - std::string get_triangle_as_string(int i) const; - - // Before deserialization, reserve space for n_triangles. - void reserve(int n_triangles) { m_data.first.reserve(n_triangles); } - // Deserialize triangles one by one, with strictly increasing triangle_id. - void set_triangle_from_string(int triangle_id, const std::string& str); - // After deserializing the last triangle, shrink data to fit. - void shrink_to_fit() { m_data.first.shrink_to_fit(); m_data.second.shrink_to_fit(); } - -private: - // Constructors to be only called by derived classes. - // Default constructor to assign a unique ID. - explicit FacetsAnnotation() = default; - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - explicit FacetsAnnotation(int) : ObjectWithTimestamp(-1) {} - // Copy constructor copies the ID. - explicit FacetsAnnotation(const FacetsAnnotation &rhs) = default; - // Move constructor copies the ID. - explicit FacetsAnnotation(FacetsAnnotation &&rhs) = default; - - // called by ModelVolume::assign_copy() - FacetsAnnotation& operator=(const FacetsAnnotation &rhs) = default; - FacetsAnnotation& operator=(FacetsAnnotation &&rhs) = default; - - friend class cereal::access; - friend class UndoRedo::StackImpl; - - template void serialize(Archive &ar) - { - ar(cereal::base_class(this), m_data); - } - - std::pair>, std::vector> m_data; - - // To access set_new_unique_id() when copy / pasting a ModelVolume. - friend class ModelVolume; -}; - -// An object STL, or a modifier volume, over which a different set of parameters shall be applied. -// ModelVolume instances are owned by a ModelObject. -class ModelVolume final : public ObjectBase -{ -public: - std::string name; - // struct used by reload from disk command to recover data from disk - struct Source - { - std::string input_file; - int object_idx{ -1 }; - int volume_idx{ -1 }; - Vec3d mesh_offset{ Vec3d::Zero() }; - Geometry::Transformation transform; - bool is_converted_from_inches{ false }; - bool is_converted_from_meters{ false }; - bool is_from_builtin_objects{ false }; - - template void serialize(Archive& ar) { - //FIXME Vojtech: Serialize / deserialize only if the Source is set. - // likely testing input_file or object_idx would be sufficient. - ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters, is_from_builtin_objects); - } - }; - Source source; - - // The triangular model. - const TriangleMesh& mesh() const { return *m_mesh.get(); } - void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } - void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } - void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared(mesh); } - void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } - void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } - void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } - void reset_mesh() { m_mesh = std::make_shared(); } - // Configuration parameters specific to an object model geometry or a modifier volume, - // overriding the global Slic3r settings and the ModelObject settings. - ModelConfigObject config; - - // List of mesh facets to be supported/unsupported. - FacetsAnnotation supported_facets; - - // List of seam enforcers/blockers. - FacetsAnnotation seam_facets; - - // List of mesh facets painted for MMU segmentation. - FacetsAnnotation mmu_segmentation_facets; - - // A parent object owning this modifier volume. - ModelObject* get_object() const { return this->object; } - ModelVolumeType type() const { return m_type; } - void set_type(const ModelVolumeType t) { m_type = t; } - bool is_model_part() const { return m_type == ModelVolumeType::MODEL_PART; } - bool is_negative_volume() const { return m_type == ModelVolumeType::NEGATIVE_VOLUME; } - bool is_modifier() const { return m_type == ModelVolumeType::PARAMETER_MODIFIER; } - bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; } - bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } - bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } - t_model_material_id material_id() const { return m_material_id; } - void set_material_id(t_model_material_id material_id); - ModelMaterial* material() const; - void set_material(t_model_material_id material_id, const ModelMaterial &material); - // Extract the current extruder ID based on this ModelVolume's config and the parent ModelObject's config. - // Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes). - int extruder_id() const; - - bool is_splittable() const; - - // Split this volume, append the result to the object owning this volume. - // Return the number of volumes created from this one. - // This is useful to assign different materials to different volumes of an object. - size_t split(unsigned int max_extruders); - void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); } - void translate(const Vec3d& displacement); - void scale(const Vec3d& scaling_factors); - void scale(double x, double y, double z) { scale(Vec3d(x, y, z)); } - void scale(double s) { scale(Vec3d(s, s, s)); } - void rotate(double angle, Axis axis); - void rotate(double angle, const Vec3d& axis); - void mirror(Axis axis); - - // This method could only be called before the meshes of this ModelVolumes are not shared! - void scale_geometry_after_creation(const Vec3f &versor); - void scale_geometry_after_creation(const float scale) { this->scale_geometry_after_creation(Vec3f(scale, scale, scale)); } - - // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box. - // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared! - void center_geometry_after_creation(bool update_source_offset = true); - - void calculate_convex_hull(); - const TriangleMesh& get_convex_hull() const; - const std::shared_ptr& get_convex_hull_shared_ptr() const { return m_convex_hull; } - // Get count of errors in the mesh - int get_repaired_errors_count() const; - - // Helpers for loading / storing into AMF / 3MF files. - static ModelVolumeType type_from_string(const std::string &s); - static std::string type_to_string(const ModelVolumeType t); - - const Geometry::Transformation& get_transformation() const { return m_transformation; } - void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } - void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); } - - const Vec3d& get_offset() const { return m_transformation.get_offset(); } - double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } - - void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } - void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } - - const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } - double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } - - void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } - void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } - - Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } - double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } - - void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } - void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } - - const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } - double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } - bool is_left_handed() const { return m_transformation.is_left_handed(); } - - void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } - void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } - void convert_from_imperial_units(); - void convert_from_meters(); - - const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - - void set_new_unique_id() { - ObjectBase::set_new_unique_id(); - this->config.set_new_unique_id(); - this->supported_facets.set_new_unique_id(); - this->seam_facets.set_new_unique_id(); - this->mmu_segmentation_facets.set_new_unique_id(); - } - - bool is_fdm_support_painted() const { return !this->supported_facets.empty(); } - bool is_seam_painted() const { return !this->seam_facets.empty(); } - bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } - -protected: - friend class Print; - friend class SLAPrint; - friend class Model; - friend class ModelObject; - friend void model_volume_list_update_supports(ModelObject& model_object_dst, const ModelObject& model_object_new); - - // Copies IDs of both the ModelVolume and its config. - explicit ModelVolume(const ModelVolume &rhs) = default; - void set_model_object(ModelObject *model_object) { object = model_object; } - void assign_new_unique_ids_recursive() override; - void transform_this_mesh(const Transform3d& t, bool fix_left_handed); - void transform_this_mesh(const Matrix3d& m, bool fix_left_handed); - -private: - // Parent object owning this ModelVolume. - ModelObject* object; - // The triangular model. - std::shared_ptr m_mesh; - // Is it an object to be printed, or a modifier volume? - ModelVolumeType m_type; - t_model_material_id m_material_id; - // The convex hull of this model's mesh. - std::shared_ptr m_convex_hull; - Geometry::Transformation m_transformation; - - // flag to optimize the checking if the volume is splittable - // -1 -> is unknown value (before first cheking) - // 0 -> is not splittable - // 1 -> is splittable - mutable int m_is_splittable{ -1 }; - - ModelVolume(ModelObject *object, const TriangleMesh &mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART) : m_mesh(new TriangleMesh(mesh)), m_type(type), object(object) - { - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->supported_facets.id().valid()); - assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->supported_facets.id()); - assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); - if (mesh.facets_count() > 1) - calculate_convex_hull(); - } - ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull, ModelVolumeType type = ModelVolumeType::MODEL_PART) : - m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(type), object(object) { - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->supported_facets.id().valid()); - assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->supported_facets.id()); - assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); - } - - // Copying an existing volume, therefore this volume will get a copy of the ID assigned. - ModelVolume(ModelObject *object, const ModelVolume &other) : - ObjectBase(other), - name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), - config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), - supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets) - { - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->supported_facets.id().valid()); - assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->supported_facets.id()); - assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); - assert(this->id() == other.id()); - assert(this->config.id() == other.config.id()); - assert(this->supported_facets.id() == other.supported_facets.id()); - assert(this->seam_facets.id() == other.seam_facets.id()); - assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id()); - this->set_material_id(other.material_id()); - } - // Providing a new mesh, therefore this volume will get a new unique ID assigned. - ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) : - name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation) - { - assert(this->id().valid()); - assert(this->config.id().valid()); - assert(this->supported_facets.id().valid()); - assert(this->seam_facets.id().valid()); - assert(this->mmu_segmentation_facets.id().valid()); - assert(this->id() != this->config.id()); - assert(this->id() != this->supported_facets.id()); - assert(this->id() != this->seam_facets.id()); - assert(this->id() != this->mmu_segmentation_facets.id()); - assert(this->id() != other.id()); - assert(this->config.id() == other.config.id()); - this->set_material_id(other.material_id()); - this->config.set_new_unique_id(); - if (m_mesh->facets_count() > 1) - calculate_convex_hull(); - assert(this->config.id().valid()); - assert(this->config.id() != other.config.id()); - assert(this->supported_facets.id() != other.supported_facets.id()); - assert(this->seam_facets.id() != other.seam_facets.id()); - assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id()); - assert(this->id() != this->config.id()); - assert(this->supported_facets.empty()); - assert(this->seam_facets.empty()); - assert(this->mmu_segmentation_facets.empty()); - } - - ModelVolume& operator=(ModelVolume &rhs) = delete; - - friend class cereal::access; - friend class UndoRedo::StackImpl; - // Used for deserialization, therefore no IDs are allocated. - ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) { - assert(this->id().invalid()); - assert(this->config.id().invalid()); - assert(this->supported_facets.id().invalid()); - assert(this->seam_facets.id().invalid()); - assert(this->mmu_segmentation_facets.id().invalid()); - } - template void load(Archive &ar) { - bool has_convex_hull; - ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); - cereal::load_by_value(ar, supported_facets); - cereal::load_by_value(ar, seam_facets); - cereal::load_by_value(ar, mmu_segmentation_facets); - cereal::load_by_value(ar, config); - assert(m_mesh); - if (has_convex_hull) { - cereal::load_optional(ar, m_convex_hull); - if (! m_convex_hull && ! m_mesh->empty()) - // The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it. - this->calculate_convex_hull(); - } else - m_convex_hull.reset(); - } - template void save(Archive &ar) const { - bool has_convex_hull = m_convex_hull.get() != nullptr; - ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); - cereal::save_by_value(ar, supported_facets); - cereal::save_by_value(ar, seam_facets); - cereal::save_by_value(ar, mmu_segmentation_facets); - cereal::save_by_value(ar, config); - if (has_convex_hull) - cereal::save_optional(ar, m_convex_hull); - } -}; - -inline void model_volumes_sort_by_id(ModelVolumePtrs &model_volumes) -{ - std::sort(model_volumes.begin(), model_volumes.end(), [](const ModelVolume *l, const ModelVolume *r) { return l->id() < r->id(); }); -} - -inline const ModelVolume* model_volume_find_by_id(const ModelVolumePtrs &model_volumes, const ObjectID id) -{ - auto it = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [id](const ModelVolume *mv) { return mv->id() < id; }); - return it != model_volumes.end() && (*it)->id() == id ? *it : nullptr; -} - -enum ModelInstanceEPrintVolumeState : unsigned char -{ - ModelInstancePVS_Inside, - ModelInstancePVS_Partly_Outside, - ModelInstancePVS_Fully_Outside, - ModelInstanceNum_BedStates -}; - -// A single instance of a ModelObject. -// Knows the affine transformation of an object. -class ModelInstance final : public ObjectBase -{ -private: - Geometry::Transformation m_transformation; - -public: - // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) - ModelInstanceEPrintVolumeState print_volume_state; - // Whether or not this instance is printable - bool printable; - - ModelObject* get_object() const { return this->object; } - - const Geometry::Transformation& get_transformation() const { return m_transformation; } - void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } - - const Vec3d& get_offset() const { return m_transformation.get_offset(); } - double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } - - void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } - void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } - - const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } - double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } - - void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } - void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } - - const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); } - double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } - - void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } - void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } - - const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } - double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } - bool is_left_handed() const { return m_transformation.is_left_handed(); } - - void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } - void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } - - // To be called on an external mesh - void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; - // Calculate a bounding box of a transformed mesh. To be called on an external mesh. - BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const; - // Transform an external bounding box. - BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const; - // Transform an external vector. - Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const; - // To be called on an external polygon. It does not translate the polygon, only rotates and scales. - void transform_polygon(Polygon* polygon) const; - - const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - - bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); } - - // Getting the input polygon for arrange - arrangement::ArrangePolygon get_arrange_polygon() const; - - // Apply the arrange result on the ModelInstance - void apply_arrange_result(const Vec2d& offs, double rotation) - { - // write the transformation data into the model instance - set_rotation(Z, rotation); - set_offset(X, unscale(offs(X))); - set_offset(Y, unscale(offs(Y))); - this->object->invalidate_bounding_box(); - } - -protected: - friend class Print; - friend class SLAPrint; - friend class Model; - friend class ModelObject; - - explicit ModelInstance(const ModelInstance &rhs) = default; - void set_model_object(ModelObject *model_object) { object = model_object; } - -private: - // Parent object, owning this instance. - ModelObject* object; - - // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), printable(true), object(object) { assert(this->id().valid()); } - // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject *object, const ModelInstance &other) : - m_transformation(other.m_transformation), print_volume_state(ModelInstancePVS_Inside), printable(other.printable), object(object) { assert(this->id().valid() && this->id() != other.id()); } - - explicit ModelInstance(ModelInstance &&rhs) = delete; - ModelInstance& operator=(const ModelInstance &rhs) = delete; - ModelInstance& operator=(ModelInstance &&rhs) = delete; - - friend class cereal::access; - friend class UndoRedo::StackImpl; - // Used for deserialization, therefore no IDs are allocated. - ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); } - template void serialize(Archive &ar) { - ar(m_transformation, print_volume_state, printable); - } -}; - - -class ModelWipeTower final : public ObjectBase -{ -public: - Vec2d position; - double rotation; - -private: - friend class cereal::access; - friend class UndoRedo::StackImpl; - friend class Model; - - // Constructors to be only called by derived classes. - // Default constructor to assign a unique ID. - explicit ModelWipeTower() {} - // Constructor with ignored int parameter to assign an invalid ID, to be replaced - // by an existing ID copied from elsewhere. - explicit ModelWipeTower(int) : ObjectBase(-1) {} - // Copy constructor copies the ID. - explicit ModelWipeTower(const ModelWipeTower &cfg) = default; - - // Disabled methods. - ModelWipeTower(ModelWipeTower &&rhs) = delete; - ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete; - ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete; - - // For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object. - template void serialize(Archive &ar) { ar(position, rotation); } -}; - -// The print bed content. -// Description of a triangular model with multiple materials, multiple instances with various affine transformations -// and with multiple modifier meshes. -// A model groups multiple objects, each object having possibly multiple instances, -// all objects may share mutliple materials. -class Model final : public ObjectBase -{ -public: - // Materials are owned by a model and referenced by objects through t_model_material_id. - // Single material may be shared by multiple models. - ModelMaterialMap materials; - // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation). - ModelObjectPtrs objects; - // Wipe tower object. - ModelWipeTower wipe_tower; - - // Extensions for color print - CustomGCode::Info custom_gcode_per_print_z; - - // Default constructor assigns a new ID to the model. - Model() { assert(this->id().valid()); } - ~Model() { this->clear_objects(); this->clear_materials(); } - - /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ - /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ - Model(const Model &rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); } - explicit Model(Model &&rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); } - Model& operator=(const Model &rhs) { this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } - Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } - - OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) - - enum class LoadAttribute : int { - AddDefaultInstances, - CheckVersion - }; - using LoadAttributes = enum_bitmask; - - static Model read_from_file( - const std::string& input_file, - DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr, - LoadAttributes options = LoadAttribute::AddDefaultInstances); - static Model read_from_archive( - const std::string& input_file, - DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, - LoadAttributes options = LoadAttribute::AddDefaultInstances); - - // Add a new ModelObject to this Model, generate a new ID for this ModelObject. - ModelObject* add_object(); - ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); - ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); - ModelObject* add_object(const ModelObject &other); - void delete_object(size_t idx); - bool delete_object(ObjectID id); - bool delete_object(ModelObject* object); - void clear_objects(); - - ModelMaterial* add_material(t_model_material_id material_id); - ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other); - ModelMaterial* get_material(t_model_material_id material_id) { - ModelMaterialMap::iterator i = this->materials.find(material_id); - return (i == this->materials.end()) ? nullptr : i->second; - } - - void delete_material(t_model_material_id material_id); - void clear_materials(); - bool add_default_instances(); - // Returns approximate axis aligned bounding box of this model - BoundingBoxf3 bounding_box() const; - // Set the print_volume_state of PrintObject::instances, - // return total number of printable objects. - unsigned int update_print_volume_state(const BuildVolume &build_volume); - // Returns true if any ModelObject was modified. - bool center_instances_around_point(const Vec2d &point); - void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } - TriangleMesh mesh() const; - - // Croaks if the duplicated objects do not fit the print bed. - void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); - - bool looks_like_multipart_object() const; - void convert_multipart_object(unsigned int max_extruders); - bool looks_like_imperial_units() const; - void convert_from_imperial_units(bool only_small_volumes); - bool looks_like_saved_in_meters() const; - void convert_from_meters(bool only_small_volumes); - int removed_objects_with_zero_volume(); - - // Ensures that the min z of the model is not negative - void adjust_min_z(); - - void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } - - // Propose an output file name & path based on the first printable object's name and source input file's path. - std::string propose_export_file_name_and_path() const; - // Propose an output path, replace extension. The new_extension shall contain the initial dot. - std::string propose_export_file_name_and_path(const std::string &new_extension) const; - - // Checks if any of objects is painted using the fdm support painting gizmo. - bool is_fdm_support_painted() const; - // Checks if any of objects is painted using the seam painting gizmo. - bool is_seam_painted() const; - // Checks if any of objects is painted using the multi-material painting gizmo. - bool is_mm_painted() const; - -private: - explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); } - void assign_new_unique_ids_recursive(); - void update_links_bottom_up_recursive(); - - friend class cereal::access; - friend class UndoRedo::StackImpl; - template void serialize(Archive &ar) { - Internal::StaticSerializationWrapper wipe_tower_wrapper(wipe_tower); - ar(materials, objects, wipe_tower_wrapper); - } -}; - -ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute) - -#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE -#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE - -// Test whether the two models contain the same number of ModelObjects with the same set of IDs -// ordered in the same order. In that case it is not necessary to kill the background processing. -bool model_object_list_equal(const Model &model_old, const Model &model_new); - -// Test whether the new model is just an extension of the old model (new objects were added -// to the end of the original list. In that case it is not necessary to kill the background processing. -bool model_object_list_extended(const Model &model_old, const Model &model_new); - -// Test whether the new ModelObject contains a different set of volumes (or sorted in a different order) -// than the old ModelObject. -bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type); -bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const std::initializer_list &types); - -// Test whether the now ModelObject has newer custom supports data than the old one. -// The function assumes that volumes list is synchronized. -bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new); - -// Test whether the now ModelObject has newer custom seam data than the old one. -// The function assumes that volumes list is synchronized. -bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new); - -// Test whether the now ModelObject has newer MMU segmentation data than the old one. -// The function assumes that volumes list is synchronized. -extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new); - -// If the model has multi-part objects, then it is currently not supported by the SLA mode. -// Either the model cannot be loaded, or a SLA printer has to be activated. -bool model_has_multi_part_objects(const Model &model); -// If the model has advanced features, then it cannot be processed in simple mode. -bool model_has_advanced_features(const Model &model); - -#ifndef NDEBUG -// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. -void check_model_ids_validity(const Model &model); -void check_model_ids_equal(const Model &model1, const Model &model2); -#endif /* NDEBUG */ - -static const float SINKING_Z_THRESHOLD = -0.001f; -static const double SINKING_MIN_Z_THRESHOLD = 0.05; - -} // namespace Slic3r - -namespace cereal -{ - template struct specialize {}; - template struct specialize {}; -} - -#endif /* slic3r_Model_hpp_ */ +#ifndef slic3r_Model_hpp_ +#define slic3r_Model_hpp_ + +#include "libslic3r.h" +#include "enum_bitmask.hpp" +#include "Geometry.hpp" +#include "ObjectID.hpp" +#include "Point.hpp" +#include "PrintConfig.hpp" +#include "Slicing.hpp" +#include "SLA/SupportPoint.hpp" +#include "SLA/Hollowing.hpp" +#include "TriangleMesh.hpp" +#include "Arrange.hpp" +#include "CustomGCode.hpp" +#include "enum_bitmask.hpp" + +#include +#include +#include +#include +#include + +namespace cereal { + class BinaryInputArchive; + class BinaryOutputArchive; + template void load_optional(BinaryInputArchive &ar, std::shared_ptr &ptr); + template void save_optional(BinaryOutputArchive &ar, const std::shared_ptr &ptr); + template void load_by_value(BinaryInputArchive &ar, T &obj); + template void save_by_value(BinaryOutputArchive &ar, const T &obj); +} + +namespace Slic3r { +enum class ConversionType; + +class BuildVolume; +class Model; +class ModelInstance; +class ModelMaterial; +class ModelObject; +class ModelVolume; +class ModelWipeTower; +class Print; +class SLAPrint; +class TriangleSelector; + +namespace UndoRedo { + class StackImpl; +} + +class ModelConfigObject : public ObjectBase, public ModelConfig +{ +private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + friend class ModelObject; + friend class ModelVolume; + friend class ModelMaterial; + + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit ModelConfigObject() = default; + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit ModelConfigObject(int) : ObjectBase(-1) {} + // Copy constructor copies the ID. + explicit ModelConfigObject(const ModelConfigObject &cfg) = default; + // Move constructor copies the ID. + explicit ModelConfigObject(ModelConfigObject &&cfg) = default; + + Timestamp timestamp() const throw() override { return this->ModelConfig::timestamp(); } + bool object_id_and_timestamp_match(const ModelConfigObject &rhs) const throw() { return this->id() == rhs.id() && this->timestamp() == rhs.timestamp(); } + + // called by ModelObject::assign_copy() + ModelConfigObject& operator=(const ModelConfigObject &rhs) = default; + ModelConfigObject& operator=(ModelConfigObject &&rhs) = default; + + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + } +}; + +namespace Internal { + template + class StaticSerializationWrapper + { + public: + StaticSerializationWrapper(T &wrap) : wrapped(wrap) {} + private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + template void load(Archive &ar) { cereal::load_by_value(ar, wrapped); } + template void save(Archive &ar) const { cereal::save_by_value(ar, wrapped); } + T& wrapped; + }; +} + +typedef std::string t_model_material_id; +typedef std::string t_model_material_attribute; +typedef std::map t_model_material_attributes; + +typedef std::map ModelMaterialMap; +typedef std::vector ModelObjectPtrs; +typedef std::vector ModelVolumePtrs; +typedef std::vector ModelInstancePtrs; + +#define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \ + /* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \ + /* to make a private copy for background processing. */ \ + static TYPE* new_copy(const TYPE &rhs) { auto *ret = new TYPE(rhs); assert(ret->id() == rhs.id()); return ret; } \ + static TYPE* new_copy(TYPE &&rhs) { auto *ret = new TYPE(std::move(rhs)); assert(ret->id() == rhs.id()); return ret; } \ + static TYPE make_copy(const TYPE &rhs) { TYPE ret(rhs); assert(ret.id() == rhs.id()); return ret; } \ + static TYPE make_copy(TYPE &&rhs) { TYPE ret(std::move(rhs)); assert(ret.id() == rhs.id()); return ret; } \ + TYPE& assign_copy(const TYPE &rhs); \ + TYPE& assign_copy(TYPE &&rhs); \ + /* Copy a TYPE, generate new IDs. The front end will use this call. */ \ + static TYPE* new_clone(const TYPE &rhs) { \ + /* Default constructor assigning an invalid ID. */ \ + auto obj = new TYPE(-1); \ + obj->assign_clone(rhs); \ + assert(obj->id().valid() && obj->id() != rhs.id()); \ + return obj; \ + } \ + TYPE make_clone(const TYPE &rhs) { \ + /* Default constructor assigning an invalid ID. */ \ + TYPE obj(-1); \ + obj.assign_clone(rhs); \ + assert(obj.id().valid() && obj.id() != rhs.id()); \ + return obj; \ + } \ + TYPE& assign_clone(const TYPE &rhs) { \ + this->assign_copy(rhs); \ + assert(this->id().valid() && this->id() == rhs.id()); \ + this->assign_new_unique_ids_recursive(); \ + assert(this->id().valid() && this->id() != rhs.id()); \ + return *this; \ + } + +// Material, which may be shared across multiple ModelObjects of a single Model. +class ModelMaterial final : public ObjectBase +{ +public: + // Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose. + t_model_material_attributes attributes; + // Dynamic configuration storage for the object specific configuration values, overriding the global configuration. + ModelConfigObject config; + + Model* get_model() const { return m_model; } + void apply(const t_model_material_attributes &attributes) + { this->attributes.insert(attributes.begin(), attributes.end()); } + +private: + // Parent, owning this material. + Model *m_model; + + // To be accessed by the Model. + friend class Model; + // Constructor, which assigns a new unique ID to the material and to its config. + ModelMaterial(Model *model) : m_model(model) { assert(this->id().valid()); } + // Copy constructor copies the IDs of the ModelMaterial and its config, and m_model! + ModelMaterial(const ModelMaterial &rhs) = default; + void set_model(Model *model) { m_model = model; } + void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); } + + // To be accessed by the serialization and Undo/Redo code. + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Create an object for deserialization, don't allocate IDs for ModelMaterial and its config. + ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); } + template void serialize(Archive &ar) { + assert(this->id().invalid()); assert(this->config.id().invalid()); + Internal::StaticSerializationWrapper config_wrapper(config); + ar(attributes, config_wrapper); + // assert(this->id().valid()); assert(this->config.id().valid()); + } + + // Disabled methods. + ModelMaterial(ModelMaterial &&rhs) = delete; + ModelMaterial& operator=(const ModelMaterial &rhs) = delete; + ModelMaterial& operator=(ModelMaterial &&rhs) = delete; +}; + +class LayerHeightProfile final : public ObjectWithTimestamp { +public: + // Assign the content if the timestamp differs, don't assign an ObjectID. + void assign(const LayerHeightProfile &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } + void assign(LayerHeightProfile &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } + + std::vector get() const throw() { return m_data; } + bool empty() const throw() { return m_data.empty(); } + void set(const std::vector &data) { if (m_data != data) { m_data = data; this->touch(); } } + void set(std::vector &&data) { if (m_data != data) { m_data = std::move(data); this->touch(); } } + void clear() { m_data.clear(); this->touch(); } + + template void serialize(Archive &ar) + { + ar(cereal::base_class(this), m_data); + } + +private: + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit LayerHeightProfile() = default; + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit LayerHeightProfile(int) : ObjectWithTimestamp(-1) {} + // Copy constructor copies the ID. + explicit LayerHeightProfile(const LayerHeightProfile &rhs) = default; + // Move constructor copies the ID. + explicit LayerHeightProfile(LayerHeightProfile &&rhs) = default; + + // called by ModelObject::assign_copy() + LayerHeightProfile& operator=(const LayerHeightProfile &rhs) = default; + LayerHeightProfile& operator=(LayerHeightProfile &&rhs) = default; + + std::vector m_data; + + // to access set_new_unique_id() when copy / pasting an object + friend class ModelObject; +}; + +// Declared outside of ModelVolume, so it could be forward declared. +enum class ModelVolumeType : int { + INVALID = -1, + MODEL_PART = 0, + NEGATIVE_VOLUME, + PARAMETER_MODIFIER, + SUPPORT_BLOCKER, + SUPPORT_ENFORCER, +}; + +enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower }; +using ModelObjectCutAttributes = enum_bitmask; +ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); + +// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), +// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. +// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, +// different rotation and different uniform scaling. +class ModelObject final : public ObjectBase +{ +public: + std::string name; + std::string input_file; // XXX: consider fs::path + // Instances of this ModelObject. Each instance defines a shift on the print bed, rotation around the Z axis and a uniform scaling. + // Instances are owned by this ModelObject. + ModelInstancePtrs instances; + // Printable and modifier volumes, each with its material ID and a set of override parameters. + // ModelVolumes are owned by this ModelObject. + ModelVolumePtrs volumes; + // Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings. + ModelConfigObject config; + // Variation of a layer thickness for spans of Z coordinates + optional parameter overrides. + t_layer_config_ranges layer_config_ranges; + // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. + // The pairs of are packed into a 1D array. + LayerHeightProfile layer_height_profile; + // Whether or not this object is printable + bool printable; + + // This vector holds position of selected support points for SLA. The data are + // saved in mesh coordinates to allow using them for several instances. + // The format is (x, y, z, point_size, supports_island) + sla::SupportPoints sla_support_points; + // To keep track of where the points came from (used for synchronization between + // the SLA gizmo and the backend). + sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints; + + // Holes to be drilled into the object so resin can flow out + sla::DrainHoles sla_drain_holes; + + /* This vector accumulates the total translation applied to the object by the + center_around_origin() method. Callers might want to apply the same translation + to new volumes before adding them to this object in order to preserve alignment + when user expects that. */ + Vec3d origin_translation; + + Model* get_model() { return m_model; } + const Model* get_model() const { return m_model; } + + ModelVolume* add_volume(const TriangleMesh &mesh); + ModelVolume* add_volume(TriangleMesh &&mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART); + ModelVolume* add_volume(const ModelVolume &volume, ModelVolumeType type = ModelVolumeType::INVALID); + ModelVolume* add_volume(const ModelVolume &volume, TriangleMesh &&mesh); + void delete_volume(size_t idx); + void clear_volumes(); + void sort_volumes(bool full_sort); + bool is_multiparts() const { return volumes.size() > 1; } + // Checks if any of object volume is painted using the fdm support painting gizmo. + bool is_fdm_support_painted() const; + // Checks if any of object volume is painted using the seam painting gizmo. + bool is_seam_painted() const; + // Checks if any of object volume is painted using the multi-material painting gizmo. + bool is_mm_painted() const; + + ModelInstance* add_instance(); + ModelInstance* add_instance(const ModelInstance &instance); + ModelInstance* add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation, const Vec3d &mirror); + void delete_instance(size_t idx); + void delete_last_instance(); + void clear_instances(); + + // Returns the bounding box of the transformed instances. + // This bounding box is approximate and not snug. + // This bounding box is being cached. + const BoundingBoxf3& bounding_box() const; + void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } + + // A mesh containing all transformed instances of this object. + TriangleMesh mesh() const; + // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. + // Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater. + TriangleMesh raw_mesh() const; + // The same as above, but producing a lightweight indexed_triangle_set. + indexed_triangle_set raw_indexed_triangle_set() const; + // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. + // This bounding box is only used for the actual slicing. + const BoundingBoxf3& raw_bounding_box() const; + // A snug bounding box around the transformed non-modifier object volumes. + BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; + // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. + const BoundingBoxf3& raw_mesh_bounding_box() const; + // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. + BoundingBoxf3 full_raw_mesh_bounding_box() const; + + // Calculate 2D convex hull of of a projection of the transformed printable volumes into the XY plane. + // This method is cheap in that it does not make any unnecessary copy of the volume meshes. + // This method is used by the auto arrange function. + Polygon convex_hull_2d(const Transform3d &trafo_instance) const; + + void center_around_origin(bool include_modifiers = true); + void ensure_on_bed(bool allow_negative_z = false); + + void translate_instances(const Vec3d& vector); + void translate_instance(size_t instance_idx, const Vec3d& vector); + void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); } + void translate(double x, double y, double z); + void scale(const Vec3d &versor); + void scale(const double s) { this->scale(Vec3d(s, s, s)); } + void scale(double x, double y, double z) { this->scale(Vec3d(x, y, z)); } + /// Scale the current ModelObject to fit by altering the scaling factor of ModelInstances. + /// It operates on the total size by duplicating the object according to all the instances. + /// \param size Sizef3 the size vector + void scale_to_fit(const Vec3d &size); + void rotate(double angle, Axis axis); + void rotate(double angle, const Vec3d& axis); + void mirror(Axis axis); + + // This method could only be called before the meshes of this ModelVolumes are not shared! + void scale_mesh_after_creation(const float scale); + void convert_units(ModelObjectPtrs&new_objects, ConversionType conv_type, std::vector volume_idxs); + + size_t materials_count() const; + size_t facets_count() const; + size_t parts_count() const; + ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); + void split(ModelObjectPtrs* new_objects); + void merge(); + // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, + // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. + // This situation is solved by baking in the instance transformation into the mesh vertices. + // Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well. + void bake_xy_rotation_into_meshes(size_t instance_idx); + + double get_min_z() const; + double get_max_z() const; + double get_instance_min_z(size_t instance_idx) const; + double get_instance_max_z(size_t instance_idx) const; + + // Print object statistics to console. + void print_info() const; + + std::string get_export_filename() const; + + // Get full stl statistics for all object's meshes + TriangleMeshStats get_object_stl_stats() const; + // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) + int get_repaired_errors_count(const int vol_idx = -1) const; + +private: + friend class Model; + // This constructor assigns new ID to this ModelObject and its config. + explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()), + m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) + { + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->layer_height_profile.id().valid()); + } + explicit ModelObject(int) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) + { + assert(this->id().invalid()); + assert(this->config.id().invalid()); + assert(this->layer_height_profile.id().invalid()); + } + ~ModelObject(); + void assign_new_unique_ids_recursive() override; + + // To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" + // (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). + ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1), m_model(rhs.m_model) { + assert(this->id().invalid()); + assert(this->config.id().invalid()); + assert(this->layer_height_profile.id().invalid()); + assert(rhs.id() != rhs.config.id()); + assert(rhs.id() != rhs.layer_height_profile.id()); + this->assign_copy(rhs); + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->layer_height_profile.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->layer_height_profile.id()); + assert(this->id() == rhs.id()); + assert(this->config.id() == rhs.config.id()); + assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); + } + explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1), layer_height_profile(-1) { + assert(this->id().invalid()); + assert(this->config.id().invalid()); + assert(this->layer_height_profile.id().invalid()); + assert(rhs.id() != rhs.config.id()); + assert(rhs.id() != rhs.layer_height_profile.id()); + this->assign_copy(std::move(rhs)); + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->layer_height_profile.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->layer_height_profile.id()); + assert(this->id() == rhs.id()); + assert(this->config.id() == rhs.config.id()); + assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); + } + ModelObject& operator=(const ModelObject &rhs) { + this->assign_copy(rhs); + m_model = rhs.m_model; + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->layer_height_profile.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->layer_height_profile.id()); + assert(this->id() == rhs.id()); + assert(this->config.id() == rhs.config.id()); + assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); + return *this; + } + ModelObject& operator=(ModelObject &&rhs) { + this->assign_copy(std::move(rhs)); + m_model = rhs.m_model; + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->layer_height_profile.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->layer_height_profile.id()); + assert(this->id() == rhs.id()); + assert(this->config.id() == rhs.config.id()); + assert(this->layer_height_profile.id() == rhs.layer_height_profile.id()); + return *this; + } + void set_new_unique_id() { + ObjectBase::set_new_unique_id(); + this->config.set_new_unique_id(); + this->layer_height_profile.set_new_unique_id(); + } + + OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject) + + // Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized. + Model *m_model = nullptr; + + // Bounding box, cached. + mutable BoundingBoxf3 m_bounding_box; + mutable bool m_bounding_box_valid; + mutable BoundingBoxf3 m_raw_bounding_box; + mutable bool m_raw_bounding_box_valid; + mutable BoundingBoxf3 m_raw_mesh_bounding_box; + mutable bool m_raw_mesh_bounding_box_valid; + + // Called by Print::apply() to set the model pointer after making a copy. + friend class Print; + friend class SLAPrint; + void set_model(Model *model) { m_model = model; } + + // Undo / Redo through the cereal serialization library + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Used for deserialization -> Don't allocate any IDs for the ModelObject or its config. + ModelObject() : + ObjectBase(-1), config(-1), layer_height_profile(-1), + m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) { + assert(this->id().invalid()); + assert(this->config.id().invalid()); + assert(this->layer_height_profile.id().invalid()); + } + template void serialize(Archive &ar) { + ar(cereal::base_class(this)); + Internal::StaticSerializationWrapper config_wrapper(config); + Internal::StaticSerializationWrapper layer_heigth_profile_wrapper(layer_height_profile); + ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_heigth_profile_wrapper, + sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation, + m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid); + } + + // Called by Print::validate() from the UI thread. + unsigned int update_instances_print_volume_state(const BuildVolume &build_volume); +}; + +enum class EnforcerBlockerType : int8_t { + // Maximum is 3. The value is serialized in TriangleSelector into 2 bits. + NONE = 0, + ENFORCER = 1, + BLOCKER = 2, + // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. + Extruder1 = ENFORCER, + Extruder2 = BLOCKER, + Extruder3, + Extruder4, + Extruder5, + Extruder6, + Extruder7, + Extruder8, + Extruder9, + Extruder10, + Extruder11, + Extruder12, + Extruder13, + Extruder14, + Extruder15, +}; + +enum class ConversionType : int { + CONV_TO_INCH, + CONV_FROM_INCH, + CONV_TO_METER, + CONV_FROM_METER, +}; + +class FacetsAnnotation final : public ObjectWithTimestamp { +public: + // Assign the content if the timestamp differs, don't assign an ObjectID. + void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } + void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } + const std::pair>, std::vector>& get_data() const throw() { return m_data; } + bool set(const TriangleSelector& selector); + indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; + indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const; + bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const; + bool empty() const { return m_data.first.empty(); } + + // Following method clears the config and increases its timestamp, so the deleted + // state is considered changed from perspective of the undo/redo stack. + void reset(); + + // Serialize triangle into string, for serialization into 3MF/AMF. + std::string get_triangle_as_string(int i) const; + + // Before deserialization, reserve space for n_triangles. + void reserve(int n_triangles) { m_data.first.reserve(n_triangles); } + // Deserialize triangles one by one, with strictly increasing triangle_id. + void set_triangle_from_string(int triangle_id, const std::string& str); + // After deserializing the last triangle, shrink data to fit. + void shrink_to_fit() { m_data.first.shrink_to_fit(); m_data.second.shrink_to_fit(); } + +private: + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit FacetsAnnotation() = default; + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit FacetsAnnotation(int) : ObjectWithTimestamp(-1) {} + // Copy constructor copies the ID. + explicit FacetsAnnotation(const FacetsAnnotation &rhs) = default; + // Move constructor copies the ID. + explicit FacetsAnnotation(FacetsAnnotation &&rhs) = default; + + // called by ModelVolume::assign_copy() + FacetsAnnotation& operator=(const FacetsAnnotation &rhs) = default; + FacetsAnnotation& operator=(FacetsAnnotation &&rhs) = default; + + friend class cereal::access; + friend class UndoRedo::StackImpl; + + template void serialize(Archive &ar) + { + ar(cereal::base_class(this), m_data); + } + + std::pair>, std::vector> m_data; + + // To access set_new_unique_id() when copy / pasting a ModelVolume. + friend class ModelVolume; +}; + +// An object STL, or a modifier volume, over which a different set of parameters shall be applied. +// ModelVolume instances are owned by a ModelObject. +class ModelVolume final : public ObjectBase +{ +public: + std::string name; + // struct used by reload from disk command to recover data from disk + struct Source + { + std::string input_file; + int object_idx{ -1 }; + int volume_idx{ -1 }; + Vec3d mesh_offset{ Vec3d::Zero() }; + Geometry::Transformation transform; + bool is_converted_from_inches{ false }; + bool is_converted_from_meters{ false }; + bool is_from_builtin_objects{ false }; + + template void serialize(Archive& ar) { + //FIXME Vojtech: Serialize / deserialize only if the Source is set. + // likely testing input_file or object_idx would be sufficient. + ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters, is_from_builtin_objects); + } + }; + Source source; + + // The triangular model. + const TriangleMesh& mesh() const { return *m_mesh.get(); } + void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } + void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } + void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared(mesh); } + void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } + void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } + void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } + void reset_mesh() { m_mesh = std::make_shared(); } + // Configuration parameters specific to an object model geometry or a modifier volume, + // overriding the global Slic3r settings and the ModelObject settings. + ModelConfigObject config; + + // List of mesh facets to be supported/unsupported. + FacetsAnnotation supported_facets; + + // List of seam enforcers/blockers. + FacetsAnnotation seam_facets; + + // List of mesh facets painted for MMU segmentation. + FacetsAnnotation mmu_segmentation_facets; + + // A parent object owning this modifier volume. + ModelObject* get_object() const { return this->object; } + ModelVolumeType type() const { return m_type; } + void set_type(const ModelVolumeType t) { m_type = t; } + bool is_model_part() const { return m_type == ModelVolumeType::MODEL_PART; } + bool is_negative_volume() const { return m_type == ModelVolumeType::NEGATIVE_VOLUME; } + bool is_modifier() const { return m_type == ModelVolumeType::PARAMETER_MODIFIER; } + bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; } + bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; } + bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } + t_model_material_id material_id() const { return m_material_id; } + void set_material_id(t_model_material_id material_id); + ModelMaterial* material() const; + void set_material(t_model_material_id material_id, const ModelMaterial &material); + // Extract the current extruder ID based on this ModelVolume's config and the parent ModelObject's config. + // Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes). + int extruder_id() const; + + bool is_splittable() const; + + // Split this volume, append the result to the object owning this volume. + // Return the number of volumes created from this one. + // This is useful to assign different materials to different volumes of an object. + size_t split(unsigned int max_extruders); + void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); } + void translate(const Vec3d& displacement); + void scale(const Vec3d& scaling_factors); + void scale(double x, double y, double z) { scale(Vec3d(x, y, z)); } + void scale(double s) { scale(Vec3d(s, s, s)); } + void rotate(double angle, Axis axis); + void rotate(double angle, const Vec3d& axis); + void mirror(Axis axis); + + // This method could only be called before the meshes of this ModelVolumes are not shared! + void scale_geometry_after_creation(const Vec3f &versor); + void scale_geometry_after_creation(const float scale) { this->scale_geometry_after_creation(Vec3f(scale, scale, scale)); } + + // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box. + // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared! + void center_geometry_after_creation(bool update_source_offset = true); + + void calculate_convex_hull(); + const TriangleMesh& get_convex_hull() const; + const std::shared_ptr& get_convex_hull_shared_ptr() const { return m_convex_hull; } + // Get count of errors in the mesh + int get_repaired_errors_count() const; + + // Helpers for loading / storing into AMF / 3MF files. + static ModelVolumeType type_from_string(const std::string &s); + static std::string type_to_string(const ModelVolumeType t); + + const Geometry::Transformation& get_transformation() const { return m_transformation; } + void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void set_transformation(const Transform3d& trafo) { m_transformation = trafo; } +#else + void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_offset() const { return m_transformation.get_offset(); } +#else + const Vec3d& get_offset() const { return m_transformation.get_offset(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } + + void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } + void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_rotation() const { return m_transformation.get_rotation(); } +#else + const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } + + void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } + void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } + + Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } + double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } + + void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } + void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_mirror() const { return m_transformation.get_mirror(); } +#else + const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } + bool is_left_handed() const { return m_transformation.is_left_handed(); } + + void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } + void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } + void convert_from_imperial_units(); + void convert_from_meters(); + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d& get_matrix() const { return m_transformation.get_matrix(); } + Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); } +#else + const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + + void set_new_unique_id() { + ObjectBase::set_new_unique_id(); + this->config.set_new_unique_id(); + this->supported_facets.set_new_unique_id(); + this->seam_facets.set_new_unique_id(); + this->mmu_segmentation_facets.set_new_unique_id(); + } + + bool is_fdm_support_painted() const { return !this->supported_facets.empty(); } + bool is_seam_painted() const { return !this->seam_facets.empty(); } + bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } + +protected: + friend class Print; + friend class SLAPrint; + friend class Model; + friend class ModelObject; + friend void model_volume_list_update_supports(ModelObject& model_object_dst, const ModelObject& model_object_new); + + // Copies IDs of both the ModelVolume and its config. + explicit ModelVolume(const ModelVolume &rhs) = default; + void set_model_object(ModelObject *model_object) { object = model_object; } + void assign_new_unique_ids_recursive() override; + void transform_this_mesh(const Transform3d& t, bool fix_left_handed); + void transform_this_mesh(const Matrix3d& m, bool fix_left_handed); + +private: + // Parent object owning this ModelVolume. + ModelObject* object; + // The triangular model. + std::shared_ptr m_mesh; + // Is it an object to be printed, or a modifier volume? + ModelVolumeType m_type; + t_model_material_id m_material_id; + // The convex hull of this model's mesh. + std::shared_ptr m_convex_hull; + Geometry::Transformation m_transformation; + + // flag to optimize the checking if the volume is splittable + // -1 -> is unknown value (before first cheking) + // 0 -> is not splittable + // 1 -> is splittable + mutable int m_is_splittable{ -1 }; + + ModelVolume(ModelObject *object, const TriangleMesh &mesh, ModelVolumeType type = ModelVolumeType::MODEL_PART) : m_mesh(new TriangleMesh(mesh)), m_type(type), object(object) + { + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->supported_facets.id().valid()); + assert(this->seam_facets.id().valid()); + assert(this->mmu_segmentation_facets.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->seam_facets.id()); + assert(this->id() != this->mmu_segmentation_facets.id()); + if (mesh.facets_count() > 1) + calculate_convex_hull(); + } + ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull, ModelVolumeType type = ModelVolumeType::MODEL_PART) : + m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(type), object(object) { + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->supported_facets.id().valid()); + assert(this->seam_facets.id().valid()); + assert(this->mmu_segmentation_facets.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->seam_facets.id()); + assert(this->id() != this->mmu_segmentation_facets.id()); + } + + // Copying an existing volume, therefore this volume will get a copy of the ID assigned. + ModelVolume(ModelObject *object, const ModelVolume &other) : + ObjectBase(other), + name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), + config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), + supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets) + { + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->supported_facets.id().valid()); + assert(this->seam_facets.id().valid()); + assert(this->mmu_segmentation_facets.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->seam_facets.id()); + assert(this->id() != this->mmu_segmentation_facets.id()); + assert(this->id() == other.id()); + assert(this->config.id() == other.config.id()); + assert(this->supported_facets.id() == other.supported_facets.id()); + assert(this->seam_facets.id() == other.seam_facets.id()); + assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id()); + this->set_material_id(other.material_id()); + } + // Providing a new mesh, therefore this volume will get a new unique ID assigned. + ModelVolume(ModelObject *object, const ModelVolume &other, TriangleMesh &&mesh) : + name(other.name), source(other.source), config(other.config), object(object), m_mesh(new TriangleMesh(std::move(mesh))), m_type(other.m_type), m_transformation(other.m_transformation) + { + assert(this->id().valid()); + assert(this->config.id().valid()); + assert(this->supported_facets.id().valid()); + assert(this->seam_facets.id().valid()); + assert(this->mmu_segmentation_facets.id().valid()); + assert(this->id() != this->config.id()); + assert(this->id() != this->supported_facets.id()); + assert(this->id() != this->seam_facets.id()); + assert(this->id() != this->mmu_segmentation_facets.id()); + assert(this->id() != other.id()); + assert(this->config.id() == other.config.id()); + this->set_material_id(other.material_id()); + this->config.set_new_unique_id(); + if (m_mesh->facets_count() > 1) + calculate_convex_hull(); + assert(this->config.id().valid()); + assert(this->config.id() != other.config.id()); + assert(this->supported_facets.id() != other.supported_facets.id()); + assert(this->seam_facets.id() != other.seam_facets.id()); + assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id()); + assert(this->id() != this->config.id()); + assert(this->supported_facets.empty()); + assert(this->seam_facets.empty()); + assert(this->mmu_segmentation_facets.empty()); + } + + ModelVolume& operator=(ModelVolume &rhs) = delete; + + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Used for deserialization, therefore no IDs are allocated. + ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) { + assert(this->id().invalid()); + assert(this->config.id().invalid()); + assert(this->supported_facets.id().invalid()); + assert(this->seam_facets.id().invalid()); + assert(this->mmu_segmentation_facets.id().invalid()); + } + template void load(Archive &ar) { + bool has_convex_hull; + ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + cereal::load_by_value(ar, supported_facets); + cereal::load_by_value(ar, seam_facets); + cereal::load_by_value(ar, mmu_segmentation_facets); + cereal::load_by_value(ar, config); + assert(m_mesh); + if (has_convex_hull) { + cereal::load_optional(ar, m_convex_hull); + if (! m_convex_hull && ! m_mesh->empty()) + // The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it. + this->calculate_convex_hull(); + } else + m_convex_hull.reset(); + } + template void save(Archive &ar) const { + bool has_convex_hull = m_convex_hull.get() != nullptr; + ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull); + cereal::save_by_value(ar, supported_facets); + cereal::save_by_value(ar, seam_facets); + cereal::save_by_value(ar, mmu_segmentation_facets); + cereal::save_by_value(ar, config); + if (has_convex_hull) + cereal::save_optional(ar, m_convex_hull); + } +}; + +inline void model_volumes_sort_by_id(ModelVolumePtrs &model_volumes) +{ + std::sort(model_volumes.begin(), model_volumes.end(), [](const ModelVolume *l, const ModelVolume *r) { return l->id() < r->id(); }); +} + +inline const ModelVolume* model_volume_find_by_id(const ModelVolumePtrs &model_volumes, const ObjectID id) +{ + auto it = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [id](const ModelVolume *mv) { return mv->id() < id; }); + return it != model_volumes.end() && (*it)->id() == id ? *it : nullptr; +} + +enum ModelInstanceEPrintVolumeState : unsigned char +{ + ModelInstancePVS_Inside, + ModelInstancePVS_Partly_Outside, + ModelInstancePVS_Fully_Outside, + ModelInstanceNum_BedStates +}; + +// A single instance of a ModelObject. +// Knows the affine transformation of an object. +class ModelInstance final : public ObjectBase +{ +private: + Geometry::Transformation m_transformation; + +public: + // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) + ModelInstanceEPrintVolumeState print_volume_state; + // Whether or not this instance is printable + bool printable; + + ModelObject* get_object() const { return this->object; } + + const Geometry::Transformation& get_transformation() const { return m_transformation; } + void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_offset() const { return m_transformation.get_offset(); } +#else + const Vec3d& get_offset() const { return m_transformation.get_offset(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } + + void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } + void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_rotation() const { return m_transformation.get_rotation(); } +#else + const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } + + void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } + void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } +#else + const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } + + void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } + void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_mirror() const { return m_transformation.get_mirror(); } +#else + const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } + bool is_left_handed() const { return m_transformation.is_left_handed(); } + + void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } + void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } + + // To be called on an external mesh + void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const; + // Calculate a bounding box of a transformed mesh. To be called on an external mesh. + BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const; + // Transform an external bounding box. + BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const; + // Transform an external vector. + Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const; + // To be called on an external polygon. It does not translate the polygon, only rotates and scales. + void transform_polygon(Polygon* polygon) const; + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d& get_matrix() const { return m_transformation.get_matrix(); } + Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); } +#else + const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + + bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); } + + // Getting the input polygon for arrange + arrangement::ArrangePolygon get_arrange_polygon() const; + + // Apply the arrange result on the ModelInstance + void apply_arrange_result(const Vec2d& offs, double rotation) + { + // write the transformation data into the model instance + set_rotation(Z, rotation); + set_offset(X, unscale(offs(X))); + set_offset(Y, unscale(offs(Y))); + this->object->invalidate_bounding_box(); + } + +protected: + friend class Print; + friend class SLAPrint; + friend class Model; + friend class ModelObject; + + explicit ModelInstance(const ModelInstance &rhs) = default; + void set_model_object(ModelObject *model_object) { object = model_object; } + +private: + // Parent object, owning this instance. + ModelObject* object; + + // Constructor, which assigns a new unique ID. + explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), printable(true), object(object) { assert(this->id().valid()); } + // Constructor, which assigns a new unique ID. + explicit ModelInstance(ModelObject *object, const ModelInstance &other) : + m_transformation(other.m_transformation), print_volume_state(ModelInstancePVS_Inside), printable(other.printable), object(object) { assert(this->id().valid() && this->id() != other.id()); } + + explicit ModelInstance(ModelInstance &&rhs) = delete; + ModelInstance& operator=(const ModelInstance &rhs) = delete; + ModelInstance& operator=(ModelInstance &&rhs) = delete; + + friend class cereal::access; + friend class UndoRedo::StackImpl; + // Used for deserialization, therefore no IDs are allocated. + ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); } + template void serialize(Archive &ar) { + ar(m_transformation, print_volume_state, printable); + } +}; + + +class ModelWipeTower final : public ObjectBase +{ +public: + Vec2d position; + double rotation; + +private: + friend class cereal::access; + friend class UndoRedo::StackImpl; + friend class Model; + + // Constructors to be only called by derived classes. + // Default constructor to assign a unique ID. + explicit ModelWipeTower() {} + // Constructor with ignored int parameter to assign an invalid ID, to be replaced + // by an existing ID copied from elsewhere. + explicit ModelWipeTower(int) : ObjectBase(-1) {} + // Copy constructor copies the ID. + explicit ModelWipeTower(const ModelWipeTower &cfg) = default; + + // Disabled methods. + ModelWipeTower(ModelWipeTower &&rhs) = delete; + ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete; + ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete; + + // For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object. + template void serialize(Archive &ar) { ar(position, rotation); } +}; + +// The print bed content. +// Description of a triangular model with multiple materials, multiple instances with various affine transformations +// and with multiple modifier meshes. +// A model groups multiple objects, each object having possibly multiple instances, +// all objects may share mutliple materials. +class Model final : public ObjectBase +{ +public: + // Materials are owned by a model and referenced by objects through t_model_material_id. + // Single material may be shared by multiple models. + ModelMaterialMap materials; + // Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation). + ModelObjectPtrs objects; + // Wipe tower object. + ModelWipeTower wipe_tower; + + // Extensions for color print + CustomGCode::Info custom_gcode_per_print_z; + + // Default constructor assigns a new ID to the model. + Model() { assert(this->id().valid()); } + ~Model() { this->clear_objects(); this->clear_materials(); } + + /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ + /* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */ + Model(const Model &rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); } + explicit Model(Model &&rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); } + Model& operator=(const Model &rhs) { this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } + Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; } + + OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) + + enum class LoadAttribute : int { + AddDefaultInstances, + CheckVersion + }; + using LoadAttributes = enum_bitmask; + + static Model read_from_file( + const std::string& input_file, + DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr, + LoadAttributes options = LoadAttribute::AddDefaultInstances); + static Model read_from_archive( + const std::string& input_file, + DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, + LoadAttributes options = LoadAttribute::AddDefaultInstances); + + // Add a new ModelObject to this Model, generate a new ID for this ModelObject. + ModelObject* add_object(); + ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); + ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); + ModelObject* add_object(const ModelObject &other); + void delete_object(size_t idx); + bool delete_object(ObjectID id); + bool delete_object(ModelObject* object); + void clear_objects(); + + ModelMaterial* add_material(t_model_material_id material_id); + ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other); + ModelMaterial* get_material(t_model_material_id material_id) { + ModelMaterialMap::iterator i = this->materials.find(material_id); + return (i == this->materials.end()) ? nullptr : i->second; + } + + void delete_material(t_model_material_id material_id); + void clear_materials(); + bool add_default_instances(); + // Returns approximate axis aligned bounding box of this model + BoundingBoxf3 bounding_box() const; + // Set the print_volume_state of PrintObject::instances, + // return total number of printable objects. + unsigned int update_print_volume_state(const BuildVolume &build_volume); + // Returns true if any ModelObject was modified. + bool center_instances_around_point(const Vec2d &point); + void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } + TriangleMesh mesh() const; + + // Croaks if the duplicated objects do not fit the print bed. + void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); + + bool looks_like_multipart_object() const; + void convert_multipart_object(unsigned int max_extruders); + bool looks_like_imperial_units() const; + void convert_from_imperial_units(bool only_small_volumes); + bool looks_like_saved_in_meters() const; + void convert_from_meters(bool only_small_volumes); + int removed_objects_with_zero_volume(); + + // Ensures that the min z of the model is not negative + void adjust_min_z(); + + void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } + + // Propose an output file name & path based on the first printable object's name and source input file's path. + std::string propose_export_file_name_and_path() const; + // Propose an output path, replace extension. The new_extension shall contain the initial dot. + std::string propose_export_file_name_and_path(const std::string &new_extension) const; + + // Checks if any of objects is painted using the fdm support painting gizmo. + bool is_fdm_support_painted() const; + // Checks if any of objects is painted using the seam painting gizmo. + bool is_seam_painted() const; + // Checks if any of objects is painted using the multi-material painting gizmo. + bool is_mm_painted() const; + +private: + explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); } + void assign_new_unique_ids_recursive(); + void update_links_bottom_up_recursive(); + + friend class cereal::access; + friend class UndoRedo::StackImpl; + template void serialize(Archive &ar) { + Internal::StaticSerializationWrapper wipe_tower_wrapper(wipe_tower); + ar(materials, objects, wipe_tower_wrapper); + } +}; + +ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute) + +#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE +#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE + +// Test whether the two models contain the same number of ModelObjects with the same set of IDs +// ordered in the same order. In that case it is not necessary to kill the background processing. +bool model_object_list_equal(const Model &model_old, const Model &model_new); + +// Test whether the new model is just an extension of the old model (new objects were added +// to the end of the original list. In that case it is not necessary to kill the background processing. +bool model_object_list_extended(const Model &model_old, const Model &model_new); + +// Test whether the new ModelObject contains a different set of volumes (or sorted in a different order) +// than the old ModelObject. +bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type); +bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const std::initializer_list &types); + +// Test whether the now ModelObject has newer custom supports data than the old one. +// The function assumes that volumes list is synchronized. +bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new); + +// Test whether the now ModelObject has newer custom seam data than the old one. +// The function assumes that volumes list is synchronized. +bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new); + +// Test whether the now ModelObject has newer MMU segmentation data than the old one. +// The function assumes that volumes list is synchronized. +extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new); + +// If the model has multi-part objects, then it is currently not supported by the SLA mode. +// Either the model cannot be loaded, or a SLA printer has to be activated. +bool model_has_multi_part_objects(const Model &model); +// If the model has advanced features, then it cannot be processed in simple mode. +bool model_has_advanced_features(const Model &model); + +#ifndef NDEBUG +// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. +void check_model_ids_validity(const Model &model); +void check_model_ids_equal(const Model &model1, const Model &model2); +#endif /* NDEBUG */ + +static const float SINKING_Z_THRESHOLD = -0.001f; +static const double SINKING_MIN_Z_THRESHOLD = 0.05; + +} // namespace Slic3r + +namespace cereal +{ + template struct specialize {}; + template struct specialize {}; +} + +#endif /* slic3r_Model_hpp_ */ diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 84152ee9c..4fc76800c 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -1,565 +1,569 @@ -#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 Line; -class MultiPoint; -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 Vec2d = Eigen::Matrix; -using Vec3d = 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) { return { ptN.x(), ptN.y() }; } - -template -Eigen::Matrix to_3d(const Eigen::MatrixBase> & pt, const T z) { 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(lrint(x)), coord_t(lrint(y))) {} - Point(const Point &rhs) { *this = rhs; } - explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(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; } - int nearest_point_index(const Points &points) const; - int nearest_point_index(const PointConstPtrs &points) const; - int nearest_point_index(const PointPtrs &points) const; - bool nearest_point(const Points &points, Point* point) const; - Point projection_onto(const MultiPoint &poly) const; - Point projection_onto(const Line &line) const; -}; - -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); - -// 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()); -} - -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 { - 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 - -// 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 load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); } - template void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); } -} - -// 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 Line; +class MultiPoint; +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 Vec2d = Eigen::Matrix; +using Vec3d = 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) { return { ptN.x(), ptN.y() }; } + +template +Eigen::Matrix to_3d(const Eigen::MatrixBase> & pt, const T z) { 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(lrint(x)), coord_t(lrint(y))) {} + Point(const Point &rhs) { *this = rhs; } + explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(lrint(rhs.x())), coord_t(lrint(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; } + int nearest_point_index(const Points &points) const; + int nearest_point_index(const PointConstPtrs &points) const; + int nearest_point_index(const PointPtrs &points) const; + bool nearest_point(const Points &points, Point* point) const; + Point projection_onto(const MultiPoint &poly) const; + Point projection_onto(const Line &line) const; +}; + +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); + +// 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()); +} + +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 { + 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 + +// 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 load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); } + template void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + template void load(Archive& archive, Slic3r::Transform3d& m) { archive.loadBinary((char*)m.data(), sizeof(double) * 16); } + template void save(Archive& archive, const Slic3r::Transform3d& m) { archive.saveBinary((char*)m.data(), sizeof(double) * 16); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +} + +// 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/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 34e3129d9..b88ae9e50 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1,1444 +1,1453 @@ -#include "Model.hpp" -#include "Print.hpp" - -#include - -namespace Slic3r { - -// Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new -// in the exact order and with the same IDs. -// It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order. -// Friend to ModelVolume to allow copying. -// static is not accepted by gcc if declared as a friend of ModelObject. -/* static */ void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_new) -{ - typedef std::pair ModelVolumeWithStatus; - std::vector old_volumes; - old_volumes.reserve(model_object_dst.volumes.size()); - for (const ModelVolume *model_volume : model_object_dst.volumes) - old_volumes.emplace_back(ModelVolumeWithStatus(model_volume, false)); - auto model_volume_lower = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() < mv2.first->id(); }; - auto model_volume_equal = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() == mv2.first->id(); }; - std::sort(old_volumes.begin(), old_volumes.end(), model_volume_lower); - model_object_dst.volumes.clear(); - model_object_dst.volumes.reserve(model_object_new.volumes.size()); - for (const ModelVolume *model_volume_src : model_object_new.volumes) { - ModelVolumeWithStatus key(model_volume_src, false); - auto it = std::lower_bound(old_volumes.begin(), old_volumes.end(), key, model_volume_lower); - if (it != old_volumes.end() && model_volume_equal(*it, key)) { - // The volume was found in the old list. Just copy it. - assert(! it->second); // not consumed yet - it->second = true; - ModelVolume *model_volume_dst = const_cast(it->first); - // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. - assert((model_volume_dst->is_support_modifier() && model_volume_src->is_support_modifier()) || model_volume_dst->type() == model_volume_src->type()); - model_object_dst.volumes.emplace_back(model_volume_dst); - if (model_volume_dst->is_support_modifier()) { - // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. - model_volume_dst->set_type(model_volume_src->type()); - model_volume_dst->set_transformation(model_volume_src->get_transformation()); - } - assert(model_volume_dst->get_matrix().isApprox(model_volume_src->get_matrix())); - } else { - // The volume was not found in the old list. Create a new copy. - assert(model_volume_src->is_support_modifier()); - model_object_dst.volumes.emplace_back(new ModelVolume(*model_volume_src)); - model_object_dst.volumes.back()->set_model_object(&model_object_dst); - } - } - // Release the non-consumed old volumes (those were deleted from the new list). - for (ModelVolumeWithStatus &mv_with_status : old_volumes) - if (! mv_with_status.second) - delete mv_with_status.first; -} - -static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolumeType type) -{ - size_t i_src, i_dst; - for (i_src = 0, i_dst = 0; i_src < model_object_src.volumes.size() && i_dst < model_object_dst.volumes.size();) { - const ModelVolume &mv_src = *model_object_src.volumes[i_src]; - ModelVolume &mv_dst = *model_object_dst.volumes[i_dst]; - if (mv_src.type() != type) { - ++ i_src; - continue; - } - if (mv_dst.type() != type) { - ++ i_dst; - continue; - } - assert(mv_src.id() == mv_dst.id()); - // Copy the ModelVolume data. - mv_dst.name = mv_src.name; - mv_dst.config.assign_config(mv_src.config); - assert(mv_dst.supported_facets.id() == mv_src.supported_facets.id()); - mv_dst.supported_facets.assign(mv_src.supported_facets); - assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id()); - mv_dst.seam_facets.assign(mv_src.seam_facets); - assert(mv_dst.mmu_segmentation_facets.id() == mv_src.mmu_segmentation_facets.id()); - mv_dst.mmu_segmentation_facets.assign(mv_src.mmu_segmentation_facets); - //FIXME what to do with the materials? - // mv_dst.m_material_id = mv_src.m_material_id; - ++ i_src; - ++ i_dst; - } -} - -static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src) -{ - assert(lr_dst.size() == lr_src.size()); - auto it_src = lr_src.cbegin(); - for (auto &kvp_dst : lr_dst) { - const auto &kvp_src = *it_src ++; - assert(std::abs(kvp_dst.first.first - kvp_src.first.first ) <= EPSILON); - assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON); - // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile. - // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON); - kvp_dst.second = kvp_src.second; - } -} - -static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs) -{ - typedef Transform3d::Scalar T; - const T *lv = lhs.data(); - const T *rv = rhs.data(); - for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) { - if (*lv < *rv) - return true; - else if (*lv > *rv) - return false; - } - return false; -} - -static inline bool transform3d_equal(const Transform3d &lhs, const Transform3d &rhs) -{ - typedef Transform3d::Scalar T; - const T *lv = lhs.data(); - const T *rv = rhs.data(); - for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) - if (*lv != *rv) - return false; - return true; -} - -struct PrintObjectTrafoAndInstances -{ - Transform3d trafo; - PrintInstances instances; - bool operator<(const PrintObjectTrafoAndInstances &rhs) const { return transform3d_lower(this->trafo, rhs.trafo); } -}; - -// Generate a list of trafos and XY offsets for instances of a ModelObject -static std::vector print_objects_from_model_object(const ModelObject &model_object) -{ - std::set trafos; - PrintObjectTrafoAndInstances trafo; - for (ModelInstance *model_instance : model_object.instances) - if (model_instance->is_printable()) { - trafo.trafo = model_instance->get_matrix(); - auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]); - // Reset the XY axes of the transformation. - trafo.trafo.data()[12] = 0; - trafo.trafo.data()[13] = 0; - // Search or insert a trafo. - auto it = trafos.emplace(trafo).first; - const_cast(*it).instances.emplace_back(PrintInstance{ nullptr, model_instance, shift }); - } - return std::vector(trafos.begin(), trafos.end()); -} - -// Compare just the layer ranges and their layer heights, not the associated configs. -// Ignore the layer heights if check_layer_heights is false. -static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height) -{ - if (lr1.size() != lr2.size()) - return false; - auto it2 = lr2.begin(); - for (const auto &kvp1 : lr1) { - const auto &kvp2 = *it2 ++; - if (std::abs(kvp1.first.first - kvp2.first.first ) > EPSILON || - std::abs(kvp1.first.second - kvp2.first.second) > EPSILON || - (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON)) - return false; - } - return true; -} - -// Returns true if va == vb when all CustomGCode items that are not ToolChangeCode are ignored. -static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector &va, const std::vector &vb) -{ - auto it_a = va.begin(); - auto it_b = vb.begin(); - while (it_a != va.end() || it_b != vb.end()) { - if (it_a != va.end() && it_a->type != CustomGCode::ToolChange) { - // Skip any CustomGCode items, which are not tool changes. - ++ it_a; - continue; - } - if (it_b != vb.end() && it_b->type != CustomGCode::ToolChange) { - // Skip any CustomGCode items, which are not tool changes. - ++ it_b; - continue; - } - if (it_a == va.end() || it_b == vb.end()) - // va or vb contains more Tool Changes than the other. - return true; - assert(it_a->type == CustomGCode::ToolChange); - assert(it_b->type == CustomGCode::ToolChange); - if (*it_a != *it_b) - // The two Tool Changes differ. - return true; - ++ it_a; - ++ it_b; - } - // There is no change in custom Tool Changes. - return false; -} - -// Collect changes to print config, account for overrides of extruder retract values by filament presets. -static t_config_option_keys print_config_diffs( - const PrintConfig ¤t_config, - const DynamicPrintConfig &new_full_config, - DynamicPrintConfig &filament_overrides) -{ - const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); - const std::string filament_prefix = "filament_"; - t_config_option_keys print_diff; - for (const t_config_option_key &opt_key : current_config.keys()) { - const ConfigOption *opt_old = current_config.option(opt_key); - assert(opt_old != nullptr); - const ConfigOption *opt_new = new_full_config.option(opt_key); - // assert(opt_new != nullptr); - if (opt_new == nullptr) - //FIXME This may happen when executing some test cases. - continue; - const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; - if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { - // An extruder retract override is available at some of the filament presets. - bool overriden = opt_new->overriden_by(opt_new_filament); - if (overriden || *opt_old != *opt_new) { - auto opt_copy = opt_new->clone(); - opt_copy->apply_override(opt_new_filament); - bool changed = *opt_old != *opt_copy; - if (changed) - print_diff.emplace_back(opt_key); - if (changed || overriden) { - // filament_overrides will be applied to the placeholder parser, which layers these parameters over full_print_config. - filament_overrides.set_key_value(opt_key, opt_copy); - } else - delete opt_copy; - } - } else if (*opt_new != *opt_old) - print_diff.emplace_back(opt_key); - } - - return print_diff; -} - -// Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser. -static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig ¤t_full_config, const DynamicPrintConfig &new_full_config) -{ - t_config_option_keys full_config_diff; - for (const t_config_option_key &opt_key : new_full_config.keys()) { - const ConfigOption *opt_old = current_full_config.option(opt_key); - const ConfigOption *opt_new = new_full_config.option(opt_key); - if (opt_old == nullptr || *opt_new != *opt_old) - full_config_diff.emplace_back(opt_key); - } - return full_config_diff; -} - -// Repository for solving partial overlaps of ModelObject::layer_config_ranges. -// Here the const DynamicPrintConfig* point to the config in ModelObject::layer_config_ranges. -class LayerRanges -{ -public: - struct LayerRange { - t_layer_height_range layer_height_range; - // Config is owned by the associated ModelObject. - const DynamicPrintConfig* config { nullptr }; - - bool operator<(const LayerRange &rhs) const throw() { return this->layer_height_range < rhs.layer_height_range; } - }; - - LayerRanges() = default; - LayerRanges(const t_layer_config_ranges &in) { this->assign(in); } - - // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs. - void assign(const t_layer_config_ranges &in) { - m_ranges.clear(); - m_ranges.reserve(in.size()); - // Input ranges are sorted lexicographically. First range trims the other ranges. - coordf_t last_z = 0; - for (const std::pair &range : in) - if (range.first.second > last_z) { - coordf_t min_z = std::max(range.first.first, 0.); - if (min_z > last_z + EPSILON) { - m_ranges.push_back({ t_layer_height_range(last_z, min_z) }); - last_z = min_z; - } - if (range.first.second > last_z + EPSILON) { - const DynamicPrintConfig *cfg = &range.second.get(); - m_ranges.push_back({ t_layer_height_range(last_z, range.first.second), cfg }); - last_z = range.first.second; - } - } - if (m_ranges.empty()) - m_ranges.push_back({ t_layer_height_range(0, DBL_MAX) }); - else if (m_ranges.back().config == nullptr) - m_ranges.back().layer_height_range.second = DBL_MAX; - else - m_ranges.push_back({ t_layer_height_range(m_ranges.back().layer_height_range.second, DBL_MAX) }); - } - - const DynamicPrintConfig* config(const t_layer_height_range &range) const { - auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), LayerRange{ { range.first - EPSILON, range.second - EPSILON } }); - // #ys_FIXME_COLOR - // assert(it != m_ranges.end()); - // assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON); - // assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); - if (it == m_ranges.end() || - std::abs(it->layer_height_range.first - range.first) > EPSILON || - std::abs(it->layer_height_range.second - range.second) > EPSILON ) - return nullptr; // desired range doesn't found - return it == m_ranges.end() ? nullptr : it->config; - } - - std::vector::const_iterator begin() const { return m_ranges.cbegin(); } - std::vector::const_iterator end () const { return m_ranges.cend(); } - size_t size () const { return m_ranges.size(); } - -private: - // Layer ranges with their config overrides and list of volumes with their snug bounding boxes in a given layer range. - std::vector m_ranges; -}; - -// To track Model / ModelObject updates between the front end and back end, including layer height ranges, their configs, -// and snug bounding boxes of ModelVolumes. -struct ModelObjectStatus { - enum Status { - Unknown, - Old, - New, - Moved, - Deleted, - }; - - enum class PrintObjectRegionsStatus { - Invalid, - Valid, - PartiallyValid, - }; - - ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} - ~ModelObjectStatus() { if (print_object_regions) print_object_regions->ref_cnt_dec(); } - - // Key of the set. - ObjectID id; - // Status of this ModelObject with id on apply(). - Status status; - // PrintObjects to be generated for this ModelObject including their base transformation. - std::vector print_instances; - // Regions shared by the associated PrintObjects. - PrintObjectRegions *print_object_regions { nullptr }; - // Status of the above. - PrintObjectRegionsStatus print_object_regions_status { PrintObjectRegionsStatus::Invalid }; - - // Search by id. - bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } -}; - -struct ModelObjectStatusDB -{ - void add(const ModelObject &model_object, const ModelObjectStatus::Status status) { - assert(db.find(ModelObjectStatus(model_object.id())) == db.end()); - db.emplace(model_object.id(), status); - } - - bool add_if_new(const ModelObject &model_object, const ModelObjectStatus::Status status) { - auto it = db.find(ModelObjectStatus(model_object.id())); - if (it == db.end()) { - db.emplace_hint(it, model_object.id(), status); - return true; - } - return false; - } - - const ModelObjectStatus& get(const ModelObject &model_object) { - auto it = db.find(ModelObjectStatus(model_object.id())); - assert(it != db.end()); - return *it; - } - - const ModelObjectStatus& reuse(const ModelObject &model_object) { - const ModelObjectStatus &result = this->get(model_object); - assert(result.status != ModelObjectStatus::Deleted); - return result; - } - - std::set db; -}; - -struct PrintObjectStatus { - enum Status { - Unknown, - Deleted, - Reused, - New - }; - - PrintObjectStatus(PrintObject *print_object, Status status = Unknown) : - id(print_object->model_object()->id()), - print_object(print_object), - trafo(print_object->trafo()), - status(status) {} - PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} - - // ID of the ModelObject & PrintObject - ObjectID id; - // Pointer to the old PrintObject - PrintObject *print_object; - // Trafo generated with model_object->world_matrix(true) - Transform3d trafo; - Status status; - - // Search by id. - bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; } -}; - -class PrintObjectStatusDB { -public: - using iterator = std::multiset::iterator; - using const_iterator = std::multiset::const_iterator; - - PrintObjectStatusDB(const PrintObjectPtrs &print_objects) { - for (PrintObject *print_object : print_objects) - m_db.emplace(PrintObjectStatus(print_object)); - } - - struct iterator_range : std::pair - { - using std::pair::pair; - iterator_range(const std::pair in) : std::pair(in) {} - - const_iterator begin() throw() { return this->first; } - const_iterator end() throw() { return this->second; } - }; - - iterator_range get_range(const ModelObject &model_object) const { - return m_db.equal_range(PrintObjectStatus(model_object.id())); - } - - iterator_range get_range(const ModelObjectStatus &model_object_status) const { - return m_db.equal_range(PrintObjectStatus(model_object_status.id)); - } - - size_t count(const ModelObject &model_object) { - return m_db.count(PrintObjectStatus(model_object.id())); - } - - std::multiset::iterator begin() { return m_db.begin(); } - std::multiset::iterator end() { return m_db.end(); } - - void clear() { - m_db.clear(); - } - -private: - std::multiset m_db; -}; - -static inline bool model_volume_solid_or_modifier(const ModelVolume &mv) -{ - ModelVolumeType type = mv.type(); - return type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER; -} - -static inline Transform3f trafo_for_bbox(const Transform3d &object_trafo, const Transform3d &volume_trafo) -{ - Transform3d m = object_trafo * volume_trafo; - m.translation().x() = 0.; - m.translation().y() = 0.; - return m.cast(); -} - -static inline bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transform3d &t1, const Transform3d &t2) -{ - if (std::abs(t1.translation().z() - t2.translation().z()) > EPSILON) - // One of the object is higher than the other above the build plate (or below the build plate). - return false; - Matrix3d m1 = t1.matrix().block<3, 3>(0, 0); - Matrix3d m2 = t2.matrix().block<3, 3>(0, 0); - Matrix3d m = m2.inverse() * m1; - Vec3d z = m.block<3, 1>(0, 2); - if (std::abs(z.x()) > EPSILON || std::abs(z.y()) > EPSILON || std::abs(z.z() - 1.) > EPSILON) - // Z direction or length changed. - return false; - // Z still points in the same direction and it has the same length. - Vec3d x = m.block<3, 1>(0, 0); - Vec3d y = m.block<3, 1>(0, 1); - if (std::abs(x.z()) > EPSILON || std::abs(y.z()) > EPSILON) - return false; - double lx2 = x.squaredNorm(); - double ly2 = y.squaredNorm(); - if (lx2 - 1. > EPSILON * EPSILON || ly2 - 1. > EPSILON * EPSILON) - return false; - // Verify whether the vectors x, y are still perpendicular. - double d = x.dot(y); - return std::abs(d * d) < EPSILON * lx2 * ly2; -} - -static PrintObjectRegions::BoundingBox transformed_its_bbox2d(const indexed_triangle_set &its, const Transform3f &m, float offset) -{ - assert(! its.indices.empty()); - - PrintObjectRegions::BoundingBox bbox(m * its.vertices[its.indices.front()(0)]); - for (const stl_triangle_vertex_indices &tri : its.indices) - for (int i = 0; i < 3; ++ i) - bbox.extend(m * its.vertices[tri(i)]); - bbox.min() -= Vec3f(offset, offset, float(EPSILON)); - bbox.max() += Vec3f(offset, offset, float(EPSILON)); - return bbox; -} - -static void transformed_its_bboxes_in_z_ranges( - const indexed_triangle_set &its, - const Transform3f &m, - const std::vector &z_ranges, - std::vector> &bboxes, - const float offset) -{ - bboxes.assign(z_ranges.size(), std::make_pair(PrintObjectRegions::BoundingBox(), false)); - for (const stl_triangle_vertex_indices &tri : its.indices) { - const Vec3f pts[3] = { m * its.vertices[tri(0)], m * its.vertices[tri(1)], m * its.vertices[tri(2)] }; - for (size_t irange = 0; irange < z_ranges.size(); ++ irange) { - const t_layer_height_range &z_range = z_ranges[irange]; - std::pair &bbox = bboxes[irange]; - auto bbox_extend = [&bbox](const Vec3f& p) { - if (bbox.second) { - bbox.first.extend(p); - } else { - bbox.first.min() = bbox.first.max() = p; - bbox.second = true; - } - }; - int iprev = 2; - for (int iedge = 0; iedge < 3; ++ iedge) { - const Vec3f *p1 = &pts[iprev]; - const Vec3f *p2 = &pts[iedge]; - // Sort the edge points by Z. - if (p1->z() > p2->z()) - std::swap(p1, p2); - if (p2->z() <= z_range.first || p1->z() >= z_range.second) { - // Out of this slab. - } else if (p1->z() < z_range.first) { - if (p1->z() > z_range.second) { - // Two intersections. - float zspan = p2->z() - p1->z(); - float t1 = (z_range.first - p1->z()) / zspan; - float t2 = (z_range.second - p1->z()) / zspan; - Vec2f p = to_2d(*p1); - Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); - bbox_extend(to_3d((p + v * t1).eval(), float(z_range.first))); - bbox_extend(to_3d((p + v * t2).eval(), float(z_range.second))); - } else { - // Single intersection with the lower limit. - float t = (z_range.first - p1->z()) / (p2->z() - p1->z()); - Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); - bbox_extend(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.first))); - bbox_extend(*p2); - } - } else if (p2->z() > z_range.second) { - // Single intersection with the upper limit. - float t = (z_range.second - p1->z()) / (p2->z() - p1->z()); - Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); - bbox_extend(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.second))); - bbox_extend(*p1); - } else { - // Both points are inside. - bbox_extend(*p1); - bbox_extend(*p2); - } - iprev = iedge; - } - } - } - - for (std::pair &bbox : bboxes) { - bbox.first.min() -= Vec3f(offset, offset, float(EPSILON)); - bbox.first.max() += Vec3f(offset, offset, float(EPSILON)); - } -} - -// Last PrintObject for this print_object_regions has been fully invalidated (deleted). -// Keep print_object_regions, but delete those volumes, which were either removed from new_volumes, or which rotated or scaled, so they need -// their bounding boxes to be recalculated. -void print_objects_regions_invalidate_keep_some_volumes(PrintObjectRegions &print_object_regions, ModelVolumePtrs old_volumes, ModelVolumePtrs new_volumes) -{ - print_object_regions.all_regions.clear(); - - model_volumes_sort_by_id(old_volumes); - model_volumes_sort_by_id(new_volumes); - - size_t i_cached_volume = 0; - size_t last_cached_volume = 0; - size_t i_old = 0; - for (size_t i_new = 0; i_new < new_volumes.size(); ++ i_new) - if (model_volume_solid_or_modifier(*new_volumes[i_new])) { - for (; i_old < old_volumes.size(); ++ i_old) - if (old_volumes[i_old]->id() >= new_volumes[i_new]->id()) - break; - if (i_old != old_volumes.size() && old_volumes[i_old]->id() == new_volumes[i_new]->id()) { - if (old_volumes[i_old]->get_matrix().isApprox(new_volumes[i_new]->get_matrix())) { - // Reuse the volume. - for (; print_object_regions.cached_volume_ids[i_cached_volume] < old_volumes[i_old]->id(); ++ i_cached_volume) - assert(i_cached_volume < print_object_regions.cached_volume_ids.size()); - assert(i_cached_volume < print_object_regions.cached_volume_ids.size() && print_object_regions.cached_volume_ids[i_cached_volume] == old_volumes[i_old]->id()); - print_object_regions.cached_volume_ids[last_cached_volume ++] = print_object_regions.cached_volume_ids[i_cached_volume ++]; - } else { - // Don't reuse the volume. - } - } - } - print_object_regions.cached_volume_ids.erase(print_object_regions.cached_volume_ids.begin() + last_cached_volume, print_object_regions.cached_volume_ids.end()); -} - -// Find a bounding box of a volume's part intersecting layer_range. Such a bounding box will likely be smaller in XY than the full bounding box, -// thus it will intersect with lower number of other volumes. -const PrintObjectRegions::BoundingBox* find_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const ModelVolume &volume) -{ - auto it = lower_bound_by_predicate(layer_range.volumes.begin(), layer_range.volumes.end(), [&volume](const PrintObjectRegions::VolumeExtents &l){ return l.volume_id < volume.id(); }); - return it != layer_range.volumes.end() && it->volume_id == volume.id() ? &it->bbox : nullptr; -} - -// Find a bounding box of a topmost printable volume referenced by this modifier given this_region_id. -PrintObjectRegions::BoundingBox find_modifier_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const int this_region_id) -{ - // Find the top-most printable volume of this modifier, or the printable volume itself. - const PrintObjectRegions::VolumeRegion &this_region = layer_range.volume_regions[this_region_id]; - const PrintObjectRegions::BoundingBox *this_extents = find_volume_extents(layer_range, *this_region.model_volume); - assert(this_extents); - PrintObjectRegions::BoundingBox out { *this_extents }; - if (! this_region.model_volume->is_model_part()) - for (int parent_region_id = this_region.parent;;) { - assert(parent_region_id >= 0); - const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; - const PrintObjectRegions::BoundingBox *parent_extents = find_volume_extents(layer_range, *parent_region.model_volume); - assert(parent_extents); - out.extend(*parent_extents); - if (parent_region.model_volume->is_model_part()) - break; - parent_region_id = parent_region.parent; - } - return out; -} - -PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders); - -void print_region_ref_inc(PrintRegion &r) { ++ r.m_ref_cnt; } -void print_region_ref_reset(PrintRegion &r) { r.m_ref_cnt = 0; } -int print_region_ref_cnt(const PrintRegion &r) { return r.m_ref_cnt; } - -// Verify whether the PrintRegions of a PrintObject are still valid, possibly after updating the region configs. -// Before region configs are updated, callback_invalidate() is called to possibly stop background processing. -// Returns false if this object needs to be resliced because regions were merged or split. -bool verify_update_print_object_regions( - ModelVolumePtrs model_volumes, - const PrintRegionConfig &default_region_config, - size_t num_extruders, - const std::vector &painting_extruders, - PrintObjectRegions &print_object_regions, - const std::function &callback_invalidate) -{ - // Sort by ModelVolume ID. - model_volumes_sort_by_id(model_volumes); - - for (std::unique_ptr ®ion : print_object_regions.all_regions) - print_region_ref_reset(*region); - - // Verify and / or update PrintRegions produced by ModelVolumes, layer range modifiers, modifier volumes. - for (PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) { - // Each modifier ModelVolume intersecting this layer_range shall be referenced here at least once if it intersects some - // printable ModelVolume at this layer_range even if it does not modify its overlapping printable ModelVolume configuration yet. - // VolumeRegions reference ModelVolumes in layer_range.volume_regions the order they are stored in ModelObject volumes. - // Remember whether a given modifier ModelVolume was visited already. - auto it_model_volume_modifier_last = model_volumes.end(); - for (PrintObjectRegions::VolumeRegion ®ion : layer_range.volume_regions) - if (region.model_volume->is_model_part() || region.model_volume->is_modifier()) { - auto it_model_volume = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [®ion](const ModelVolume *l){ return l->id() < region.model_volume->id(); }); - assert(it_model_volume != model_volumes.end() && (*it_model_volume)->id() == region.model_volume->id()); - if (region.model_volume->is_modifier() && it_model_volume != it_model_volume_modifier_last) { - // A modifier ModelVolume is visited for the first time. - // A visited modifier may not have had parent volume_regions created overlapping with some model parts or modifiers, - // if the visited modifier did not modify their properties. Now the visited modifier's configuration may have changed, - // which may require new regions to be created. - it_model_volume_modifier_last = it_model_volume; - int next_region_id = int(®ion - layer_range.volume_regions.data()); - const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, *region.model_volume); - assert(bbox); - for (int parent_region_id = next_region_id - 1; parent_region_id >= 0; -- parent_region_id) { - const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; - assert(parent_region.model_volume != region.model_volume); - if (parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) { - // volume_regions are produced in decreasing order of parent volume_regions ids. - // Some regions may not have been generated the last time by generate_print_object_regions(). - assert(next_region_id == int(layer_range.volume_regions.size()) || - layer_range.volume_regions[next_region_id].model_volume != region.model_volume || - layer_range.volume_regions[next_region_id].parent <= parent_region_id); - if (next_region_id < int(layer_range.volume_regions.size()) && - layer_range.volume_regions[next_region_id].model_volume == region.model_volume && - layer_range.volume_regions[next_region_id].parent == parent_region_id) { - // A parent region is already overridden. - ++ next_region_id; - } else if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) - // Such parent region does not exist. If it is needed, then we need to reslice. - // Only create new region for a modifier, which actually modifies config of it's parent. - if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, **it_model_volume, num_extruders); - config != parent_region.region->config()) - // This modifier newly overrides a region, which it did not before. We need to reslice. - return false; - } - } - } - PrintRegionConfig cfg = region.parent == -1 ? - region_config_from_model_volume(default_region_config, layer_range.config, **it_model_volume, num_extruders) : - region_config_from_model_volume(layer_range.volume_regions[region.parent].region->config(), nullptr, **it_model_volume, num_extruders); - if (cfg != region.region->config()) { - // Region configuration changed. - if (print_region_ref_cnt(*region.region) == 0) { - // Region is referenced for the first time. Just change its parameters. - // Stop the background process before assigning new configuration to the regions. - t_config_option_keys diff = region.region->config().diff(cfg); - callback_invalidate(region.region->config(), cfg, diff); - region.region->config_apply_only(cfg, diff, false); - } else { - // Region is referenced multiple times, thus the region is being split. We need to reslice. - return false; - } - } - print_region_ref_inc(*region.region); - } - } - - // Verify and / or update PrintRegions produced by color painting. - for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) - for (const PrintObjectRegions::PaintedRegion ®ion : layer_range.painted_regions) { - const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[region.parent]; - PrintRegionConfig cfg = parent_region.region->config(); - cfg.perimeter_extruder.value = region.extruder_id; - cfg.solid_infill_extruder.value = region.extruder_id; - cfg.infill_extruder.value = region.extruder_id; - if (cfg != region.region->config()) { - // Region configuration changed. - if (print_region_ref_cnt(*region.region) == 0) { - // Region is referenced for the first time. Just change its parameters. - // Stop the background process before assigning new configuration to the regions. - t_config_option_keys diff = region.region->config().diff(cfg); - callback_invalidate(region.region->config(), cfg, diff); - region.region->config_apply_only(cfg, diff, false); - } else { - // Region is referenced multiple times, thus the region is being split. We need to reslice. - return false; - } - } - print_region_ref_inc(*region.region); - } - - // Lastly verify, whether some regions were not merged. - { - std::vector regions; - regions.reserve(print_object_regions.all_regions.size()); - for (std::unique_ptr ®ion : print_object_regions.all_regions) { - assert(print_region_ref_cnt(*region) > 0); - regions.emplace_back(&(*region.get())); - } - std::sort(regions.begin(), regions.end(), [](const PrintRegion *l, const PrintRegion *r){ return l->config_hash() < r->config_hash(); }); - for (size_t i = 0; i < regions.size(); ++ i) { - size_t hash = regions[i]->config_hash(); - size_t j = i; - for (++ j; j < regions.size() && regions[j]->config_hash() == hash; ++ j) - if (regions[i]->config() == regions[j]->config()) { - // Regions were merged. We need to reslice. - return false; - } - } - } - - return true; -} - -// Update caches of volume bounding boxes. -void update_volume_bboxes( - std::vector &layer_ranges, - std::vector &cached_volume_ids, - ModelVolumePtrs model_volumes, - const Transform3d &object_trafo, - const float offset) -{ - // output will be sorted by the order of model_volumes sorted by their ObjectIDs. - model_volumes_sort_by_id(model_volumes); - - if (layer_ranges.size() == 1) { - PrintObjectRegions::LayerRangeRegions &layer_range = layer_ranges.front(); - std::vector volumes_old(std::move(layer_range.volumes)); - layer_range.volumes.reserve(model_volumes.size()); - for (const ModelVolume *model_volume : model_volumes) - if (model_volume_solid_or_modifier(*model_volume)) { - if (std::binary_search(cached_volume_ids.begin(), cached_volume_ids.end(), model_volume->id())) { - auto it = lower_bound_by_predicate(volumes_old.begin(), volumes_old.end(), [model_volume](PrintObjectRegions::VolumeExtents &l) { return l.volume_id < model_volume->id(); }); - if (it != volumes_old.end() && it->volume_id == model_volume->id()) - layer_range.volumes.emplace_back(*it); - } else - layer_range.volumes.push_back({ model_volume->id(), - transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), offset) }); - } - } else { - std::vector> volumes_old; - if (cached_volume_ids.empty()) - for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) - layer_range.volumes.clear(); - else { - volumes_old.reserve(layer_ranges.size()); - for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) - volumes_old.emplace_back(std::move(layer_range.volumes)); - } - - std::vector> bboxes; - std::vector ranges; - ranges.reserve(layer_ranges.size()); - for (const PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) { - t_layer_height_range r = layer_range.layer_height_range; - r.first -= EPSILON; - r.second += EPSILON; - ranges.emplace_back(r); - } - for (const ModelVolume *model_volume : model_volumes) - if (model_volume_solid_or_modifier(*model_volume)) { - if (std::binary_search(cached_volume_ids.begin(), cached_volume_ids.end(), model_volume->id())) { - for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) { - const auto &vold = volumes_old[&layer_range - layer_ranges.data()]; - auto it = lower_bound_by_predicate(vold.begin(), vold.end(), [model_volume](const PrintObjectRegions::VolumeExtents &l) { return l.volume_id < model_volume->id(); }); - if (it != vold.end() && it->volume_id == model_volume->id()) - layer_range.volumes.emplace_back(*it); - } - } else { - transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), ranges, bboxes, offset); - for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) - if (auto &bbox = bboxes[&layer_range - layer_ranges.data()]; bbox.second) - layer_range.volumes.push_back({ model_volume->id(), bbox.first }); - } - } - } - - cached_volume_ids.clear(); - cached_volume_ids.reserve(model_volumes.size()); - for (const ModelVolume *v : model_volumes) - if (model_volume_solid_or_modifier(*v)) - cached_volume_ids.emplace_back(v->id()); -} - -// Either a fresh PrintObject, or PrintObject regions were invalidated (merged, split). -// Generate PrintRegions from scratch. -static PrintObjectRegions* generate_print_object_regions( - PrintObjectRegions *print_object_regions_old, - const ModelVolumePtrs &model_volumes, - const LayerRanges &model_layer_ranges, - const PrintRegionConfig &default_region_config, - const Transform3d &trafo, - size_t num_extruders, - const float xy_size_compensation, - const std::vector &painting_extruders) -{ - // Reuse the old object or generate a new one. - auto out = print_object_regions_old ? std::unique_ptr(print_object_regions_old) : std::make_unique(); - auto &all_regions = out->all_regions; - auto &layer_ranges_regions = out->layer_ranges; - - all_regions.clear(); - - bool reuse_old = print_object_regions_old && !print_object_regions_old->layer_ranges.empty(); - - if (reuse_old) { - // Reuse old bounding boxes of some ModelVolumes and their ranges. - // Verify that the old ranges match the new ranges. - assert(model_layer_ranges.size() == layer_ranges_regions.size()); - for (const auto &range : model_layer_ranges) { - PrintObjectRegions::LayerRangeRegions &r = layer_ranges_regions[&range - &*model_layer_ranges.begin()]; - assert(range.layer_height_range == r.layer_height_range); - // If model::assign_copy() is called, layer_ranges_regions is copied thus the pointers to configs are lost. - r.config = range.config; - r.volume_regions.clear(); - r.painted_regions.clear(); - } - } else { - out->trafo_bboxes = trafo; - layer_ranges_regions.reserve(model_layer_ranges.size()); - for (const auto &range : model_layer_ranges) - layer_ranges_regions.push_back({ range.layer_height_range, range.config }); - } - - const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); - update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_size_compensation)); - - std::vector region_set; - auto get_create_region = [®ion_set, &all_regions](PrintRegionConfig &&config) -> PrintRegion* { - size_t hash = config.hash(); - auto it = Slic3r::lower_bound_by_predicate(region_set.begin(), region_set.end(), [&config, hash](const PrintRegion* l) { - return l->config_hash() < hash || (l->config_hash() == hash && l->config() < config); }); - if (it != region_set.end() && (*it)->config_hash() == hash && (*it)->config() == config) - return *it; - // Insert into a sorted array, it has O(n) complexity, but the calling algorithm has an O(n^2*log(n)) complexity anyways. - all_regions.emplace_back(std::make_unique(std::move(config), hash, int(all_regions.size()))); - PrintRegion *region = all_regions.back().get(); - region_set.emplace(it, region); - return region; - }; - - // Chain the regions in the order they are stored in the volumes list. - for (int volume_id = 0; volume_id < int(model_volumes.size()); ++ volume_id) { - const ModelVolume &volume = *model_volumes[volume_id]; - if (model_volume_solid_or_modifier(volume)) { - for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) - if (const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, volume); bbox) { - if (volume.is_model_part()) { - // Add a model volume, assign an existing region or generate a new one. - layer_range.volume_regions.push_back({ - &volume, -1, - get_create_region(region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders)), - bbox - }); - } else if (volume.is_negative_volume()) { - // Add a negative (subtractor) volume. Such volume has neither region nor parent volume assigned. - layer_range.volume_regions.push_back({ &volume, -1, nullptr, bbox }); - } else { - assert(volume.is_modifier()); - // Modifiers may be chained one over the other. Check for overlap, merge DynamicPrintConfigs. - bool added = false; - int parent_model_part_id = -1; - for (int parent_region_id = int(layer_range.volume_regions.size()) - 1; parent_region_id >= 0; -- parent_region_id) { - const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; - const ModelVolume &parent_volume = *parent_region.model_volume; - if (parent_volume.is_model_part() || parent_volume.is_modifier()) - if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) { - // Only create new region for a modifier, which actually modifies config of it's parent. - if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders); - config != parent_region.region->config()) { - added = true; - layer_range.volume_regions.push_back({ &volume, parent_region_id, get_create_region(std::move(config)), bbox }); - } else if (parent_model_part_id == -1 && parent_volume.is_model_part()) - parent_model_part_id = parent_region_id; - } - } - if (! added && parent_model_part_id >= 0) - // This modifier does not override any printable volume's configuration, however it may in the future. - // Store it so that verify_update_print_object_regions() will handle this modifier correctly if its configuration changes. - layer_range.volume_regions.push_back({ &volume, parent_model_part_id, layer_range.volume_regions[parent_model_part_id].region, bbox }); - } - } - } - } - - // Finally add painting regions. - for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) { - for (unsigned int painted_extruder_id : painting_extruders) - for (int parent_region_id = 0; parent_region_id < int(layer_range.volume_regions.size()); ++ parent_region_id) - if (const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; - parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) { - PrintRegionConfig cfg = parent_region.region->config(); - cfg.perimeter_extruder.value = painted_extruder_id; - cfg.solid_infill_extruder.value = painted_extruder_id; - cfg.infill_extruder.value = painted_extruder_id; - layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))}); - } - // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MMU segmentation. - std::sort(layer_range.painted_regions.begin(), layer_range.painted_regions.end(), [&layer_range](auto &l, auto &r) { - int lid = layer_range.volume_regions[l.parent].region->print_object_region_id(); - int rid = layer_range.volume_regions[r.parent].region->print_object_region_id(); - return lid < rid || (lid == rid && l.extruder_id < r.extruder_id); }); - } - - return out.release(); -} - -Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config) -{ -#ifdef _DEBUG - check_model_ids_validity(model); -#endif /* _DEBUG */ - - // Normalize the config. - new_full_config.option("print_settings_id", true); - new_full_config.option("filament_settings_id", true); - new_full_config.option("printer_settings_id", true); - new_full_config.option("physical_printer_settings_id", true); - new_full_config.normalize_fdm(); - - // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. - DynamicPrintConfig filament_overrides; - t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides); - t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config); - // Collect changes to object and region configs. - t_config_option_keys object_diff = m_default_object_config.diff(new_full_config); - t_config_option_keys region_diff = m_default_region_config.diff(new_full_config); - - // Do not use the ApplyStatus as we will use the max function when updating apply_status. - unsigned int apply_status = APPLY_STATUS_UNCHANGED; - auto update_apply_status = [&apply_status](bool invalidated) - { apply_status = std::max(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); }; - if (! (print_diff.empty() && object_diff.empty() && region_diff.empty())) - update_apply_status(false); - - // Grab the lock for the Print / PrintObject milestones. - std::scoped_lock lock(this->state_mutex()); - - // The following call may stop the background processing. - if (! print_diff.empty()) - update_apply_status(this->invalidate_state_by_config_options(new_full_config, print_diff)); - - // Apply variables to placeholder parser. The placeholder parser is used by G-code export, - // which should be stopped if print_diff is not empty. - size_t num_extruders = m_config.nozzle_diameter.size(); - bool num_extruders_changed = false; - if (! full_config_diff.empty()) { - update_apply_status(this->invalidate_step(psGCodeExport)); - m_placeholder_parser.clear_config(); - // Set the profile aliases for the PrintBase::output_filename() - m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone()); - m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); - m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); - m_placeholder_parser.set("physical_printer_preset", new_full_config.option("physical_printer_settings_id")->clone()); - // We want the filament overrides to be applied over their respective extruder parameters by the PlaceholderParser. - // see "Placeholders do not respect filament overrides." GH issue #3649 - m_placeholder_parser.apply_config(filament_overrides); - // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. - m_config.apply_only(new_full_config, print_diff, true); - //FIXME use move semantics once ConfigBase supports it. - // Some filament_overrides may contain values different from new_full_config, but equal to m_config. - // As long as these config options don't reallocate memory when copying, we are safe overriding a value, which is in use by a worker thread. - m_config.apply(filament_overrides); - // Handle changes to object config defaults - m_default_object_config.apply_only(new_full_config, object_diff, true); - // Handle changes to regions config defaults - m_default_region_config.apply_only(new_full_config, region_diff, true); - m_full_print_config = std::move(new_full_config); - if (num_extruders != m_config.nozzle_diameter.size()) { - num_extruders = m_config.nozzle_diameter.size(); - num_extruders_changed = true; - } - } - - ModelObjectStatusDB model_object_status_db; - - // 1) Synchronize model objects. - bool print_regions_reshuffled = false; - if (model.id() != m_model.id()) { - // Kill everything, initialize from scratch. - // Stop background processing. - this->call_cancel_callback(); - update_apply_status(this->invalidate_all_steps()); - for (PrintObject *object : m_objects) { - model_object_status_db.add(*object->model_object(), ModelObjectStatus::Deleted); - update_apply_status(object->invalidate_all_steps()); - delete object; - } - m_objects.clear(); - print_regions_reshuffled = true; - m_model.assign_copy(model); - for (const ModelObject *model_object : m_model.objects) - model_object_status_db.add(*model_object, ModelObjectStatus::New); - } else { - if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { - update_apply_status(num_extruders_changed || - // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering. - //FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable - // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same. - (num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes)) ? - // The Tool Ordering and the Wipe Tower are no more valid. - this->invalidate_steps({ psWipeTower, psGCodeExport }) : - // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering. - this->invalidate_step(psGCodeExport)); - m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; - } - if (model_object_list_equal(m_model, model)) { - // The object list did not change. - for (const ModelObject *model_object : m_model.objects) - model_object_status_db.add(*model_object, ModelObjectStatus::Old); - } else if (model_object_list_extended(m_model, model)) { - // Add new objects. Their volumes and configs will be synchronized later. - update_apply_status(this->invalidate_step(psGCodeExport)); - for (const ModelObject *model_object : m_model.objects) - model_object_status_db.add(*model_object, ModelObjectStatus::Old); - for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) { - model_object_status_db.add(*model.objects[i], ModelObjectStatus::New); - m_model.objects.emplace_back(ModelObject::new_copy(*model.objects[i])); - m_model.objects.back()->set_model(&m_model); - } - } else { - // Reorder the objects, add new objects. - // First stop background processing before shuffling or deleting the PrintObjects in the object list. - this->call_cancel_callback(); - update_apply_status(this->invalidate_step(psGCodeExport)); - // Second create a new list of objects. - std::vector model_objects_old(std::move(m_model.objects)); - m_model.objects.clear(); - m_model.objects.reserve(model.objects.size()); - auto by_id_lower = [](const ModelObject *lhs, const ModelObject *rhs){ return lhs->id() < rhs->id(); }; - std::sort(model_objects_old.begin(), model_objects_old.end(), by_id_lower); - for (const ModelObject *mobj : model.objects) { - auto it = std::lower_bound(model_objects_old.begin(), model_objects_old.end(), mobj, by_id_lower); - if (it == model_objects_old.end() || (*it)->id() != mobj->id()) { - // New ModelObject added. - m_model.objects.emplace_back(ModelObject::new_copy(*mobj)); - m_model.objects.back()->set_model(&m_model); - model_object_status_db.add(*mobj, ModelObjectStatus::New); - } else { - // Existing ModelObject re-added (possibly moved in the list). - m_model.objects.emplace_back(*it); - model_object_status_db.add(*mobj, ModelObjectStatus::Moved); - } - } - bool deleted_any = false; - for (ModelObject *&model_object : model_objects_old) - if (model_object_status_db.add_if_new(*model_object, ModelObjectStatus::Deleted)) - deleted_any = true; - else - // Do not delete this ModelObject instance. - model_object = nullptr; - if (deleted_any) { - // Delete PrintObjects of the deleted ModelObjects. - PrintObjectPtrs print_objects_old = std::move(m_objects); - m_objects.clear(); - m_objects.reserve(print_objects_old.size()); - for (PrintObject *print_object : print_objects_old) { - const ModelObjectStatus &status = model_object_status_db.get(*print_object->model_object()); - if (status.status == ModelObjectStatus::Deleted) { - update_apply_status(print_object->invalidate_all_steps()); - delete print_object; - } else - m_objects.emplace_back(print_object); - } - for (ModelObject *model_object : model_objects_old) - delete model_object; - print_regions_reshuffled = true; - } - } - } - - // 2) Map print objects including their transformation matrices. - PrintObjectStatusDB print_object_status_db(m_objects); - - // 3) Synchronize ModelObjects & PrintObjects. - const std::initializer_list solid_or_modifier_types { ModelVolumeType::MODEL_PART, ModelVolumeType::NEGATIVE_VOLUME, ModelVolumeType::PARAMETER_MODIFIER }; - for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { - ModelObject &model_object = *m_model.objects[idx_model_object]; - ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(model_object)); - const ModelObject &model_object_new = *model.objects[idx_model_object]; - if (model_object_status.status == ModelObjectStatus::New) - // PrintObject instances will be added in the next loop. - continue; - // Update the ModelObject instance, possibly invalidate the linked PrintObjects. - assert(model_object_status.status == ModelObjectStatus::Old || model_object_status.status == ModelObjectStatus::Moved); - // Check whether a model part volume was added or removed, their transformations or order changed. - // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked. - bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) || - model_mmu_segmentation_data_changed(model_object, model_object_new) || - (model_object_new.is_mm_painted() && num_extruders_changed); - bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) || - model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); - bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty()); - bool model_origin_translation_differ = model_object.origin_translation != model_object_new.origin_translation; - auto print_objects_range = print_object_status_db.get_range(model_object); - // The list actually can be empty if all instances are out of the print bed. - //assert(print_objects_range.begin() != print_objects_range.end()); - // All PrintObjects in print_objects_range shall point to the same prints_objects_regions - if (print_objects_range.begin() != print_objects_range.end()) { - model_object_status.print_object_regions = print_objects_range.begin()->print_object->m_shared_regions; - model_object_status.print_object_regions->ref_cnt_inc(); - } - if (solid_or_modifier_differ || model_origin_translation_differ || layer_height_ranges_differ || - ! model_object.layer_height_profile.timestamp_matches(model_object_new.layer_height_profile)) { - // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. - model_object_status.print_object_regions_status = - model_object_status.print_object_regions == nullptr || model_origin_translation_differ || layer_height_ranges_differ ? - // Drop print_objects_regions. - ModelObjectStatus::PrintObjectRegionsStatus::Invalid : - // Reuse bounding boxes of print_objects_regions for ModelVolumes with unmodified transformation. - ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid; - for (const PrintObjectStatus &print_object_status : print_objects_range) { - update_apply_status(print_object_status.print_object->invalidate_all_steps()); - const_cast(print_object_status).status = PrintObjectStatus::Deleted; - } - if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid) - // Drop everything from PrintObjectRegions but those VolumeExtents (of their particular ModelVolumes) that are still valid. - print_objects_regions_invalidate_keep_some_volumes(*model_object_status.print_object_regions, model_object.volumes, model_object_new.volumes); - else if (model_object_status.print_object_regions != nullptr) - model_object_status.print_object_regions->clear(); - // Copy content of the ModelObject including its ID, do not change the parent. - model_object.assign_copy(model_object_new); - } else { - model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::Valid; - if (supports_differ || model_custom_supports_data_changed(model_object, model_object_new)) { - // First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list. - if (supports_differ) { - this->call_cancel_callback(); - update_apply_status(false); - } - // Invalidate just the supports step. - for (const PrintObjectStatus &print_object_status : print_objects_range) - update_apply_status(print_object_status.print_object->invalidate_step(posSupportMaterial)); - if (supports_differ) { - // Copy just the support volumes. - model_volume_list_update_supports(model_object, model_object_new); - } - } else if (model_custom_seam_data_changed(model_object, model_object_new)) { - update_apply_status(this->invalidate_step(psGCodeExport)); - } - } - if (! solid_or_modifier_differ) { - // Synchronize Object's config. - bool object_config_changed = ! model_object.config.timestamp_matches(model_object_new.config); - if (object_config_changed) - model_object.config.assign_config(model_object_new.config); - if (! object_diff.empty() || object_config_changed || num_extruders_changed) { - PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders); - for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(model_object)) { - t_config_option_keys diff = print_object_status.print_object->config().diff(new_config); - if (! diff.empty()) { - update_apply_status(print_object_status.print_object->invalidate_state_by_config_options(print_object_status.print_object->config(), new_config, diff)); - print_object_status.print_object->config_apply_only(new_config, diff, true); - } - } - } - // Synchronize (just copy) the remaining data of ModelVolumes (name, config, custom supports data). - //FIXME What to do with m_material_id? - model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART); - model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER); - layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */); - // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step. - model_object.name = model_object_new.name; - model_object.input_file = model_object_new.input_file; - // Only refresh ModelInstances if there is any change. - if (model_object.instances.size() != model_object_new.instances.size() || - ! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), [](auto l, auto r){ return l->id() == r->id(); })) { - // G-code generator accesses model_object.instances to generate sequential print ordering matching the Plater object list. - update_apply_status(this->invalidate_step(psGCodeExport)); - model_object.clear_instances(); - model_object.instances.reserve(model_object_new.instances.size()); - for (const ModelInstance *model_instance : model_object_new.instances) { - model_object.instances.emplace_back(new ModelInstance(*model_instance)); - model_object.instances.back()->set_model_object(&model_object); - } - } else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), - [](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable && - l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) { - // If some of the instances changed, the bounding box of the updated ModelObject is likely no more valid. - // This is safe as the ModelObject's bounding box is only accessed from this function, which is called from the main thread only. - model_object.invalidate_bounding_box(); - // Synchronize the content of instances. - auto new_instance = model_object_new.instances.begin(); - for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) { - (*old_instance)->set_transformation((*new_instance)->get_transformation()); - (*old_instance)->print_volume_state = (*new_instance)->print_volume_state; - (*old_instance)->printable = (*new_instance)->printable; - } - } - } - } - - // 4) Generate PrintObjects from ModelObjects and their instances. - { - PrintObjectPtrs print_objects_new; - print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size())); - bool new_objects = false; - // Walk over all new model objects and check, whether there are matching PrintObjects. - for (ModelObject *model_object : m_model.objects) { - ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(*model_object)); - model_object_status.print_instances = print_objects_from_model_object(*model_object); - std::vector old; - old.reserve(print_object_status_db.count(*model_object)); - for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(*model_object)) - if (print_object_status.status != PrintObjectStatus::Deleted) - old.emplace_back(&print_object_status); - // Generate a list of trafos and XY offsets for instances of a ModelObject - // Producing the config for PrintObject on demand, caching it at print_object_last. - const PrintObject *print_object_last = nullptr; - auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders](PrintObject *print_object) { - print_object->config_apply(print_object_last ? - print_object_last->config() : - PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders)); - print_object_last = print_object; - }; - if (old.empty()) { - // Simple case, just generate new instances. - for (PrintObjectTrafoAndInstances &print_instances : model_object_status.print_instances) { - PrintObject *print_object = new PrintObject(this, model_object, print_instances.trafo, std::move(print_instances.instances)); - print_object_apply_config(print_object); - print_objects_new.emplace_back(print_object); - // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); - new_objects = true; - } - continue; - } - // Complex case, try to merge the two lists. - // Sort the old lexicographically by their trafos. - std::sort(old.begin(), old.end(), [](const PrintObjectStatus *lhs, const PrintObjectStatus *rhs){ return transform3d_lower(lhs->trafo, rhs->trafo); }); - // Merge the old / new lists. - auto it_old = old.begin(); - for (PrintObjectTrafoAndInstances &new_instances : model_object_status.print_instances) { - for (; it_old != old.end() && transform3d_lower((*it_old)->trafo, new_instances.trafo); ++ it_old); - if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) { - // This is a new instance (or a set of instances with the same trafo). Just add it. - PrintObject *print_object = new PrintObject(this, model_object, new_instances.trafo, std::move(new_instances.instances)); - print_object_apply_config(print_object); - print_objects_new.emplace_back(print_object); - // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); - new_objects = true; - if (it_old != old.end()) - const_cast(*it_old)->status = PrintObjectStatus::Deleted; - } else { - // The PrintObject already exists and the copies differ. - PrintBase::ApplyStatus status = (*it_old)->print_object->set_instances(std::move(new_instances.instances)); - if (status != PrintBase::APPLY_STATUS_UNCHANGED) - update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED); - print_objects_new.emplace_back((*it_old)->print_object); - const_cast(*it_old)->status = PrintObjectStatus::Reused; - } - } - } - if (m_objects != print_objects_new) { - this->call_cancel_callback(); - update_apply_status(this->invalidate_all_steps()); - m_objects = print_objects_new; - // Delete the PrintObjects marked as Unknown or Deleted. - bool deleted_objects = false; - for (const PrintObjectStatus &pos : print_object_status_db) - if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { - update_apply_status(pos.print_object->invalidate_all_steps()); - delete pos.print_object; - deleted_objects = true; - } - if (new_objects || deleted_objects) - update_apply_status(this->invalidate_steps({ psSkirtBrim, psWipeTower, psGCodeExport })); - if (new_objects) - update_apply_status(false); - print_regions_reshuffled = true; - } - print_object_status_db.clear(); - } - - // All regions now have distinct settings. - // Check whether applying the new region config defaults we would get different regions, - // update regions or create regions from scratch. - for (auto it_print_object = m_objects.begin(); it_print_object != m_objects.end();) { - // Find the range of PrintObjects sharing the same associated ModelObject. - auto it_print_object_end = it_print_object; - PrintObject &print_object = *(*it_print_object); - const ModelObject &model_object = *print_object.model_object(); - ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(model_object)); - PrintObjectRegions *print_object_regions = model_object_status.print_object_regions; - for (++ it_print_object_end; it_print_object_end != m_objects.end() && (*it_print_object)->model_object() == (*it_print_object_end)->model_object(); ++ it_print_object_end) - assert((*it_print_object_end)->m_shared_regions == nullptr || (*it_print_object_end)->m_shared_regions == print_object_regions); - if (print_object_regions == nullptr) { - print_object_regions = new PrintObjectRegions{}; - model_object_status.print_object_regions = print_object_regions; - print_object_regions->ref_cnt_inc(); - } - std::vector painting_extruders; - if (const auto &volumes = print_object.model_object()->volumes; - num_extruders > 1 && - std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_segmentation_facets.empty(); }) != volumes.end()) { - //FIXME be more specific! Don't enumerate extruders that are not used for painting! - painting_extruders.assign(num_extruders, 0); - std::iota(painting_extruders.begin(), painting_extruders.end(), 1); - } - if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) { - // Verify that the trafo for regions & volume bounding boxes thus for regions is still applicable. - auto invalidate = [it_print_object, it_print_object_end, update_apply_status]() { - for (auto it = it_print_object; it != it_print_object_end; ++ it) - if ((*it)->m_shared_regions != nullptr) - update_apply_status((*it)->invalidate_all_steps()); - }; - if (print_object_regions && ! trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(print_object_regions->trafo_bboxes, model_object_status.print_instances.front().trafo)) { - invalidate(); - print_object_regions->clear(); - model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::Invalid; - print_regions_reshuffled = true; - } else if (print_object_regions && - verify_update_print_object_regions( - print_object.model_object()->volumes, - m_default_region_config, - num_extruders, - painting_extruders, - *print_object_regions, - [it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) { - for (auto it = it_print_object; it != it_print_object_end; ++it) - if ((*it)->m_shared_regions != nullptr) - update_apply_status((*it)->invalidate_state_by_config_options(old_config, new_config, diff_keys)); - })) { - // Regions are valid, just keep them. - } else { - // Regions were reshuffled. - invalidate(); - // At least reuse layer ranges and bounding boxes of ModelVolumes. - model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid; - print_regions_reshuffled = true; - } - } - if (print_object_regions == nullptr || model_object_status.print_object_regions_status != ModelObjectStatus::PrintObjectRegionsStatus::Valid) { - // Layer ranges with their associated configurations. Remove overlaps between the ranges - // and create the regions from scratch. - print_object_regions = generate_print_object_regions( - print_object_regions, - print_object.model_object()->volumes, - LayerRanges(print_object.model_object()->layer_config_ranges), - m_default_region_config, - model_object_status.print_instances.front().trafo, - num_extruders, - print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value), - painting_extruders); - } - for (auto it = it_print_object; it != it_print_object_end; ++it) - if ((*it)->m_shared_regions) { - assert((*it)->m_shared_regions == print_object_regions); - } else { - (*it)->m_shared_regions = print_object_regions; - print_object_regions->ref_cnt_inc(); - } - it_print_object = it_print_object_end; - } - - if (print_regions_reshuffled) { - // Update Print::m_print_regions from objects. - struct cmp { bool operator() (const PrintRegion *l, const PrintRegion *r) const { return l->config_hash() == r->config_hash() && l->config() == r->config(); } }; - std::set region_set; - m_print_regions.clear(); - PrintObjectRegions *print_object_regions = nullptr; - for (PrintObject *print_object : m_objects) - if (print_object_regions != print_object->m_shared_regions) { - print_object_regions = print_object->m_shared_regions; - for (std::unique_ptr &print_region : print_object_regions->all_regions) - if (auto it = region_set.find(print_region.get()); it == region_set.end()) { - int print_region_id = int(m_print_regions.size()); - m_print_regions.emplace_back(print_region.get()); - print_region->m_print_region_id = print_region_id; - } else { - print_region->m_print_region_id = (*it)->print_region_id(); - } - } - } - - // Update SlicingParameters for each object where the SlicingParameters is not valid. - // If it is not valid, then it is ensured that PrintObject.m_slicing_params is not in use - // (posSlicing and posSupportMaterial was invalidated). - for (PrintObject *object : m_objects) - object->update_slicing_parameters(); - -#ifdef _DEBUG - check_model_ids_equal(m_model, model); -#endif /* _DEBUG */ - - return static_cast(apply_status); -} - -} // namespace Slic3r +#include "Model.hpp" +#include "Print.hpp" + +#include + +namespace Slic3r { + +// Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new +// in the exact order and with the same IDs. +// It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order. +// Friend to ModelVolume to allow copying. +// static is not accepted by gcc if declared as a friend of ModelObject. +/* static */ void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_new) +{ + typedef std::pair ModelVolumeWithStatus; + std::vector old_volumes; + old_volumes.reserve(model_object_dst.volumes.size()); + for (const ModelVolume *model_volume : model_object_dst.volumes) + old_volumes.emplace_back(ModelVolumeWithStatus(model_volume, false)); + auto model_volume_lower = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() < mv2.first->id(); }; + auto model_volume_equal = [](const ModelVolumeWithStatus &mv1, const ModelVolumeWithStatus &mv2){ return mv1.first->id() == mv2.first->id(); }; + std::sort(old_volumes.begin(), old_volumes.end(), model_volume_lower); + model_object_dst.volumes.clear(); + model_object_dst.volumes.reserve(model_object_new.volumes.size()); + for (const ModelVolume *model_volume_src : model_object_new.volumes) { + ModelVolumeWithStatus key(model_volume_src, false); + auto it = std::lower_bound(old_volumes.begin(), old_volumes.end(), key, model_volume_lower); + if (it != old_volumes.end() && model_volume_equal(*it, key)) { + // The volume was found in the old list. Just copy it. + assert(! it->second); // not consumed yet + it->second = true; + ModelVolume *model_volume_dst = const_cast(it->first); + // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. + assert((model_volume_dst->is_support_modifier() && model_volume_src->is_support_modifier()) || model_volume_dst->type() == model_volume_src->type()); + model_object_dst.volumes.emplace_back(model_volume_dst); + if (model_volume_dst->is_support_modifier()) { + // For support modifiers, the type may have been switched from blocker to enforcer and vice versa. + model_volume_dst->set_type(model_volume_src->type()); + model_volume_dst->set_transformation(model_volume_src->get_transformation()); + } + assert(model_volume_dst->get_matrix().isApprox(model_volume_src->get_matrix())); + } else { + // The volume was not found in the old list. Create a new copy. + assert(model_volume_src->is_support_modifier()); + model_object_dst.volumes.emplace_back(new ModelVolume(*model_volume_src)); + model_object_dst.volumes.back()->set_model_object(&model_object_dst); + } + } + // Release the non-consumed old volumes (those were deleted from the new list). + for (ModelVolumeWithStatus &mv_with_status : old_volumes) + if (! mv_with_status.second) + delete mv_with_status.first; +} + +static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolumeType type) +{ + size_t i_src, i_dst; + for (i_src = 0, i_dst = 0; i_src < model_object_src.volumes.size() && i_dst < model_object_dst.volumes.size();) { + const ModelVolume &mv_src = *model_object_src.volumes[i_src]; + ModelVolume &mv_dst = *model_object_dst.volumes[i_dst]; + if (mv_src.type() != type) { + ++ i_src; + continue; + } + if (mv_dst.type() != type) { + ++ i_dst; + continue; + } + assert(mv_src.id() == mv_dst.id()); + // Copy the ModelVolume data. + mv_dst.name = mv_src.name; + mv_dst.config.assign_config(mv_src.config); + assert(mv_dst.supported_facets.id() == mv_src.supported_facets.id()); + mv_dst.supported_facets.assign(mv_src.supported_facets); + assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id()); + mv_dst.seam_facets.assign(mv_src.seam_facets); + assert(mv_dst.mmu_segmentation_facets.id() == mv_src.mmu_segmentation_facets.id()); + mv_dst.mmu_segmentation_facets.assign(mv_src.mmu_segmentation_facets); + //FIXME what to do with the materials? + // mv_dst.m_material_id = mv_src.m_material_id; + ++ i_src; + ++ i_dst; + } +} + +static inline void layer_height_ranges_copy_configs(t_layer_config_ranges &lr_dst, const t_layer_config_ranges &lr_src) +{ + assert(lr_dst.size() == lr_src.size()); + auto it_src = lr_src.cbegin(); + for (auto &kvp_dst : lr_dst) { + const auto &kvp_src = *it_src ++; + assert(std::abs(kvp_dst.first.first - kvp_src.first.first ) <= EPSILON); + assert(std::abs(kvp_dst.first.second - kvp_src.first.second) <= EPSILON); + // Layer heights are allowed do differ in case the layer height table is being overriden by the smooth profile. + // assert(std::abs(kvp_dst.second.option("layer_height")->getFloat() - kvp_src.second.option("layer_height")->getFloat()) <= EPSILON); + kvp_dst.second = kvp_src.second; + } +} + +static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs) +{ + typedef Transform3d::Scalar T; + const T *lv = lhs.data(); + const T *rv = rhs.data(); + for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) { + if (*lv < *rv) + return true; + else if (*lv > *rv) + return false; + } + return false; +} + +static inline bool transform3d_equal(const Transform3d &lhs, const Transform3d &rhs) +{ + typedef Transform3d::Scalar T; + const T *lv = lhs.data(); + const T *rv = rhs.data(); + for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) + if (*lv != *rv) + return false; + return true; +} + +struct PrintObjectTrafoAndInstances +{ + Transform3d trafo; + PrintInstances instances; + bool operator<(const PrintObjectTrafoAndInstances &rhs) const { return transform3d_lower(this->trafo, rhs.trafo); } +}; + +// Generate a list of trafos and XY offsets for instances of a ModelObject +static std::vector print_objects_from_model_object(const ModelObject &model_object) +{ + std::set trafos; + PrintObjectTrafoAndInstances trafo; + for (ModelInstance *model_instance : model_object.instances) + if (model_instance->is_printable()) { + trafo.trafo = model_instance->get_matrix(); + auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]); + // Reset the XY axes of the transformation. + trafo.trafo.data()[12] = 0; + trafo.trafo.data()[13] = 0; + // Search or insert a trafo. + auto it = trafos.emplace(trafo).first; + const_cast(*it).instances.emplace_back(PrintInstance{ nullptr, model_instance, shift }); + } + return std::vector(trafos.begin(), trafos.end()); +} + +// Compare just the layer ranges and their layer heights, not the associated configs. +// Ignore the layer heights if check_layer_heights is false. +static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height) +{ + if (lr1.size() != lr2.size()) + return false; + auto it2 = lr2.begin(); + for (const auto &kvp1 : lr1) { + const auto &kvp2 = *it2 ++; + if (std::abs(kvp1.first.first - kvp2.first.first ) > EPSILON || + std::abs(kvp1.first.second - kvp2.first.second) > EPSILON || + (check_layer_height && std::abs(kvp1.second.option("layer_height")->getFloat() - kvp2.second.option("layer_height")->getFloat()) > EPSILON)) + return false; + } + return true; +} + +// Returns true if va == vb when all CustomGCode items that are not ToolChangeCode are ignored. +static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector &va, const std::vector &vb) +{ + auto it_a = va.begin(); + auto it_b = vb.begin(); + while (it_a != va.end() || it_b != vb.end()) { + if (it_a != va.end() && it_a->type != CustomGCode::ToolChange) { + // Skip any CustomGCode items, which are not tool changes. + ++ it_a; + continue; + } + if (it_b != vb.end() && it_b->type != CustomGCode::ToolChange) { + // Skip any CustomGCode items, which are not tool changes. + ++ it_b; + continue; + } + if (it_a == va.end() || it_b == vb.end()) + // va or vb contains more Tool Changes than the other. + return true; + assert(it_a->type == CustomGCode::ToolChange); + assert(it_b->type == CustomGCode::ToolChange); + if (*it_a != *it_b) + // The two Tool Changes differ. + return true; + ++ it_a; + ++ it_b; + } + // There is no change in custom Tool Changes. + return false; +} + +// Collect changes to print config, account for overrides of extruder retract values by filament presets. +static t_config_option_keys print_config_diffs( + const PrintConfig ¤t_config, + const DynamicPrintConfig &new_full_config, + DynamicPrintConfig &filament_overrides) +{ + const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); + const std::string filament_prefix = "filament_"; + t_config_option_keys print_diff; + for (const t_config_option_key &opt_key : current_config.keys()) { + const ConfigOption *opt_old = current_config.option(opt_key); + assert(opt_old != nullptr); + const ConfigOption *opt_new = new_full_config.option(opt_key); + // assert(opt_new != nullptr); + if (opt_new == nullptr) + //FIXME This may happen when executing some test cases. + continue; + const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; + if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { + // An extruder retract override is available at some of the filament presets. + bool overriden = opt_new->overriden_by(opt_new_filament); + if (overriden || *opt_old != *opt_new) { + auto opt_copy = opt_new->clone(); + opt_copy->apply_override(opt_new_filament); + bool changed = *opt_old != *opt_copy; + if (changed) + print_diff.emplace_back(opt_key); + if (changed || overriden) { + // filament_overrides will be applied to the placeholder parser, which layers these parameters over full_print_config. + filament_overrides.set_key_value(opt_key, opt_copy); + } else + delete opt_copy; + } + } else if (*opt_new != *opt_old) + print_diff.emplace_back(opt_key); + } + + return print_diff; +} + +// Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser. +static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig ¤t_full_config, const DynamicPrintConfig &new_full_config) +{ + t_config_option_keys full_config_diff; + for (const t_config_option_key &opt_key : new_full_config.keys()) { + const ConfigOption *opt_old = current_full_config.option(opt_key); + const ConfigOption *opt_new = new_full_config.option(opt_key); + if (opt_old == nullptr || *opt_new != *opt_old) + full_config_diff.emplace_back(opt_key); + } + return full_config_diff; +} + +// Repository for solving partial overlaps of ModelObject::layer_config_ranges. +// Here the const DynamicPrintConfig* point to the config in ModelObject::layer_config_ranges. +class LayerRanges +{ +public: + struct LayerRange { + t_layer_height_range layer_height_range; + // Config is owned by the associated ModelObject. + const DynamicPrintConfig* config { nullptr }; + + bool operator<(const LayerRange &rhs) const throw() { return this->layer_height_range < rhs.layer_height_range; } + }; + + LayerRanges() = default; + LayerRanges(const t_layer_config_ranges &in) { this->assign(in); } + + // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs. + void assign(const t_layer_config_ranges &in) { + m_ranges.clear(); + m_ranges.reserve(in.size()); + // Input ranges are sorted lexicographically. First range trims the other ranges. + coordf_t last_z = 0; + for (const std::pair &range : in) + if (range.first.second > last_z) { + coordf_t min_z = std::max(range.first.first, 0.); + if (min_z > last_z + EPSILON) { + m_ranges.push_back({ t_layer_height_range(last_z, min_z) }); + last_z = min_z; + } + if (range.first.second > last_z + EPSILON) { + const DynamicPrintConfig *cfg = &range.second.get(); + m_ranges.push_back({ t_layer_height_range(last_z, range.first.second), cfg }); + last_z = range.first.second; + } + } + if (m_ranges.empty()) + m_ranges.push_back({ t_layer_height_range(0, DBL_MAX) }); + else if (m_ranges.back().config == nullptr) + m_ranges.back().layer_height_range.second = DBL_MAX; + else + m_ranges.push_back({ t_layer_height_range(m_ranges.back().layer_height_range.second, DBL_MAX) }); + } + + const DynamicPrintConfig* config(const t_layer_height_range &range) const { + auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), LayerRange{ { range.first - EPSILON, range.second - EPSILON } }); + // #ys_FIXME_COLOR + // assert(it != m_ranges.end()); + // assert(it == m_ranges.end() || std::abs(it->first.first - range.first ) < EPSILON); + // assert(it == m_ranges.end() || std::abs(it->first.second - range.second) < EPSILON); + if (it == m_ranges.end() || + std::abs(it->layer_height_range.first - range.first) > EPSILON || + std::abs(it->layer_height_range.second - range.second) > EPSILON ) + return nullptr; // desired range doesn't found + return it == m_ranges.end() ? nullptr : it->config; + } + + std::vector::const_iterator begin() const { return m_ranges.cbegin(); } + std::vector::const_iterator end () const { return m_ranges.cend(); } + size_t size () const { return m_ranges.size(); } + +private: + // Layer ranges with their config overrides and list of volumes with their snug bounding boxes in a given layer range. + std::vector m_ranges; +}; + +// To track Model / ModelObject updates between the front end and back end, including layer height ranges, their configs, +// and snug bounding boxes of ModelVolumes. +struct ModelObjectStatus { + enum Status { + Unknown, + Old, + New, + Moved, + Deleted, + }; + + enum class PrintObjectRegionsStatus { + Invalid, + Valid, + PartiallyValid, + }; + + ModelObjectStatus(ObjectID id, Status status = Unknown) : id(id), status(status) {} + ~ModelObjectStatus() { if (print_object_regions) print_object_regions->ref_cnt_dec(); } + + // Key of the set. + ObjectID id; + // Status of this ModelObject with id on apply(). + Status status; + // PrintObjects to be generated for this ModelObject including their base transformation. + std::vector print_instances; + // Regions shared by the associated PrintObjects. + PrintObjectRegions *print_object_regions { nullptr }; + // Status of the above. + PrintObjectRegionsStatus print_object_regions_status { PrintObjectRegionsStatus::Invalid }; + + // Search by id. + bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } +}; + +struct ModelObjectStatusDB +{ + void add(const ModelObject &model_object, const ModelObjectStatus::Status status) { + assert(db.find(ModelObjectStatus(model_object.id())) == db.end()); + db.emplace(model_object.id(), status); + } + + bool add_if_new(const ModelObject &model_object, const ModelObjectStatus::Status status) { + auto it = db.find(ModelObjectStatus(model_object.id())); + if (it == db.end()) { + db.emplace_hint(it, model_object.id(), status); + return true; + } + return false; + } + + const ModelObjectStatus& get(const ModelObject &model_object) { + auto it = db.find(ModelObjectStatus(model_object.id())); + assert(it != db.end()); + return *it; + } + + const ModelObjectStatus& reuse(const ModelObject &model_object) { + const ModelObjectStatus &result = this->get(model_object); + assert(result.status != ModelObjectStatus::Deleted); + return result; + } + + std::set db; +}; + +struct PrintObjectStatus { + enum Status { + Unknown, + Deleted, + Reused, + New + }; + + PrintObjectStatus(PrintObject *print_object, Status status = Unknown) : + id(print_object->model_object()->id()), + print_object(print_object), + trafo(print_object->trafo()), + status(status) {} + PrintObjectStatus(ObjectID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + + // ID of the ModelObject & PrintObject + ObjectID id; + // Pointer to the old PrintObject + PrintObject *print_object; + // Trafo generated with model_object->world_matrix(true) + Transform3d trafo; + Status status; + + // Search by id. + bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; } +}; + +class PrintObjectStatusDB { +public: + using iterator = std::multiset::iterator; + using const_iterator = std::multiset::const_iterator; + + PrintObjectStatusDB(const PrintObjectPtrs &print_objects) { + for (PrintObject *print_object : print_objects) + m_db.emplace(PrintObjectStatus(print_object)); + } + + struct iterator_range : std::pair + { + using std::pair::pair; + iterator_range(const std::pair in) : std::pair(in) {} + + const_iterator begin() throw() { return this->first; } + const_iterator end() throw() { return this->second; } + }; + + iterator_range get_range(const ModelObject &model_object) const { + return m_db.equal_range(PrintObjectStatus(model_object.id())); + } + + iterator_range get_range(const ModelObjectStatus &model_object_status) const { + return m_db.equal_range(PrintObjectStatus(model_object_status.id)); + } + + size_t count(const ModelObject &model_object) { + return m_db.count(PrintObjectStatus(model_object.id())); + } + + std::multiset::iterator begin() { return m_db.begin(); } + std::multiset::iterator end() { return m_db.end(); } + + void clear() { + m_db.clear(); + } + +private: + std::multiset m_db; +}; + +static inline bool model_volume_solid_or_modifier(const ModelVolume &mv) +{ + ModelVolumeType type = mv.type(); + return type == ModelVolumeType::MODEL_PART || type == ModelVolumeType::NEGATIVE_VOLUME || type == ModelVolumeType::PARAMETER_MODIFIER; +} + +static inline Transform3f trafo_for_bbox(const Transform3d &object_trafo, const Transform3d &volume_trafo) +{ + Transform3d m = object_trafo * volume_trafo; + m.translation().x() = 0.; + m.translation().y() = 0.; + return m.cast(); +} + +static inline bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transform3d &t1, const Transform3d &t2) +{ + if (std::abs(t1.translation().z() - t2.translation().z()) > EPSILON) + // One of the object is higher than the other above the build plate (or below the build plate). + return false; + Matrix3d m1 = t1.matrix().block<3, 3>(0, 0); + Matrix3d m2 = t2.matrix().block<3, 3>(0, 0); + Matrix3d m = m2.inverse() * m1; + Vec3d z = m.block<3, 1>(0, 2); + if (std::abs(z.x()) > EPSILON || std::abs(z.y()) > EPSILON || std::abs(z.z() - 1.) > EPSILON) + // Z direction or length changed. + return false; + // Z still points in the same direction and it has the same length. + Vec3d x = m.block<3, 1>(0, 0); + Vec3d y = m.block<3, 1>(0, 1); + if (std::abs(x.z()) > EPSILON || std::abs(y.z()) > EPSILON) + return false; + double lx2 = x.squaredNorm(); + double ly2 = y.squaredNorm(); + if (lx2 - 1. > EPSILON * EPSILON || ly2 - 1. > EPSILON * EPSILON) + return false; + // Verify whether the vectors x, y are still perpendicular. + double d = x.dot(y); + return std::abs(d * d) < EPSILON * lx2 * ly2; +} + +static PrintObjectRegions::BoundingBox transformed_its_bbox2d(const indexed_triangle_set &its, const Transform3f &m, float offset) +{ + assert(! its.indices.empty()); + + PrintObjectRegions::BoundingBox bbox(m * its.vertices[its.indices.front()(0)]); + for (const stl_triangle_vertex_indices &tri : its.indices) + for (int i = 0; i < 3; ++ i) + bbox.extend(m * its.vertices[tri(i)]); + bbox.min() -= Vec3f(offset, offset, float(EPSILON)); + bbox.max() += Vec3f(offset, offset, float(EPSILON)); + return bbox; +} + +static void transformed_its_bboxes_in_z_ranges( + const indexed_triangle_set &its, + const Transform3f &m, + const std::vector &z_ranges, + std::vector> &bboxes, + const float offset) +{ + bboxes.assign(z_ranges.size(), std::make_pair(PrintObjectRegions::BoundingBox(), false)); + for (const stl_triangle_vertex_indices &tri : its.indices) { + const Vec3f pts[3] = { m * its.vertices[tri(0)], m * its.vertices[tri(1)], m * its.vertices[tri(2)] }; + for (size_t irange = 0; irange < z_ranges.size(); ++ irange) { + const t_layer_height_range &z_range = z_ranges[irange]; + std::pair &bbox = bboxes[irange]; + auto bbox_extend = [&bbox](const Vec3f& p) { + if (bbox.second) { + bbox.first.extend(p); + } else { + bbox.first.min() = bbox.first.max() = p; + bbox.second = true; + } + }; + int iprev = 2; + for (int iedge = 0; iedge < 3; ++ iedge) { + const Vec3f *p1 = &pts[iprev]; + const Vec3f *p2 = &pts[iedge]; + // Sort the edge points by Z. + if (p1->z() > p2->z()) + std::swap(p1, p2); + if (p2->z() <= z_range.first || p1->z() >= z_range.second) { + // Out of this slab. + } else if (p1->z() < z_range.first) { + if (p1->z() > z_range.second) { + // Two intersections. + float zspan = p2->z() - p1->z(); + float t1 = (z_range.first - p1->z()) / zspan; + float t2 = (z_range.second - p1->z()) / zspan; + Vec2f p = to_2d(*p1); + Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); + bbox_extend(to_3d((p + v * t1).eval(), float(z_range.first))); + bbox_extend(to_3d((p + v * t2).eval(), float(z_range.second))); + } else { + // Single intersection with the lower limit. + float t = (z_range.first - p1->z()) / (p2->z() - p1->z()); + Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); + bbox_extend(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.first))); + bbox_extend(*p2); + } + } else if (p2->z() > z_range.second) { + // Single intersection with the upper limit. + float t = (z_range.second - p1->z()) / (p2->z() - p1->z()); + Vec2f v(p2->x() - p1->x(), p2->y() - p1->y()); + bbox_extend(to_3d((to_2d(*p1) + v * t).eval(), float(z_range.second))); + bbox_extend(*p1); + } else { + // Both points are inside. + bbox_extend(*p1); + bbox_extend(*p2); + } + iprev = iedge; + } + } + } + + for (std::pair &bbox : bboxes) { + bbox.first.min() -= Vec3f(offset, offset, float(EPSILON)); + bbox.first.max() += Vec3f(offset, offset, float(EPSILON)); + } +} + +// Last PrintObject for this print_object_regions has been fully invalidated (deleted). +// Keep print_object_regions, but delete those volumes, which were either removed from new_volumes, or which rotated or scaled, so they need +// their bounding boxes to be recalculated. +void print_objects_regions_invalidate_keep_some_volumes(PrintObjectRegions &print_object_regions, ModelVolumePtrs old_volumes, ModelVolumePtrs new_volumes) +{ + print_object_regions.all_regions.clear(); + + model_volumes_sort_by_id(old_volumes); + model_volumes_sort_by_id(new_volumes); + + size_t i_cached_volume = 0; + size_t last_cached_volume = 0; + size_t i_old = 0; + for (size_t i_new = 0; i_new < new_volumes.size(); ++ i_new) + if (model_volume_solid_or_modifier(*new_volumes[i_new])) { + for (; i_old < old_volumes.size(); ++ i_old) + if (old_volumes[i_old]->id() >= new_volumes[i_new]->id()) + break; + if (i_old != old_volumes.size() && old_volumes[i_old]->id() == new_volumes[i_new]->id()) { + if (old_volumes[i_old]->get_matrix().isApprox(new_volumes[i_new]->get_matrix())) { + // Reuse the volume. + for (; print_object_regions.cached_volume_ids[i_cached_volume] < old_volumes[i_old]->id(); ++ i_cached_volume) + assert(i_cached_volume < print_object_regions.cached_volume_ids.size()); + assert(i_cached_volume < print_object_regions.cached_volume_ids.size() && print_object_regions.cached_volume_ids[i_cached_volume] == old_volumes[i_old]->id()); + print_object_regions.cached_volume_ids[last_cached_volume ++] = print_object_regions.cached_volume_ids[i_cached_volume ++]; + } else { + // Don't reuse the volume. + } + } + } + print_object_regions.cached_volume_ids.erase(print_object_regions.cached_volume_ids.begin() + last_cached_volume, print_object_regions.cached_volume_ids.end()); +} + +// Find a bounding box of a volume's part intersecting layer_range. Such a bounding box will likely be smaller in XY than the full bounding box, +// thus it will intersect with lower number of other volumes. +const PrintObjectRegions::BoundingBox* find_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const ModelVolume &volume) +{ + auto it = lower_bound_by_predicate(layer_range.volumes.begin(), layer_range.volumes.end(), [&volume](const PrintObjectRegions::VolumeExtents &l){ return l.volume_id < volume.id(); }); + return it != layer_range.volumes.end() && it->volume_id == volume.id() ? &it->bbox : nullptr; +} + +// Find a bounding box of a topmost printable volume referenced by this modifier given this_region_id. +PrintObjectRegions::BoundingBox find_modifier_volume_extents(const PrintObjectRegions::LayerRangeRegions &layer_range, const int this_region_id) +{ + // Find the top-most printable volume of this modifier, or the printable volume itself. + const PrintObjectRegions::VolumeRegion &this_region = layer_range.volume_regions[this_region_id]; + const PrintObjectRegions::BoundingBox *this_extents = find_volume_extents(layer_range, *this_region.model_volume); + assert(this_extents); + PrintObjectRegions::BoundingBox out { *this_extents }; + if (! this_region.model_volume->is_model_part()) + for (int parent_region_id = this_region.parent;;) { + assert(parent_region_id >= 0); + const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; + const PrintObjectRegions::BoundingBox *parent_extents = find_volume_extents(layer_range, *parent_region.model_volume); + assert(parent_extents); + out.extend(*parent_extents); + if (parent_region.model_volume->is_model_part()) + break; + parent_region_id = parent_region.parent; + } + return out; +} + +PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders); + +void print_region_ref_inc(PrintRegion &r) { ++ r.m_ref_cnt; } +void print_region_ref_reset(PrintRegion &r) { r.m_ref_cnt = 0; } +int print_region_ref_cnt(const PrintRegion &r) { return r.m_ref_cnt; } + +// Verify whether the PrintRegions of a PrintObject are still valid, possibly after updating the region configs. +// Before region configs are updated, callback_invalidate() is called to possibly stop background processing. +// Returns false if this object needs to be resliced because regions were merged or split. +bool verify_update_print_object_regions( + ModelVolumePtrs model_volumes, + const PrintRegionConfig &default_region_config, + size_t num_extruders, + const std::vector &painting_extruders, + PrintObjectRegions &print_object_regions, + const std::function &callback_invalidate) +{ + // Sort by ModelVolume ID. + model_volumes_sort_by_id(model_volumes); + + for (std::unique_ptr ®ion : print_object_regions.all_regions) + print_region_ref_reset(*region); + + // Verify and / or update PrintRegions produced by ModelVolumes, layer range modifiers, modifier volumes. + for (PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) { + // Each modifier ModelVolume intersecting this layer_range shall be referenced here at least once if it intersects some + // printable ModelVolume at this layer_range even if it does not modify its overlapping printable ModelVolume configuration yet. + // VolumeRegions reference ModelVolumes in layer_range.volume_regions the order they are stored in ModelObject volumes. + // Remember whether a given modifier ModelVolume was visited already. + auto it_model_volume_modifier_last = model_volumes.end(); + for (PrintObjectRegions::VolumeRegion ®ion : layer_range.volume_regions) + if (region.model_volume->is_model_part() || region.model_volume->is_modifier()) { + auto it_model_volume = lower_bound_by_predicate(model_volumes.begin(), model_volumes.end(), [®ion](const ModelVolume *l){ return l->id() < region.model_volume->id(); }); + assert(it_model_volume != model_volumes.end() && (*it_model_volume)->id() == region.model_volume->id()); + if (region.model_volume->is_modifier() && it_model_volume != it_model_volume_modifier_last) { + // A modifier ModelVolume is visited for the first time. + // A visited modifier may not have had parent volume_regions created overlapping with some model parts or modifiers, + // if the visited modifier did not modify their properties. Now the visited modifier's configuration may have changed, + // which may require new regions to be created. + it_model_volume_modifier_last = it_model_volume; + int next_region_id = int(®ion - layer_range.volume_regions.data()); + const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, *region.model_volume); + assert(bbox); + for (int parent_region_id = next_region_id - 1; parent_region_id >= 0; -- parent_region_id) { + const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; + assert(parent_region.model_volume != region.model_volume); + if (parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) { + // volume_regions are produced in decreasing order of parent volume_regions ids. + // Some regions may not have been generated the last time by generate_print_object_regions(). + assert(next_region_id == int(layer_range.volume_regions.size()) || + layer_range.volume_regions[next_region_id].model_volume != region.model_volume || + layer_range.volume_regions[next_region_id].parent <= parent_region_id); + if (next_region_id < int(layer_range.volume_regions.size()) && + layer_range.volume_regions[next_region_id].model_volume == region.model_volume && + layer_range.volume_regions[next_region_id].parent == parent_region_id) { + // A parent region is already overridden. + ++ next_region_id; + } else if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) + // Such parent region does not exist. If it is needed, then we need to reslice. + // Only create new region for a modifier, which actually modifies config of it's parent. + if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, **it_model_volume, num_extruders); + config != parent_region.region->config()) + // This modifier newly overrides a region, which it did not before. We need to reslice. + return false; + } + } + } + PrintRegionConfig cfg = region.parent == -1 ? + region_config_from_model_volume(default_region_config, layer_range.config, **it_model_volume, num_extruders) : + region_config_from_model_volume(layer_range.volume_regions[region.parent].region->config(), nullptr, **it_model_volume, num_extruders); + if (cfg != region.region->config()) { + // Region configuration changed. + if (print_region_ref_cnt(*region.region) == 0) { + // Region is referenced for the first time. Just change its parameters. + // Stop the background process before assigning new configuration to the regions. + t_config_option_keys diff = region.region->config().diff(cfg); + callback_invalidate(region.region->config(), cfg, diff); + region.region->config_apply_only(cfg, diff, false); + } else { + // Region is referenced multiple times, thus the region is being split. We need to reslice. + return false; + } + } + print_region_ref_inc(*region.region); + } + } + + // Verify and / or update PrintRegions produced by color painting. + for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges) + for (const PrintObjectRegions::PaintedRegion ®ion : layer_range.painted_regions) { + const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[region.parent]; + PrintRegionConfig cfg = parent_region.region->config(); + cfg.perimeter_extruder.value = region.extruder_id; + cfg.solid_infill_extruder.value = region.extruder_id; + cfg.infill_extruder.value = region.extruder_id; + if (cfg != region.region->config()) { + // Region configuration changed. + if (print_region_ref_cnt(*region.region) == 0) { + // Region is referenced for the first time. Just change its parameters. + // Stop the background process before assigning new configuration to the regions. + t_config_option_keys diff = region.region->config().diff(cfg); + callback_invalidate(region.region->config(), cfg, diff); + region.region->config_apply_only(cfg, diff, false); + } else { + // Region is referenced multiple times, thus the region is being split. We need to reslice. + return false; + } + } + print_region_ref_inc(*region.region); + } + + // Lastly verify, whether some regions were not merged. + { + std::vector regions; + regions.reserve(print_object_regions.all_regions.size()); + for (std::unique_ptr ®ion : print_object_regions.all_regions) { + assert(print_region_ref_cnt(*region) > 0); + regions.emplace_back(&(*region.get())); + } + std::sort(regions.begin(), regions.end(), [](const PrintRegion *l, const PrintRegion *r){ return l->config_hash() < r->config_hash(); }); + for (size_t i = 0; i < regions.size(); ++ i) { + size_t hash = regions[i]->config_hash(); + size_t j = i; + for (++ j; j < regions.size() && regions[j]->config_hash() == hash; ++ j) + if (regions[i]->config() == regions[j]->config()) { + // Regions were merged. We need to reslice. + return false; + } + } + } + + return true; +} + +// Update caches of volume bounding boxes. +void update_volume_bboxes( + std::vector &layer_ranges, + std::vector &cached_volume_ids, + ModelVolumePtrs model_volumes, + const Transform3d &object_trafo, + const float offset) +{ + // output will be sorted by the order of model_volumes sorted by their ObjectIDs. + model_volumes_sort_by_id(model_volumes); + + if (layer_ranges.size() == 1) { + PrintObjectRegions::LayerRangeRegions &layer_range = layer_ranges.front(); + std::vector volumes_old(std::move(layer_range.volumes)); + layer_range.volumes.reserve(model_volumes.size()); + for (const ModelVolume *model_volume : model_volumes) + if (model_volume_solid_or_modifier(*model_volume)) { + if (std::binary_search(cached_volume_ids.begin(), cached_volume_ids.end(), model_volume->id())) { + auto it = lower_bound_by_predicate(volumes_old.begin(), volumes_old.end(), [model_volume](PrintObjectRegions::VolumeExtents &l) { return l.volume_id < model_volume->id(); }); + if (it != volumes_old.end() && it->volume_id == model_volume->id()) + layer_range.volumes.emplace_back(*it); + } else +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + layer_range.volumes.push_back({ model_volume->id(), + transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix()), offset) }); +#else + layer_range.volumes.push_back({ model_volume->id(), + transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), offset) }); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + } + } else { + std::vector> volumes_old; + if (cached_volume_ids.empty()) + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) + layer_range.volumes.clear(); + else { + volumes_old.reserve(layer_ranges.size()); + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) + volumes_old.emplace_back(std::move(layer_range.volumes)); + } + + std::vector> bboxes; + std::vector ranges; + ranges.reserve(layer_ranges.size()); + for (const PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) { + t_layer_height_range r = layer_range.layer_height_range; + r.first -= EPSILON; + r.second += EPSILON; + ranges.emplace_back(r); + } + for (const ModelVolume *model_volume : model_volumes) + if (model_volume_solid_or_modifier(*model_volume)) { + if (std::binary_search(cached_volume_ids.begin(), cached_volume_ids.end(), model_volume->id())) { + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) { + const auto &vold = volumes_old[&layer_range - layer_ranges.data()]; + auto it = lower_bound_by_predicate(vold.begin(), vold.end(), [model_volume](const PrintObjectRegions::VolumeExtents &l) { return l.volume_id < model_volume->id(); }); + if (it != vold.end() && it->volume_id == model_volume->id()) + layer_range.volumes.emplace_back(*it); + } + } else { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix()), ranges, bboxes, offset); +#else + transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), ranges, bboxes, offset); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) + if (auto &bbox = bboxes[&layer_range - layer_ranges.data()]; bbox.second) + layer_range.volumes.push_back({ model_volume->id(), bbox.first }); + } + } + } + + cached_volume_ids.clear(); + cached_volume_ids.reserve(model_volumes.size()); + for (const ModelVolume *v : model_volumes) + if (model_volume_solid_or_modifier(*v)) + cached_volume_ids.emplace_back(v->id()); +} + +// Either a fresh PrintObject, or PrintObject regions were invalidated (merged, split). +// Generate PrintRegions from scratch. +static PrintObjectRegions* generate_print_object_regions( + PrintObjectRegions *print_object_regions_old, + const ModelVolumePtrs &model_volumes, + const LayerRanges &model_layer_ranges, + const PrintRegionConfig &default_region_config, + const Transform3d &trafo, + size_t num_extruders, + const float xy_size_compensation, + const std::vector &painting_extruders) +{ + // Reuse the old object or generate a new one. + auto out = print_object_regions_old ? std::unique_ptr(print_object_regions_old) : std::make_unique(); + auto &all_regions = out->all_regions; + auto &layer_ranges_regions = out->layer_ranges; + + all_regions.clear(); + + bool reuse_old = print_object_regions_old && !print_object_regions_old->layer_ranges.empty(); + + if (reuse_old) { + // Reuse old bounding boxes of some ModelVolumes and their ranges. + // Verify that the old ranges match the new ranges. + assert(model_layer_ranges.size() == layer_ranges_regions.size()); + for (const auto &range : model_layer_ranges) { + PrintObjectRegions::LayerRangeRegions &r = layer_ranges_regions[&range - &*model_layer_ranges.begin()]; + assert(range.layer_height_range == r.layer_height_range); + // If model::assign_copy() is called, layer_ranges_regions is copied thus the pointers to configs are lost. + r.config = range.config; + r.volume_regions.clear(); + r.painted_regions.clear(); + } + } else { + out->trafo_bboxes = trafo; + layer_ranges_regions.reserve(model_layer_ranges.size()); + for (const auto &range : model_layer_ranges) + layer_ranges_regions.push_back({ range.layer_height_range, range.config }); + } + + const bool is_mm_painted = num_extruders > 1 && std::any_of(model_volumes.cbegin(), model_volumes.cend(), [](const ModelVolume *mv) { return mv->is_mm_painted(); }); + update_volume_bboxes(layer_ranges_regions, out->cached_volume_ids, model_volumes, out->trafo_bboxes, is_mm_painted ? 0.f : std::max(0.f, xy_size_compensation)); + + std::vector region_set; + auto get_create_region = [®ion_set, &all_regions](PrintRegionConfig &&config) -> PrintRegion* { + size_t hash = config.hash(); + auto it = Slic3r::lower_bound_by_predicate(region_set.begin(), region_set.end(), [&config, hash](const PrintRegion* l) { + return l->config_hash() < hash || (l->config_hash() == hash && l->config() < config); }); + if (it != region_set.end() && (*it)->config_hash() == hash && (*it)->config() == config) + return *it; + // Insert into a sorted array, it has O(n) complexity, but the calling algorithm has an O(n^2*log(n)) complexity anyways. + all_regions.emplace_back(std::make_unique(std::move(config), hash, int(all_regions.size()))); + PrintRegion *region = all_regions.back().get(); + region_set.emplace(it, region); + return region; + }; + + // Chain the regions in the order they are stored in the volumes list. + for (int volume_id = 0; volume_id < int(model_volumes.size()); ++ volume_id) { + const ModelVolume &volume = *model_volumes[volume_id]; + if (model_volume_solid_or_modifier(volume)) { + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) + if (const PrintObjectRegions::BoundingBox *bbox = find_volume_extents(layer_range, volume); bbox) { + if (volume.is_model_part()) { + // Add a model volume, assign an existing region or generate a new one. + layer_range.volume_regions.push_back({ + &volume, -1, + get_create_region(region_config_from_model_volume(default_region_config, layer_range.config, volume, num_extruders)), + bbox + }); + } else if (volume.is_negative_volume()) { + // Add a negative (subtractor) volume. Such volume has neither region nor parent volume assigned. + layer_range.volume_regions.push_back({ &volume, -1, nullptr, bbox }); + } else { + assert(volume.is_modifier()); + // Modifiers may be chained one over the other. Check for overlap, merge DynamicPrintConfigs. + bool added = false; + int parent_model_part_id = -1; + for (int parent_region_id = int(layer_range.volume_regions.size()) - 1; parent_region_id >= 0; -- parent_region_id) { + const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; + const ModelVolume &parent_volume = *parent_region.model_volume; + if (parent_volume.is_model_part() || parent_volume.is_modifier()) + if (PrintObjectRegions::BoundingBox parent_bbox = find_modifier_volume_extents(layer_range, parent_region_id); parent_bbox.intersects(*bbox)) { + // Only create new region for a modifier, which actually modifies config of it's parent. + if (PrintRegionConfig config = region_config_from_model_volume(parent_region.region->config(), nullptr, volume, num_extruders); + config != parent_region.region->config()) { + added = true; + layer_range.volume_regions.push_back({ &volume, parent_region_id, get_create_region(std::move(config)), bbox }); + } else if (parent_model_part_id == -1 && parent_volume.is_model_part()) + parent_model_part_id = parent_region_id; + } + } + if (! added && parent_model_part_id >= 0) + // This modifier does not override any printable volume's configuration, however it may in the future. + // Store it so that verify_update_print_object_regions() will handle this modifier correctly if its configuration changes. + layer_range.volume_regions.push_back({ &volume, parent_model_part_id, layer_range.volume_regions[parent_model_part_id].region, bbox }); + } + } + } + } + + // Finally add painting regions. + for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges_regions) { + for (unsigned int painted_extruder_id : painting_extruders) + for (int parent_region_id = 0; parent_region_id < int(layer_range.volume_regions.size()); ++ parent_region_id) + if (const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id]; + parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) { + PrintRegionConfig cfg = parent_region.region->config(); + cfg.perimeter_extruder.value = painted_extruder_id; + cfg.solid_infill_extruder.value = painted_extruder_id; + cfg.infill_extruder.value = painted_extruder_id; + layer_range.painted_regions.push_back({ painted_extruder_id, parent_region_id, get_create_region(std::move(cfg))}); + } + // Sort the regions by parent region::print_object_region_id() and extruder_id to help the slicing algorithm when applying MMU segmentation. + std::sort(layer_range.painted_regions.begin(), layer_range.painted_regions.end(), [&layer_range](auto &l, auto &r) { + int lid = layer_range.volume_regions[l.parent].region->print_object_region_id(); + int rid = layer_range.volume_regions[r.parent].region->print_object_region_id(); + return lid < rid || (lid == rid && l.extruder_id < r.extruder_id); }); + } + + return out.release(); +} + +Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config) +{ +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + + // Normalize the config. + new_full_config.option("print_settings_id", true); + new_full_config.option("filament_settings_id", true); + new_full_config.option("printer_settings_id", true); + new_full_config.option("physical_printer_settings_id", true); + new_full_config.normalize_fdm(); + + // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. + DynamicPrintConfig filament_overrides; + t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides); + t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config); + // Collect changes to object and region configs. + t_config_option_keys object_diff = m_default_object_config.diff(new_full_config); + t_config_option_keys region_diff = m_default_region_config.diff(new_full_config); + + // Do not use the ApplyStatus as we will use the max function when updating apply_status. + unsigned int apply_status = APPLY_STATUS_UNCHANGED; + auto update_apply_status = [&apply_status](bool invalidated) + { apply_status = std::max(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); }; + if (! (print_diff.empty() && object_diff.empty() && region_diff.empty())) + update_apply_status(false); + + // Grab the lock for the Print / PrintObject milestones. + std::scoped_lock lock(this->state_mutex()); + + // The following call may stop the background processing. + if (! print_diff.empty()) + update_apply_status(this->invalidate_state_by_config_options(new_full_config, print_diff)); + + // Apply variables to placeholder parser. The placeholder parser is used by G-code export, + // which should be stopped if print_diff is not empty. + size_t num_extruders = m_config.nozzle_diameter.size(); + bool num_extruders_changed = false; + if (! full_config_diff.empty()) { + update_apply_status(this->invalidate_step(psGCodeExport)); + m_placeholder_parser.clear_config(); + // Set the profile aliases for the PrintBase::output_filename() + m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone()); + m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); + m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("physical_printer_preset", new_full_config.option("physical_printer_settings_id")->clone()); + // We want the filament overrides to be applied over their respective extruder parameters by the PlaceholderParser. + // see "Placeholders do not respect filament overrides." GH issue #3649 + m_placeholder_parser.apply_config(filament_overrides); + // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. + m_config.apply_only(new_full_config, print_diff, true); + //FIXME use move semantics once ConfigBase supports it. + // Some filament_overrides may contain values different from new_full_config, but equal to m_config. + // As long as these config options don't reallocate memory when copying, we are safe overriding a value, which is in use by a worker thread. + m_config.apply(filament_overrides); + // Handle changes to object config defaults + m_default_object_config.apply_only(new_full_config, object_diff, true); + // Handle changes to regions config defaults + m_default_region_config.apply_only(new_full_config, region_diff, true); + m_full_print_config = std::move(new_full_config); + if (num_extruders != m_config.nozzle_diameter.size()) { + num_extruders = m_config.nozzle_diameter.size(); + num_extruders_changed = true; + } + } + + ModelObjectStatusDB model_object_status_db; + + // 1) Synchronize model objects. + bool print_regions_reshuffled = false; + if (model.id() != m_model.id()) { + // Kill everything, initialize from scratch. + // Stop background processing. + this->call_cancel_callback(); + update_apply_status(this->invalidate_all_steps()); + for (PrintObject *object : m_objects) { + model_object_status_db.add(*object->model_object(), ModelObjectStatus::Deleted); + update_apply_status(object->invalidate_all_steps()); + delete object; + } + m_objects.clear(); + print_regions_reshuffled = true; + m_model.assign_copy(model); + for (const ModelObject *model_object : m_model.objects) + model_object_status_db.add(*model_object, ModelObjectStatus::New); + } else { + if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { + update_apply_status(num_extruders_changed || + // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering. + //FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable + // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same. + (num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes)) ? + // The Tool Ordering and the Wipe Tower are no more valid. + this->invalidate_steps({ psWipeTower, psGCodeExport }) : + // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering. + this->invalidate_step(psGCodeExport)); + m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; + } + if (model_object_list_equal(m_model, model)) { + // The object list did not change. + for (const ModelObject *model_object : m_model.objects) + model_object_status_db.add(*model_object, ModelObjectStatus::Old); + } else if (model_object_list_extended(m_model, model)) { + // Add new objects. Their volumes and configs will be synchronized later. + update_apply_status(this->invalidate_step(psGCodeExport)); + for (const ModelObject *model_object : m_model.objects) + model_object_status_db.add(*model_object, ModelObjectStatus::Old); + for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) { + model_object_status_db.add(*model.objects[i], ModelObjectStatus::New); + m_model.objects.emplace_back(ModelObject::new_copy(*model.objects[i])); + m_model.objects.back()->set_model(&m_model); + } + } else { + // Reorder the objects, add new objects. + // First stop background processing before shuffling or deleting the PrintObjects in the object list. + this->call_cancel_callback(); + update_apply_status(this->invalidate_step(psGCodeExport)); + // Second create a new list of objects. + std::vector model_objects_old(std::move(m_model.objects)); + m_model.objects.clear(); + m_model.objects.reserve(model.objects.size()); + auto by_id_lower = [](const ModelObject *lhs, const ModelObject *rhs){ return lhs->id() < rhs->id(); }; + std::sort(model_objects_old.begin(), model_objects_old.end(), by_id_lower); + for (const ModelObject *mobj : model.objects) { + auto it = std::lower_bound(model_objects_old.begin(), model_objects_old.end(), mobj, by_id_lower); + if (it == model_objects_old.end() || (*it)->id() != mobj->id()) { + // New ModelObject added. + m_model.objects.emplace_back(ModelObject::new_copy(*mobj)); + m_model.objects.back()->set_model(&m_model); + model_object_status_db.add(*mobj, ModelObjectStatus::New); + } else { + // Existing ModelObject re-added (possibly moved in the list). + m_model.objects.emplace_back(*it); + model_object_status_db.add(*mobj, ModelObjectStatus::Moved); + } + } + bool deleted_any = false; + for (ModelObject *&model_object : model_objects_old) + if (model_object_status_db.add_if_new(*model_object, ModelObjectStatus::Deleted)) + deleted_any = true; + else + // Do not delete this ModelObject instance. + model_object = nullptr; + if (deleted_any) { + // Delete PrintObjects of the deleted ModelObjects. + PrintObjectPtrs print_objects_old = std::move(m_objects); + m_objects.clear(); + m_objects.reserve(print_objects_old.size()); + for (PrintObject *print_object : print_objects_old) { + const ModelObjectStatus &status = model_object_status_db.get(*print_object->model_object()); + if (status.status == ModelObjectStatus::Deleted) { + update_apply_status(print_object->invalidate_all_steps()); + delete print_object; + } else + m_objects.emplace_back(print_object); + } + for (ModelObject *model_object : model_objects_old) + delete model_object; + print_regions_reshuffled = true; + } + } + } + + // 2) Map print objects including their transformation matrices. + PrintObjectStatusDB print_object_status_db(m_objects); + + // 3) Synchronize ModelObjects & PrintObjects. + const std::initializer_list solid_or_modifier_types { ModelVolumeType::MODEL_PART, ModelVolumeType::NEGATIVE_VOLUME, ModelVolumeType::PARAMETER_MODIFIER }; + for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { + ModelObject &model_object = *m_model.objects[idx_model_object]; + ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(model_object)); + const ModelObject &model_object_new = *model.objects[idx_model_object]; + if (model_object_status.status == ModelObjectStatus::New) + // PrintObject instances will be added in the next loop. + continue; + // Update the ModelObject instance, possibly invalidate the linked PrintObjects. + assert(model_object_status.status == ModelObjectStatus::Old || model_object_status.status == ModelObjectStatus::Moved); + // Check whether a model part volume was added or removed, their transformations or order changed. + // Only volume IDs, volume types, transformation matrices and their order are checked, configuration and other parameters are NOT checked. + bool solid_or_modifier_differ = model_volume_list_changed(model_object, model_object_new, solid_or_modifier_types) || + model_mmu_segmentation_data_changed(model_object, model_object_new) || + (model_object_new.is_mm_painted() && num_extruders_changed); + bool supports_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER) || + model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER); + bool layer_height_ranges_differ = ! layer_height_ranges_equal(model_object.layer_config_ranges, model_object_new.layer_config_ranges, model_object_new.layer_height_profile.empty()); + bool model_origin_translation_differ = model_object.origin_translation != model_object_new.origin_translation; + auto print_objects_range = print_object_status_db.get_range(model_object); + // The list actually can be empty if all instances are out of the print bed. + //assert(print_objects_range.begin() != print_objects_range.end()); + // All PrintObjects in print_objects_range shall point to the same prints_objects_regions + if (print_objects_range.begin() != print_objects_range.end()) { + model_object_status.print_object_regions = print_objects_range.begin()->print_object->m_shared_regions; + model_object_status.print_object_regions->ref_cnt_inc(); + } + if (solid_or_modifier_differ || model_origin_translation_differ || layer_height_ranges_differ || + ! model_object.layer_height_profile.timestamp_matches(model_object_new.layer_height_profile)) { + // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. + model_object_status.print_object_regions_status = + model_object_status.print_object_regions == nullptr || model_origin_translation_differ || layer_height_ranges_differ ? + // Drop print_objects_regions. + ModelObjectStatus::PrintObjectRegionsStatus::Invalid : + // Reuse bounding boxes of print_objects_regions for ModelVolumes with unmodified transformation. + ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid; + for (const PrintObjectStatus &print_object_status : print_objects_range) { + update_apply_status(print_object_status.print_object->invalidate_all_steps()); + const_cast(print_object_status).status = PrintObjectStatus::Deleted; + } + if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid) + // Drop everything from PrintObjectRegions but those VolumeExtents (of their particular ModelVolumes) that are still valid. + print_objects_regions_invalidate_keep_some_volumes(*model_object_status.print_object_regions, model_object.volumes, model_object_new.volumes); + else if (model_object_status.print_object_regions != nullptr) + model_object_status.print_object_regions->clear(); + // Copy content of the ModelObject including its ID, do not change the parent. + model_object.assign_copy(model_object_new); + } else { + model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::Valid; + if (supports_differ || model_custom_supports_data_changed(model_object, model_object_new)) { + // First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list. + if (supports_differ) { + this->call_cancel_callback(); + update_apply_status(false); + } + // Invalidate just the supports step. + for (const PrintObjectStatus &print_object_status : print_objects_range) + update_apply_status(print_object_status.print_object->invalidate_step(posSupportMaterial)); + if (supports_differ) { + // Copy just the support volumes. + model_volume_list_update_supports(model_object, model_object_new); + } + } else if (model_custom_seam_data_changed(model_object, model_object_new)) { + update_apply_status(this->invalidate_step(psGCodeExport)); + } + } + if (! solid_or_modifier_differ) { + // Synchronize Object's config. + bool object_config_changed = ! model_object.config.timestamp_matches(model_object_new.config); + if (object_config_changed) + model_object.config.assign_config(model_object_new.config); + if (! object_diff.empty() || object_config_changed || num_extruders_changed) { + PrintObjectConfig new_config = PrintObject::object_config_from_model_object(m_default_object_config, model_object, num_extruders); + for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(model_object)) { + t_config_option_keys diff = print_object_status.print_object->config().diff(new_config); + if (! diff.empty()) { + update_apply_status(print_object_status.print_object->invalidate_state_by_config_options(print_object_status.print_object->config(), new_config, diff)); + print_object_status.print_object->config_apply_only(new_config, diff, true); + } + } + } + // Synchronize (just copy) the remaining data of ModelVolumes (name, config, custom supports data). + //FIXME What to do with m_material_id? + model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART); + model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER); + layer_height_ranges_copy_configs(model_object.layer_config_ranges /* dst */, model_object_new.layer_config_ranges /* src */); + // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step. + model_object.name = model_object_new.name; + model_object.input_file = model_object_new.input_file; + // Only refresh ModelInstances if there is any change. + if (model_object.instances.size() != model_object_new.instances.size() || + ! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), [](auto l, auto r){ return l->id() == r->id(); })) { + // G-code generator accesses model_object.instances to generate sequential print ordering matching the Plater object list. + update_apply_status(this->invalidate_step(psGCodeExport)); + model_object.clear_instances(); + model_object.instances.reserve(model_object_new.instances.size()); + for (const ModelInstance *model_instance : model_object_new.instances) { + model_object.instances.emplace_back(new ModelInstance(*model_instance)); + model_object.instances.back()->set_model_object(&model_object); + } + } else if (! std::equal(model_object.instances.begin(), model_object.instances.end(), model_object_new.instances.begin(), + [](auto l, auto r){ return l->print_volume_state == r->print_volume_state && l->printable == r->printable && + l->get_transformation().get_matrix().isApprox(r->get_transformation().get_matrix()); })) { + // If some of the instances changed, the bounding box of the updated ModelObject is likely no more valid. + // This is safe as the ModelObject's bounding box is only accessed from this function, which is called from the main thread only. + model_object.invalidate_bounding_box(); + // Synchronize the content of instances. + auto new_instance = model_object_new.instances.begin(); + for (auto old_instance = model_object.instances.begin(); old_instance != model_object.instances.end(); ++ old_instance, ++ new_instance) { + (*old_instance)->set_transformation((*new_instance)->get_transformation()); + (*old_instance)->print_volume_state = (*new_instance)->print_volume_state; + (*old_instance)->printable = (*new_instance)->printable; + } + } + } + } + + // 4) Generate PrintObjects from ModelObjects and their instances. + { + PrintObjectPtrs print_objects_new; + print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size())); + bool new_objects = false; + // Walk over all new model objects and check, whether there are matching PrintObjects. + for (ModelObject *model_object : m_model.objects) { + ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(*model_object)); + model_object_status.print_instances = print_objects_from_model_object(*model_object); + std::vector old; + old.reserve(print_object_status_db.count(*model_object)); + for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(*model_object)) + if (print_object_status.status != PrintObjectStatus::Deleted) + old.emplace_back(&print_object_status); + // Generate a list of trafos and XY offsets for instances of a ModelObject + // Producing the config for PrintObject on demand, caching it at print_object_last. + const PrintObject *print_object_last = nullptr; + auto print_object_apply_config = [this, &print_object_last, model_object, num_extruders](PrintObject *print_object) { + print_object->config_apply(print_object_last ? + print_object_last->config() : + PrintObject::object_config_from_model_object(m_default_object_config, *model_object, num_extruders)); + print_object_last = print_object; + }; + if (old.empty()) { + // Simple case, just generate new instances. + for (PrintObjectTrafoAndInstances &print_instances : model_object_status.print_instances) { + PrintObject *print_object = new PrintObject(this, model_object, print_instances.trafo, std::move(print_instances.instances)); + print_object_apply_config(print_object); + print_objects_new.emplace_back(print_object); + // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); + new_objects = true; + } + continue; + } + // Complex case, try to merge the two lists. + // Sort the old lexicographically by their trafos. + std::sort(old.begin(), old.end(), [](const PrintObjectStatus *lhs, const PrintObjectStatus *rhs){ return transform3d_lower(lhs->trafo, rhs->trafo); }); + // Merge the old / new lists. + auto it_old = old.begin(); + for (PrintObjectTrafoAndInstances &new_instances : model_object_status.print_instances) { + for (; it_old != old.end() && transform3d_lower((*it_old)->trafo, new_instances.trafo); ++ it_old); + if (it_old == old.end() || ! transform3d_equal((*it_old)->trafo, new_instances.trafo)) { + // This is a new instance (or a set of instances with the same trafo). Just add it. + PrintObject *print_object = new PrintObject(this, model_object, new_instances.trafo, std::move(new_instances.instances)); + print_object_apply_config(print_object); + print_objects_new.emplace_back(print_object); + // print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); + new_objects = true; + if (it_old != old.end()) + const_cast(*it_old)->status = PrintObjectStatus::Deleted; + } else { + // The PrintObject already exists and the copies differ. + PrintBase::ApplyStatus status = (*it_old)->print_object->set_instances(std::move(new_instances.instances)); + if (status != PrintBase::APPLY_STATUS_UNCHANGED) + update_apply_status(status == PrintBase::APPLY_STATUS_INVALIDATED); + print_objects_new.emplace_back((*it_old)->print_object); + const_cast(*it_old)->status = PrintObjectStatus::Reused; + } + } + } + if (m_objects != print_objects_new) { + this->call_cancel_callback(); + update_apply_status(this->invalidate_all_steps()); + m_objects = print_objects_new; + // Delete the PrintObjects marked as Unknown or Deleted. + bool deleted_objects = false; + for (const PrintObjectStatus &pos : print_object_status_db) + if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { + update_apply_status(pos.print_object->invalidate_all_steps()); + delete pos.print_object; + deleted_objects = true; + } + if (new_objects || deleted_objects) + update_apply_status(this->invalidate_steps({ psSkirtBrim, psWipeTower, psGCodeExport })); + if (new_objects) + update_apply_status(false); + print_regions_reshuffled = true; + } + print_object_status_db.clear(); + } + + // All regions now have distinct settings. + // Check whether applying the new region config defaults we would get different regions, + // update regions or create regions from scratch. + for (auto it_print_object = m_objects.begin(); it_print_object != m_objects.end();) { + // Find the range of PrintObjects sharing the same associated ModelObject. + auto it_print_object_end = it_print_object; + PrintObject &print_object = *(*it_print_object); + const ModelObject &model_object = *print_object.model_object(); + ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(model_object)); + PrintObjectRegions *print_object_regions = model_object_status.print_object_regions; + for (++ it_print_object_end; it_print_object_end != m_objects.end() && (*it_print_object)->model_object() == (*it_print_object_end)->model_object(); ++ it_print_object_end) + assert((*it_print_object_end)->m_shared_regions == nullptr || (*it_print_object_end)->m_shared_regions == print_object_regions); + if (print_object_regions == nullptr) { + print_object_regions = new PrintObjectRegions{}; + model_object_status.print_object_regions = print_object_regions; + print_object_regions->ref_cnt_inc(); + } + std::vector painting_extruders; + if (const auto &volumes = print_object.model_object()->volumes; + num_extruders > 1 && + std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_segmentation_facets.empty(); }) != volumes.end()) { + //FIXME be more specific! Don't enumerate extruders that are not used for painting! + painting_extruders.assign(num_extruders, 0); + std::iota(painting_extruders.begin(), painting_extruders.end(), 1); + } + if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) { + // Verify that the trafo for regions & volume bounding boxes thus for regions is still applicable. + auto invalidate = [it_print_object, it_print_object_end, update_apply_status]() { + for (auto it = it_print_object; it != it_print_object_end; ++ it) + if ((*it)->m_shared_regions != nullptr) + update_apply_status((*it)->invalidate_all_steps()); + }; + if (print_object_regions && ! trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(print_object_regions->trafo_bboxes, model_object_status.print_instances.front().trafo)) { + invalidate(); + print_object_regions->clear(); + model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::Invalid; + print_regions_reshuffled = true; + } else if (print_object_regions && + verify_update_print_object_regions( + print_object.model_object()->volumes, + m_default_region_config, + num_extruders, + painting_extruders, + *print_object_regions, + [it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) { + for (auto it = it_print_object; it != it_print_object_end; ++it) + if ((*it)->m_shared_regions != nullptr) + update_apply_status((*it)->invalidate_state_by_config_options(old_config, new_config, diff_keys)); + })) { + // Regions are valid, just keep them. + } else { + // Regions were reshuffled. + invalidate(); + // At least reuse layer ranges and bounding boxes of ModelVolumes. + model_object_status.print_object_regions_status = ModelObjectStatus::PrintObjectRegionsStatus::PartiallyValid; + print_regions_reshuffled = true; + } + } + if (print_object_regions == nullptr || model_object_status.print_object_regions_status != ModelObjectStatus::PrintObjectRegionsStatus::Valid) { + // Layer ranges with their associated configurations. Remove overlaps between the ranges + // and create the regions from scratch. + print_object_regions = generate_print_object_regions( + print_object_regions, + print_object.model_object()->volumes, + LayerRanges(print_object.model_object()->layer_config_ranges), + m_default_region_config, + model_object_status.print_instances.front().trafo, + num_extruders, + print_object.is_mm_painted() ? 0.f : float(print_object.config().xy_size_compensation.value), + painting_extruders); + } + for (auto it = it_print_object; it != it_print_object_end; ++it) + if ((*it)->m_shared_regions) { + assert((*it)->m_shared_regions == print_object_regions); + } else { + (*it)->m_shared_regions = print_object_regions; + print_object_regions->ref_cnt_inc(); + } + it_print_object = it_print_object_end; + } + + if (print_regions_reshuffled) { + // Update Print::m_print_regions from objects. + struct cmp { bool operator() (const PrintRegion *l, const PrintRegion *r) const { return l->config_hash() == r->config_hash() && l->config() == r->config(); } }; + std::set region_set; + m_print_regions.clear(); + PrintObjectRegions *print_object_regions = nullptr; + for (PrintObject *print_object : m_objects) + if (print_object_regions != print_object->m_shared_regions) { + print_object_regions = print_object->m_shared_regions; + for (std::unique_ptr &print_region : print_object_regions->all_regions) + if (auto it = region_set.find(print_region.get()); it == region_set.end()) { + int print_region_id = int(m_print_regions.size()); + m_print_regions.emplace_back(print_region.get()); + print_region->m_print_region_id = print_region_id; + } else { + print_region->m_print_region_id = (*it)->print_region_id(); + } + } + } + + // Update SlicingParameters for each object where the SlicingParameters is not valid. + // If it is not valid, then it is ensured that PrintObject.m_slicing_params is not in use + // (posSlicing and posSupportMaterial was invalidated). + for (PrintObject *object : m_objects) + object->update_slicing_parameters(); + +#ifdef _DEBUG + check_model_ids_equal(m_model, model); +#endif /* _DEBUG */ + + return static_cast(apply_status); +} + +} // namespace Slic3r diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 6a73aaf1e..c44b914b4 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -79,6 +79,8 @@ #define ENABLE_WORLD_COORDINATE_SHOW_AXES (1 && ENABLE_WORLD_COORDINATE) // Enable alternate implementation of manipulating scale for instances and volumes #define ENABLE_WORLD_COORDINATE_SCALE_REVISITED (1 && ENABLE_WORLD_COORDINATE) +// Enable implementation of Geometry::Transformation using matrices only +#define ENABLE_TRANSFORMATIONS_BY_MATRICES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index a85b8092a..263d8af08 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -1,783 +1,811 @@ -#ifndef slic3r_3DScene_hpp_ -#define slic3r_3DScene_hpp_ - -#include "libslic3r/libslic3r.h" -#include "libslic3r/Point.hpp" -#include "libslic3r/Line.hpp" -#include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/Utils.hpp" -#include "libslic3r/Geometry.hpp" -#include "libslic3r/Color.hpp" - -#include "GLModel.hpp" - -#include -#include - -#ifndef NDEBUG -#define HAS_GLSAFE -#endif // NDEBUG - -#ifdef HAS_GLSAFE - extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); - inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } - #define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) - #define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) -#else // HAS_GLSAFE - inline void glAssertRecentCall() { } - #define glsafe(cmd) cmd - #define glcheck() -#endif // HAS_GLSAFE - -namespace Slic3r { -class SLAPrintObject; -enum SLAPrintObjectStep : unsigned int; -class BuildVolume; -class DynamicPrintConfig; -class ExtrusionPath; -class ExtrusionMultiPath; -class ExtrusionLoop; -class ExtrusionEntity; -class ExtrusionEntityCollection; -class ModelObject; -class ModelVolume; -enum ModelInstanceEPrintVolumeState : unsigned char; - -// Return appropriate color based on the ModelVolume. -extern ColorRGBA color_from_model_volume(const ModelVolume& model_volume); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL -// A container for interleaved arrays of 3D vertices and normals, -// possibly indexed by triangles and / or quads. -class GLIndexedVertexArray { -public: - // Only Eigen types of Nx16 size are vectorized. This bounding box will not be vectorized. - static_assert(sizeof(Eigen::AlignedBox) == 24, "Eigen::AlignedBox is not being vectorized, thus it does not need to be aligned"); - using BoundingBox = Eigen::AlignedBox; - - GLIndexedVertexArray() { m_bounding_box.setEmpty(); } - GLIndexedVertexArray(const GLIndexedVertexArray &rhs) : - vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved), - triangle_indices(rhs.triangle_indices), - quad_indices(rhs.quad_indices), - m_bounding_box(rhs.m_bounding_box) - { assert(! rhs.has_VBOs()); m_bounding_box.setEmpty(); } - GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : - vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), - triangle_indices(std::move(rhs.triangle_indices)), - quad_indices(std::move(rhs.quad_indices)), - m_bounding_box(rhs.m_bounding_box) - { assert(! rhs.has_VBOs()); } - - ~GLIndexedVertexArray() { release_geometry(); } - - GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs) - { - assert(vertices_and_normals_interleaved_VBO_id == 0); - assert(triangle_indices_VBO_id == 0); - assert(quad_indices_VBO_id == 0); - assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); - assert(rhs.triangle_indices_VBO_id == 0); - assert(rhs.quad_indices_VBO_id == 0); - this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; - this->triangle_indices = rhs.triangle_indices; - this->quad_indices = rhs.quad_indices; - this->m_bounding_box = rhs.m_bounding_box; - this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; - this->triangle_indices_size = rhs.triangle_indices_size; - this->quad_indices_size = rhs.quad_indices_size; - return *this; - } - - GLIndexedVertexArray& operator=(GLIndexedVertexArray &&rhs) - { - assert(vertices_and_normals_interleaved_VBO_id == 0); - assert(triangle_indices_VBO_id == 0); - assert(quad_indices_VBO_id == 0); - assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); - assert(rhs.triangle_indices_VBO_id == 0); - assert(rhs.quad_indices_VBO_id == 0); - this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); - this->triangle_indices = std::move(rhs.triangle_indices); - this->quad_indices = std::move(rhs.quad_indices); - this->m_bounding_box = rhs.m_bounding_box; - this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; - this->triangle_indices_size = rhs.triangle_indices_size; - this->quad_indices_size = rhs.quad_indices_size; - return *this; - } - - // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x) - std::vector vertices_and_normals_interleaved; - std::vector triangle_indices; - std::vector quad_indices; - - // When the geometry data is loaded into the graphics card as Vertex Buffer Objects, - // the above mentioned std::vectors are cleared and the following variables keep their original length. - size_t vertices_and_normals_interleaved_size{ 0 }; - size_t triangle_indices_size{ 0 }; - size_t quad_indices_size{ 0 }; - - // IDs of the Vertex Array Objects, into which the geometry has been loaded. - // Zero if the VBOs are not sent to GPU yet. - unsigned int vertices_and_normals_interleaved_VBO_id{ 0 }; - unsigned int triangle_indices_VBO_id{ 0 }; - unsigned int quad_indices_VBO_id{ 0 }; - -#if ENABLE_SMOOTH_NORMALS - void load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals = false); - void load_mesh(const TriangleMesh& mesh, bool smooth_normals = false) { this->load_mesh_full_shading(mesh, smooth_normals); } -#else - void load_mesh_full_shading(const TriangleMesh& mesh); - void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } -#endif // ENABLE_SMOOTH_NORMALS - - void load_its_flat_shading(const indexed_triangle_set &its); - - inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; } - - inline void reserve(size_t sz) { - this->vertices_and_normals_interleaved.reserve(sz * 6); - this->triangle_indices.reserve(sz * 3); - this->quad_indices.reserve(sz * 4); - } - - inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) { - assert(this->vertices_and_normals_interleaved_VBO_id == 0); - if (this->vertices_and_normals_interleaved_VBO_id != 0) - return; - - if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity()) - this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6)); - this->vertices_and_normals_interleaved.emplace_back(nx); - this->vertices_and_normals_interleaved.emplace_back(ny); - this->vertices_and_normals_interleaved.emplace_back(nz); - this->vertices_and_normals_interleaved.emplace_back(x); - this->vertices_and_normals_interleaved.emplace_back(y); - this->vertices_and_normals_interleaved.emplace_back(z); - - this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); - m_bounding_box.extend(Vec3f(x, y, z)); - }; - - inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { - push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz)); - } - - template - inline void push_geometry(const Eigen::MatrixBase& p, const Eigen::MatrixBase& n) { - push_geometry(float(p(0)), float(p(1)), float(p(2)), float(n(0)), float(n(1)), float(n(2))); - } - - inline void push_triangle(int idx1, int idx2, int idx3) { - assert(this->vertices_and_normals_interleaved_VBO_id == 0); - if (this->vertices_and_normals_interleaved_VBO_id != 0) - return; - - if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity()) - this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3)); - this->triangle_indices.emplace_back(idx1); - this->triangle_indices.emplace_back(idx2); - this->triangle_indices.emplace_back(idx3); - this->triangle_indices_size = this->triangle_indices.size(); - }; - - inline void push_quad(int idx1, int idx2, int idx3, int idx4) { - assert(this->vertices_and_normals_interleaved_VBO_id == 0); - if (this->vertices_and_normals_interleaved_VBO_id != 0) - return; - - if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity()) - this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4)); - this->quad_indices.emplace_back(idx1); - this->quad_indices.emplace_back(idx2); - this->quad_indices.emplace_back(idx3); - this->quad_indices.emplace_back(idx4); - this->quad_indices_size = this->quad_indices.size(); - }; - - // Finalize the initialization of the geometry & indices, - // upload the geometry and indices to OpenGL VBO objects - // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(bool opengl_initialized); - // Release the geometry data, release OpenGL VBOs. - void release_geometry(); - - void render() const; - void render(const std::pair& tverts_range, const std::pair& qverts_range) const; - - // Is there any geometry data stored? - bool empty() const { return vertices_and_normals_interleaved_size == 0; } - - void clear() { - this->vertices_and_normals_interleaved.clear(); - this->triangle_indices.clear(); - this->quad_indices.clear(); - vertices_and_normals_interleaved_size = 0; - triangle_indices_size = 0; - quad_indices_size = 0; - m_bounding_box.setEmpty(); - } - - // Shrink the internal storage to tighly fit the data stored. - void shrink_to_fit() { - this->vertices_and_normals_interleaved.shrink_to_fit(); - this->triangle_indices.shrink_to_fit(); - this->quad_indices.shrink_to_fit(); - } - - const BoundingBox& bounding_box() const { return m_bounding_box; } - - // Return an estimate of the memory consumed by this class. - size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); } - // Return an estimate of the memory held by GPU vertex buffers. - size_t gpu_memory_used() const - { - size_t memsize = 0; - if (this->vertices_and_normals_interleaved_VBO_id != 0) - memsize += this->vertices_and_normals_interleaved_size * 4; - if (this->triangle_indices_VBO_id != 0) - memsize += this->triangle_indices_size * 4; - if (this->quad_indices_VBO_id != 0) - memsize += this->quad_indices_size * 4; - return memsize; - } - size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } - -private: - BoundingBox m_bounding_box; -}; -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -class GLVolume { -public: - static const ColorRGBA SELECTED_COLOR; - static const ColorRGBA HOVER_SELECT_COLOR; - static const ColorRGBA HOVER_DESELECT_COLOR; - static const ColorRGBA OUTSIDE_COLOR; - static const ColorRGBA SELECTED_OUTSIDE_COLOR; - static const ColorRGBA DISABLED_COLOR; - static const ColorRGBA SLA_SUPPORT_COLOR; - static const ColorRGBA SLA_PAD_COLOR; - static const ColorRGBA NEUTRAL_COLOR; - static const std::array MODEL_COLOR; - - enum EHoverState : unsigned char - { - HS_None, - HS_Hover, - HS_Select, - HS_Deselect - }; - - GLVolume(float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f); - GLVolume(const ColorRGBA& color) : GLVolume(color.r(), color.g(), color.b(), color.a()) {} - -private: - Geometry::Transformation m_instance_transformation; - Geometry::Transformation m_volume_transformation; - - // Shift in z required by sla supports+pad - double m_sla_shift_z; - // Bounding box of this volume, in unscaled coordinates. - std::optional m_transformed_bounding_box; - // Convex hull of the volume, if any. - std::shared_ptr m_convex_hull; - // Bounding box of this volume, in unscaled coordinates. - std::optional m_transformed_convex_hull_bounding_box; - // Bounding box of the non sinking part of this volume, in unscaled coordinates. - std::optional m_transformed_non_sinking_bounding_box; - - class SinkingContours - { - static const float HalfWidth; - GLVolume& m_parent; - GUI::GLModel m_model; - BoundingBoxf3 m_old_box; - Vec3d m_shift{ Vec3d::Zero() }; - - public: - SinkingContours(GLVolume& volume) : m_parent(volume) {} - void render(); - - private: - void update(); - }; - - SinkingContours m_sinking_contours; - -#if ENABLE_SHOW_NON_MANIFOLD_EDGES - class NonManifoldEdges - { - GLVolume& m_parent; - GUI::GLModel m_model; - bool m_update_needed{ true }; - - public: - NonManifoldEdges(GLVolume& volume) : m_parent(volume) {} - void render(); - void set_as_dirty() { m_update_needed = true; } - - private: - void update(); - }; - - NonManifoldEdges m_non_manifold_edges; -#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES - -public: - // Color of the triangles / quads held by this volume. - ColorRGBA color; - // Color used to render this volume. - ColorRGBA render_color; - - struct CompositeID { - CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {} - CompositeID() : object_id(-1), volume_id(-1), instance_id(-1) {} - // Object ID, which is equal to the index of the respective ModelObject in Model.objects array. - int object_id; - // Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array. - // If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject, - // and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports. - // Volume with a negative volume_id cannot be picked independently, it will pick the associated instance. - int volume_id; - // Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array. - int instance_id; - bool operator==(const CompositeID &rhs) const { return object_id == rhs.object_id && volume_id == rhs.volume_id && instance_id == rhs.instance_id; } - bool operator!=(const CompositeID &rhs) const { return ! (*this == rhs); } - bool operator< (const CompositeID &rhs) const - { return object_id < rhs.object_id || (object_id == rhs.object_id && (volume_id < rhs.volume_id || (volume_id == rhs.volume_id && instance_id < rhs.instance_id))); } - }; - CompositeID composite_id; - // Fingerprint of the source geometry. For ModelVolumes, it is the ModelVolume::ID and ModelInstanceID, - // for generated volumes it is the timestamp generated by PrintState::invalidate() or PrintState::set_done(), - // and the associated ModelInstanceID. - // Valid geometry_id should always be positive. - std::pair geometry_id; - // An ID containing the extruder ID (used to select color). - int extruder_id; - - // Various boolean flags. - struct { - // Is this object selected? - bool selected : 1; - // Is this object disabled from selection? - bool disabled : 1; - // Is this object printable? - bool printable : 1; - // Whether or not this volume is active for rendering - bool is_active : 1; - // Whether or not to use this volume when applying zoom_to_volumes() - bool zoom_to_volumes : 1; - // Wheter or not this volume is enabled for outside print volume detection in shader. - bool shader_outside_printer_detection_enabled : 1; - // Wheter or not this volume is outside print volume. - bool is_outside : 1; - // Wheter or not this volume has been generated from a modifier - bool is_modifier : 1; - // Wheter or not this volume has been generated from the wipe tower - bool is_wipe_tower : 1; - // Wheter or not this volume has been generated from an extrusion path - bool is_extrusion_path : 1; - // Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE) - bool force_native_color : 1; - // Whether or not render this volume in neutral - bool force_neutral_color : 1; - // Whether or not to force rendering of sinking contours - bool force_sinking_contours : 1; - }; - - // Is mouse or rectangle selection over this object to select/deselect it ? - EHoverState hover; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GUI::GLModel model; -#else - // Interleaved triangles & normals with indexed triangles & quads. - GLIndexedVertexArray indexed_vertex_array; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - // Ranges of triangle and quad indices to be rendered. - std::pair tverts_range; -#if !ENABLE_LEGACY_OPENGL_REMOVAL - std::pair qverts_range; -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - // If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts - // of the extrusions per layer. - std::vector print_zs; - // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer. - std::vector offsets; - - // Bounding box of this volume, in unscaled coordinates. - BoundingBoxf3 bounding_box() const { -#if ENABLE_LEGACY_OPENGL_REMOVAL - return this->model.get_bounding_box(); -#else - BoundingBoxf3 out; - if (!this->indexed_vertex_array.bounding_box().isEmpty()) { - out.min = this->indexed_vertex_array.bounding_box().min().cast(); - out.max = this->indexed_vertex_array.bounding_box().max().cast(); - out.defined = true; - } - return out; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - - void set_color(const ColorRGBA& rgba) { color = rgba; } - void set_render_color(const ColorRGBA& rgba) { render_color = rgba; } - // Sets render color in dependence of current state - void set_render_color(bool force_transparent); - // set color according to model volume - void set_color_from_model_volume(const ModelVolume& model_volume); - - const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; } - void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); } - - const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); } - double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); } - - void set_instance_offset(const Vec3d& offset) { m_instance_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } - void set_instance_offset(Axis axis, double offset) { m_instance_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } - - const Vec3d& get_instance_rotation() const { return m_instance_transformation.get_rotation(); } - double get_instance_rotation(Axis axis) const { return m_instance_transformation.get_rotation(axis); } - - void set_instance_rotation(const Vec3d& rotation) { m_instance_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } - void set_instance_rotation(Axis axis, double rotation) { m_instance_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } - - Vec3d get_instance_scaling_factor() const { return m_instance_transformation.get_scaling_factor(); } - double get_instance_scaling_factor(Axis axis) const { return m_instance_transformation.get_scaling_factor(axis); } - - void set_instance_scaling_factor(const Vec3d& scaling_factor) { m_instance_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } - void set_instance_scaling_factor(Axis axis, double scaling_factor) { m_instance_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } - - const Vec3d& get_instance_mirror() const { return m_instance_transformation.get_mirror(); } - double get_instance_mirror(Axis axis) const { return m_instance_transformation.get_mirror(axis); } - - void set_instance_mirror(const Vec3d& mirror) { m_instance_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } - void set_instance_mirror(Axis axis, double mirror) { m_instance_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); } - - const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; } - void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); } - - const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); } - double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); } - - void set_volume_offset(const Vec3d& offset) { m_volume_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } - void set_volume_offset(Axis axis, double offset) { m_volume_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } - - const Vec3d& get_volume_rotation() const { return m_volume_transformation.get_rotation(); } - double get_volume_rotation(Axis axis) const { return m_volume_transformation.get_rotation(axis); } - - void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } - void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } - - const Vec3d& get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } - double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); } - - void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } - void set_volume_scaling_factor(Axis axis, double scaling_factor) { m_volume_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } - - const Vec3d& get_volume_mirror() const { return m_volume_transformation.get_mirror(); } - double get_volume_mirror(Axis axis) const { return m_volume_transformation.get_mirror(axis); } - - void set_volume_mirror(const Vec3d& mirror) { m_volume_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } - void set_volume_mirror(Axis axis, double mirror) { m_volume_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); } - - double get_sla_shift_z() const { return m_sla_shift_z; } - void set_sla_shift_z(double z) { m_sla_shift_z = z; } - - void set_convex_hull(std::shared_ptr convex_hull) { m_convex_hull = std::move(convex_hull); } - void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared(convex_hull); } - void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared(std::move(convex_hull)); } - - int object_idx() const { return this->composite_id.object_id; } - int volume_idx() const { return this->composite_id.volume_id; } - int instance_idx() const { return this->composite_id.instance_id; } - - Transform3d world_matrix() const; - bool is_left_handed() const; - - const BoundingBoxf3& transformed_bounding_box() const; - // non-caching variant - BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const; - // caching variant - const BoundingBoxf3& transformed_convex_hull_bounding_box() const; - // non-caching variant - BoundingBoxf3 transformed_non_sinking_bounding_box(const Transform3d& trafo) const; - // caching variant - const BoundingBoxf3& transformed_non_sinking_bounding_box() const; - // convex hull - const TriangleMesh* convex_hull() const { return m_convex_hull.get(); } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - bool empty() const { return this->model.is_empty(); } -#else - bool empty() const { return this->indexed_vertex_array.empty(); } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - void set_range(double low, double high); - - void render(); - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); } - void release_geometry() { this->indexed_vertex_array.release_geometry(); } -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - - void set_bounding_boxes_as_dirty() { - m_transformed_bounding_box.reset(); - m_transformed_convex_hull_bounding_box.reset(); - m_transformed_non_sinking_bounding_box.reset(); - } - - bool is_sla_support() const; - bool is_sla_pad() const; - - bool is_sinking() const; - bool is_below_printbed() const; - void render_sinking_contours(); -#if ENABLE_SHOW_NON_MANIFOLD_EDGES - void render_non_manifold_edges(); -#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES - - // Return an estimate of the memory consumed by this class. - size_t cpu_memory_used() const { -#if ENABLE_LEGACY_OPENGL_REMOVAL - return sizeof(*this) + this->model.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + - this->offsets.capacity() * sizeof(size_t); - } - // Return an estimate of the memory held by GPU vertex buffers. - size_t gpu_memory_used() const { return this->model.gpu_memory_used(); } -#else - //FIXME what to do wih m_convex_hull? - return sizeof(*this) - sizeof(this->indexed_vertex_array) + this->indexed_vertex_array.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + this->offsets.capacity() * sizeof(size_t); - } - // Return an estimate of the memory held by GPU vertex buffers. - size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } -}; - -typedef std::vector GLVolumePtrs; -typedef std::pair> GLVolumeWithIdAndZ; -typedef std::vector GLVolumeWithIdAndZList; - -class GLVolumeCollection -{ -public: - enum class ERenderType : unsigned char - { - Opaque, - Transparent, - All - }; - - struct PrintVolume - { - // see: Bed3D::EShapeType - int type{ 0 }; - // data contains: - // Rectangle: - // [0] = min.x, [1] = min.y, [2] = max.x, [3] = max.y - // Circle: - // [0] = center.x, [1] = center.y, [3] = radius - std::array data; - // [0] = min z, [1] = max z - std::array zs; - }; - -private: - PrintVolume m_print_volume; - - // z range for clipping in shaders - std::array m_z_range; - - // plane coeffs for clipping in shaders - std::array m_clipping_plane; - - struct Slope - { - // toggle for slope rendering - bool active{ false }; - float normal_z; - }; - - Slope m_slope; - bool m_show_sinking_contours{ false }; -#if ENABLE_SHOW_NON_MANIFOLD_EDGES - bool m_show_non_manifold_edges{ true }; -#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES - -public: - GLVolumePtrs volumes; - - GLVolumeCollection() { set_default_slope_normal_z(); } - ~GLVolumeCollection() { clear(); } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - std::vector load_object( - const ModelObject* model_object, - int obj_idx, - const std::vector& instance_idxs); - - int load_object_volume( - const ModelObject* model_object, - int obj_idx, - int volume_idx, - int instance_idx); - - // Load SLA auxiliary GLVolumes (for support trees or pad). - void load_object_auxiliary( - const SLAPrintObject* print_object, - int obj_idx, - // pairs of - const std::vector>& instances, - SLAPrintObjectStep milestone, - // Timestamp of the last change of the milestone - size_t timestamp); - -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - int load_wipe_tower_preview( - float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); -#else - int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL -#else - std::vector load_object( - const ModelObject *model_object, - int obj_idx, - const std::vector &instance_idxs, - bool opengl_initialized); - - int load_object_volume( - const ModelObject *model_object, - int obj_idx, - int volume_idx, - int instance_idx, - bool opengl_initialized); - - // Load SLA auxiliary GLVolumes (for support trees or pad). - void load_object_auxiliary( - const SLAPrintObject *print_object, - int obj_idx, - // pairs of - const std::vector>& instances, - SLAPrintObjectStep milestone, - // Timestamp of the last change of the milestone - size_t timestamp, - bool opengl_initialized); - -#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - int load_wipe_tower_preview( - float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); -#else - int load_wipe_tower_preview( - int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); -#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLVolume* new_toolpath_volume(const ColorRGBA& rgba); - GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba); -#else - GLVolume* new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); - GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - // Render the volumes by OpenGL. -#if ENABLE_GL_SHADERS_ATTRIBUTES - void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, const Transform3d& projection_matrix, - std::function filter_func = std::function()) const; -#else - void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#if !ENABLE_LEGACY_OPENGL_REMOVAL - // Finalize the initialization of the geometry & indices, - // upload the geometry and indices to OpenGL VBO objects - // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. - void finalize_geometry(bool opengl_initialized) { for (auto* v : volumes) v->finalize_geometry(opengl_initialized); } - // Release the geometry data assigned to the volumes. - // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them. - void release_geometry() { for (auto *v : volumes) v->release_geometry(); } -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - // Clear the geometry - void clear() { for (auto *v : volumes) delete v; volumes.clear(); } - - 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_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; } - void set_clipping_plane(const std::array& coeffs) { m_clipping_plane = coeffs; } - - const std::array& get_z_range() const { return m_z_range; } - const std::array& get_clipping_plane() const { return m_clipping_plane; } - - bool is_slope_active() const { return m_slope.active; } - void set_slope_active(bool active) { m_slope.active = active; } - - float get_slope_normal_z() const { return m_slope.normal_z; } - void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; } - void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); } - void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; } -#if ENABLE_SHOW_NON_MANIFOLD_EDGES - void set_show_non_manifold_edges(bool show) { m_show_non_manifold_edges = show; } -#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES - - // returns true if all the volumes are completely contained in the print volume - // returns the containment state in the given out_state, if non-null - bool check_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state) const; - void reset_outside_state(); - - void update_colors_by_extruder(const DynamicPrintConfig* config); - - // Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection - std::vector get_current_print_zs(bool active_only) const; - - // Return an estimate of the memory consumed by this class. - size_t cpu_memory_used() const; - // Return an estimate of the memory held by GPU vertex buffers. - size_t gpu_memory_used() const; - size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } - // Return CPU, GPU and total memory log line. - std::string log_memory_info() const; - -private: - GLVolumeCollection(const GLVolumeCollection &other); - GLVolumeCollection& operator=(const GLVolumeCollection &); -}; - -GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func = nullptr); - -struct _3DScene -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL - static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GUI::GLModel::Geometry& geometry); - static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GUI::GLModel::Geometry& geometry); - static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); - static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); - static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); - static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); - static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); -#else - static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GLVolume& volume); - static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GLVolume& volume); - static void extrusionentity_to_verts(const Polyline& polyline, float width, float height, float print_z, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume); - static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume); - static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume); - static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -}; - -} - -#endif +#ifndef slic3r_3DScene_hpp_ +#define slic3r_3DScene_hpp_ + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Point.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Color.hpp" + +#include "GLModel.hpp" + +#include +#include + +#ifndef NDEBUG +#define HAS_GLSAFE +#endif // NDEBUG + +#ifdef HAS_GLSAFE + extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); + inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } + #define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) + #define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) +#else // HAS_GLSAFE + inline void glAssertRecentCall() { } + #define glsafe(cmd) cmd + #define glcheck() +#endif // HAS_GLSAFE + +namespace Slic3r { +class SLAPrintObject; +enum SLAPrintObjectStep : unsigned int; +class BuildVolume; +class DynamicPrintConfig; +class ExtrusionPath; +class ExtrusionMultiPath; +class ExtrusionLoop; +class ExtrusionEntity; +class ExtrusionEntityCollection; +class ModelObject; +class ModelVolume; +enum ModelInstanceEPrintVolumeState : unsigned char; + +// Return appropriate color based on the ModelVolume. +extern ColorRGBA color_from_model_volume(const ModelVolume& model_volume); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL +// A container for interleaved arrays of 3D vertices and normals, +// possibly indexed by triangles and / or quads. +class GLIndexedVertexArray { +public: + // Only Eigen types of Nx16 size are vectorized. This bounding box will not be vectorized. + static_assert(sizeof(Eigen::AlignedBox) == 24, "Eigen::AlignedBox is not being vectorized, thus it does not need to be aligned"); + using BoundingBox = Eigen::AlignedBox; + + GLIndexedVertexArray() { m_bounding_box.setEmpty(); } + GLIndexedVertexArray(const GLIndexedVertexArray &rhs) : + vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved), + triangle_indices(rhs.triangle_indices), + quad_indices(rhs.quad_indices), + m_bounding_box(rhs.m_bounding_box) + { assert(! rhs.has_VBOs()); m_bounding_box.setEmpty(); } + GLIndexedVertexArray(GLIndexedVertexArray &&rhs) : + vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)), + triangle_indices(std::move(rhs.triangle_indices)), + quad_indices(std::move(rhs.quad_indices)), + m_bounding_box(rhs.m_bounding_box) + { assert(! rhs.has_VBOs()); } + + ~GLIndexedVertexArray() { release_geometry(); } + + GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs) + { + assert(vertices_and_normals_interleaved_VBO_id == 0); + assert(triangle_indices_VBO_id == 0); + assert(quad_indices_VBO_id == 0); + assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); + assert(rhs.triangle_indices_VBO_id == 0); + assert(rhs.quad_indices_VBO_id == 0); + this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; + this->triangle_indices = rhs.triangle_indices; + this->quad_indices = rhs.quad_indices; + this->m_bounding_box = rhs.m_bounding_box; + this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; + this->triangle_indices_size = rhs.triangle_indices_size; + this->quad_indices_size = rhs.quad_indices_size; + return *this; + } + + GLIndexedVertexArray& operator=(GLIndexedVertexArray &&rhs) + { + assert(vertices_and_normals_interleaved_VBO_id == 0); + assert(triangle_indices_VBO_id == 0); + assert(quad_indices_VBO_id == 0); + assert(rhs.vertices_and_normals_interleaved_VBO_id == 0); + assert(rhs.triangle_indices_VBO_id == 0); + assert(rhs.quad_indices_VBO_id == 0); + this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); + this->triangle_indices = std::move(rhs.triangle_indices); + this->quad_indices = std::move(rhs.quad_indices); + this->m_bounding_box = rhs.m_bounding_box; + this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size; + this->triangle_indices_size = rhs.triangle_indices_size; + this->quad_indices_size = rhs.quad_indices_size; + return *this; + } + + // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x) + std::vector vertices_and_normals_interleaved; + std::vector triangle_indices; + std::vector quad_indices; + + // When the geometry data is loaded into the graphics card as Vertex Buffer Objects, + // the above mentioned std::vectors are cleared and the following variables keep their original length. + size_t vertices_and_normals_interleaved_size{ 0 }; + size_t triangle_indices_size{ 0 }; + size_t quad_indices_size{ 0 }; + + // IDs of the Vertex Array Objects, into which the geometry has been loaded. + // Zero if the VBOs are not sent to GPU yet. + unsigned int vertices_and_normals_interleaved_VBO_id{ 0 }; + unsigned int triangle_indices_VBO_id{ 0 }; + unsigned int quad_indices_VBO_id{ 0 }; + +#if ENABLE_SMOOTH_NORMALS + void load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals = false); + void load_mesh(const TriangleMesh& mesh, bool smooth_normals = false) { this->load_mesh_full_shading(mesh, smooth_normals); } +#else + void load_mesh_full_shading(const TriangleMesh& mesh); + void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } +#endif // ENABLE_SMOOTH_NORMALS + + void load_its_flat_shading(const indexed_triangle_set &its); + + inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; } + + inline void reserve(size_t sz) { + this->vertices_and_normals_interleaved.reserve(sz * 6); + this->triangle_indices.reserve(sz * 3); + this->quad_indices.reserve(sz * 4); + } + + inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + + if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity()) + this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6)); + this->vertices_and_normals_interleaved.emplace_back(nx); + this->vertices_and_normals_interleaved.emplace_back(ny); + this->vertices_and_normals_interleaved.emplace_back(nz); + this->vertices_and_normals_interleaved.emplace_back(x); + this->vertices_and_normals_interleaved.emplace_back(y); + this->vertices_and_normals_interleaved.emplace_back(z); + + this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); + m_bounding_box.extend(Vec3f(x, y, z)); + }; + + inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { + push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz)); + } + + template + inline void push_geometry(const Eigen::MatrixBase& p, const Eigen::MatrixBase& n) { + push_geometry(float(p(0)), float(p(1)), float(p(2)), float(n(0)), float(n(1)), float(n(2))); + } + + inline void push_triangle(int idx1, int idx2, int idx3) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + + if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity()) + this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3)); + this->triangle_indices.emplace_back(idx1); + this->triangle_indices.emplace_back(idx2); + this->triangle_indices.emplace_back(idx3); + this->triangle_indices_size = this->triangle_indices.size(); + }; + + inline void push_quad(int idx1, int idx2, int idx3, int idx4) { + assert(this->vertices_and_normals_interleaved_VBO_id == 0); + if (this->vertices_and_normals_interleaved_VBO_id != 0) + return; + + if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity()) + this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4)); + this->quad_indices.emplace_back(idx1); + this->quad_indices.emplace_back(idx2); + this->quad_indices.emplace_back(idx3); + this->quad_indices.emplace_back(idx4); + this->quad_indices_size = this->quad_indices.size(); + }; + + // Finalize the initialization of the geometry & indices, + // upload the geometry and indices to OpenGL VBO objects + // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. + void finalize_geometry(bool opengl_initialized); + // Release the geometry data, release OpenGL VBOs. + void release_geometry(); + + void render() const; + void render(const std::pair& tverts_range, const std::pair& qverts_range) const; + + // Is there any geometry data stored? + bool empty() const { return vertices_and_normals_interleaved_size == 0; } + + void clear() { + this->vertices_and_normals_interleaved.clear(); + this->triangle_indices.clear(); + this->quad_indices.clear(); + vertices_and_normals_interleaved_size = 0; + triangle_indices_size = 0; + quad_indices_size = 0; + m_bounding_box.setEmpty(); + } + + // Shrink the internal storage to tighly fit the data stored. + void shrink_to_fit() { + this->vertices_and_normals_interleaved.shrink_to_fit(); + this->triangle_indices.shrink_to_fit(); + this->quad_indices.shrink_to_fit(); + } + + const BoundingBox& bounding_box() const { return m_bounding_box; } + + // Return an estimate of the memory consumed by this class. + size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); } + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const + { + size_t memsize = 0; + if (this->vertices_and_normals_interleaved_VBO_id != 0) + memsize += this->vertices_and_normals_interleaved_size * 4; + if (this->triangle_indices_VBO_id != 0) + memsize += this->triangle_indices_size * 4; + if (this->quad_indices_VBO_id != 0) + memsize += this->quad_indices_size * 4; + return memsize; + } + size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } + +private: + BoundingBox m_bounding_box; +}; +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +class GLVolume { +public: + static const ColorRGBA SELECTED_COLOR; + static const ColorRGBA HOVER_SELECT_COLOR; + static const ColorRGBA HOVER_DESELECT_COLOR; + static const ColorRGBA OUTSIDE_COLOR; + static const ColorRGBA SELECTED_OUTSIDE_COLOR; + static const ColorRGBA DISABLED_COLOR; + static const ColorRGBA SLA_SUPPORT_COLOR; + static const ColorRGBA SLA_PAD_COLOR; + static const ColorRGBA NEUTRAL_COLOR; + static const std::array MODEL_COLOR; + + enum EHoverState : unsigned char + { + HS_None, + HS_Hover, + HS_Select, + HS_Deselect + }; + + GLVolume(float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f); + GLVolume(const ColorRGBA& color) : GLVolume(color.r(), color.g(), color.b(), color.a()) {} + +private: + Geometry::Transformation m_instance_transformation; + Geometry::Transformation m_volume_transformation; + + // Shift in z required by sla supports+pad + double m_sla_shift_z; + // Bounding box of this volume, in unscaled coordinates. + std::optional m_transformed_bounding_box; + // Convex hull of the volume, if any. + std::shared_ptr m_convex_hull; + // Bounding box of this volume, in unscaled coordinates. + std::optional m_transformed_convex_hull_bounding_box; + // Bounding box of the non sinking part of this volume, in unscaled coordinates. + std::optional m_transformed_non_sinking_bounding_box; + + class SinkingContours + { + static const float HalfWidth; + GLVolume& m_parent; + GUI::GLModel m_model; + BoundingBoxf3 m_old_box; + Vec3d m_shift{ Vec3d::Zero() }; + + public: + SinkingContours(GLVolume& volume) : m_parent(volume) {} + void render(); + + private: + void update(); + }; + + SinkingContours m_sinking_contours; + +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + class NonManifoldEdges + { + GLVolume& m_parent; + GUI::GLModel m_model; + bool m_update_needed{ true }; + + public: + NonManifoldEdges(GLVolume& volume) : m_parent(volume) {} + void render(); + void set_as_dirty() { m_update_needed = true; } + + private: + void update(); + }; + + NonManifoldEdges m_non_manifold_edges; +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + +public: + // Color of the triangles / quads held by this volume. + ColorRGBA color; + // Color used to render this volume. + ColorRGBA render_color; + + struct CompositeID { + CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {} + CompositeID() : object_id(-1), volume_id(-1), instance_id(-1) {} + // Object ID, which is equal to the index of the respective ModelObject in Model.objects array. + int object_id; + // Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array. + // If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject, + // and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports. + // Volume with a negative volume_id cannot be picked independently, it will pick the associated instance. + int volume_id; + // Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array. + int instance_id; + bool operator==(const CompositeID &rhs) const { return object_id == rhs.object_id && volume_id == rhs.volume_id && instance_id == rhs.instance_id; } + bool operator!=(const CompositeID &rhs) const { return ! (*this == rhs); } + bool operator< (const CompositeID &rhs) const + { return object_id < rhs.object_id || (object_id == rhs.object_id && (volume_id < rhs.volume_id || (volume_id == rhs.volume_id && instance_id < rhs.instance_id))); } + }; + CompositeID composite_id; + // Fingerprint of the source geometry. For ModelVolumes, it is the ModelVolume::ID and ModelInstanceID, + // for generated volumes it is the timestamp generated by PrintState::invalidate() or PrintState::set_done(), + // and the associated ModelInstanceID. + // Valid geometry_id should always be positive. + std::pair geometry_id; + // An ID containing the extruder ID (used to select color). + int extruder_id; + + // Various boolean flags. + struct { + // Is this object selected? + bool selected : 1; + // Is this object disabled from selection? + bool disabled : 1; + // Is this object printable? + bool printable : 1; + // Whether or not this volume is active for rendering + bool is_active : 1; + // Whether or not to use this volume when applying zoom_to_volumes() + bool zoom_to_volumes : 1; + // Wheter or not this volume is enabled for outside print volume detection in shader. + bool shader_outside_printer_detection_enabled : 1; + // Wheter or not this volume is outside print volume. + bool is_outside : 1; + // Wheter or not this volume has been generated from a modifier + bool is_modifier : 1; + // Wheter or not this volume has been generated from the wipe tower + bool is_wipe_tower : 1; + // Wheter or not this volume has been generated from an extrusion path + bool is_extrusion_path : 1; + // Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE) + bool force_native_color : 1; + // Whether or not render this volume in neutral + bool force_neutral_color : 1; + // Whether or not to force rendering of sinking contours + bool force_sinking_contours : 1; + }; + + // Is mouse or rectangle selection over this object to select/deselect it ? + EHoverState hover; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GUI::GLModel model; +#else + // Interleaved triangles & normals with indexed triangles & quads. + GLIndexedVertexArray indexed_vertex_array; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + // Ranges of triangle and quad indices to be rendered. + std::pair tverts_range; +#if !ENABLE_LEGACY_OPENGL_REMOVAL + std::pair qverts_range; +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + // If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts + // of the extrusions per layer. + std::vector print_zs; + // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer. + std::vector offsets; + + // Bounding box of this volume, in unscaled coordinates. + BoundingBoxf3 bounding_box() const { +#if ENABLE_LEGACY_OPENGL_REMOVAL + return this->model.get_bounding_box(); +#else + BoundingBoxf3 out; + if (!this->indexed_vertex_array.bounding_box().isEmpty()) { + out.min = this->indexed_vertex_array.bounding_box().min().cast(); + out.max = this->indexed_vertex_array.bounding_box().max().cast(); + out.defined = true; + } + return out; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + + void set_color(const ColorRGBA& rgba) { color = rgba; } + void set_render_color(const ColorRGBA& rgba) { render_color = rgba; } + // Sets render color in dependence of current state + void set_render_color(bool force_transparent); + // set color according to model volume + void set_color_from_model_volume(const ModelVolume& model_volume); + + const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; } + void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_instance_offset() const { return m_instance_transformation.get_offset(); } +#else + const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); } + + void set_instance_offset(const Vec3d& offset) { m_instance_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } + void set_instance_offset(Axis axis, double offset) { m_instance_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_instance_rotation() const { return m_instance_transformation.get_rotation(); } +#else + const Vec3d& get_instance_rotation() const { return m_instance_transformation.get_rotation(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_instance_rotation(Axis axis) const { return m_instance_transformation.get_rotation(axis); } + + void set_instance_rotation(const Vec3d& rotation) { m_instance_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } + void set_instance_rotation(Axis axis, double rotation) { m_instance_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } + + Vec3d get_instance_scaling_factor() const { return m_instance_transformation.get_scaling_factor(); } + double get_instance_scaling_factor(Axis axis) const { return m_instance_transformation.get_scaling_factor(axis); } + + void set_instance_scaling_factor(const Vec3d& scaling_factor) { m_instance_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } + void set_instance_scaling_factor(Axis axis, double scaling_factor) { m_instance_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_instance_mirror() const { return m_instance_transformation.get_mirror(); } +#else + const Vec3d& get_instance_mirror() const { return m_instance_transformation.get_mirror(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_instance_mirror(Axis axis) const { return m_instance_transformation.get_mirror(axis); } + + void set_instance_mirror(const Vec3d& mirror) { m_instance_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } + void set_instance_mirror(Axis axis, double mirror) { m_instance_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); } + + const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; } + void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_volume_offset() const { return m_volume_transformation.get_offset(); } +#else + const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); } + + void set_volume_offset(const Vec3d& offset) { m_volume_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } + void set_volume_offset(Axis axis, double offset) { m_volume_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_volume_rotation() const { return m_volume_transformation.get_rotation(); } +#else + const Vec3d& get_volume_rotation() const { return m_volume_transformation.get_rotation(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_volume_rotation(Axis axis) const { return m_volume_transformation.get_rotation(axis); } + + void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } + void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } +#else + const Vec3d& get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); } + + void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } + void set_volume_scaling_factor(Axis axis, double scaling_factor) { m_volume_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d get_volume_mirror() const { return m_volume_transformation.get_mirror(); } +#else + const Vec3d& get_volume_mirror() const { return m_volume_transformation.get_mirror(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + double get_volume_mirror(Axis axis) const { return m_volume_transformation.get_mirror(axis); } + + void set_volume_mirror(const Vec3d& mirror) { m_volume_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } + void set_volume_mirror(Axis axis, double mirror) { m_volume_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); } + + double get_sla_shift_z() const { return m_sla_shift_z; } + void set_sla_shift_z(double z) { m_sla_shift_z = z; } + + void set_convex_hull(std::shared_ptr convex_hull) { m_convex_hull = std::move(convex_hull); } + void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared(convex_hull); } + void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared(std::move(convex_hull)); } + + int object_idx() const { return this->composite_id.object_id; } + int volume_idx() const { return this->composite_id.volume_id; } + int instance_idx() const { return this->composite_id.instance_id; } + + Transform3d world_matrix() const; + bool is_left_handed() const; + + const BoundingBoxf3& transformed_bounding_box() const; + // non-caching variant + BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const; + // caching variant + const BoundingBoxf3& transformed_convex_hull_bounding_box() const; + // non-caching variant + BoundingBoxf3 transformed_non_sinking_bounding_box(const Transform3d& trafo) const; + // caching variant + const BoundingBoxf3& transformed_non_sinking_bounding_box() const; + // convex hull + const TriangleMesh* convex_hull() const { return m_convex_hull.get(); } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + bool empty() const { return this->model.is_empty(); } +#else + bool empty() const { return this->indexed_vertex_array.empty(); } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + void set_range(double low, double high); + + void render(); + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); } + void release_geometry() { this->indexed_vertex_array.release_geometry(); } +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + + void set_bounding_boxes_as_dirty() { + m_transformed_bounding_box.reset(); + m_transformed_convex_hull_bounding_box.reset(); + m_transformed_non_sinking_bounding_box.reset(); + } + + bool is_sla_support() const; + bool is_sla_pad() const; + + bool is_sinking() const; + bool is_below_printbed() const; + void render_sinking_contours(); +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + void render_non_manifold_edges(); +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + + // Return an estimate of the memory consumed by this class. + size_t cpu_memory_used() const { +#if ENABLE_LEGACY_OPENGL_REMOVAL + return sizeof(*this) + this->model.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + + this->offsets.capacity() * sizeof(size_t); + } + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const { return this->model.gpu_memory_used(); } +#else + //FIXME what to do wih m_convex_hull? + return sizeof(*this) - sizeof(this->indexed_vertex_array) + this->indexed_vertex_array.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) + this->offsets.capacity() * sizeof(size_t); + } + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } +}; + +typedef std::vector GLVolumePtrs; +typedef std::pair> GLVolumeWithIdAndZ; +typedef std::vector GLVolumeWithIdAndZList; + +class GLVolumeCollection +{ +public: + enum class ERenderType : unsigned char + { + Opaque, + Transparent, + All + }; + + struct PrintVolume + { + // see: Bed3D::EShapeType + int type{ 0 }; + // data contains: + // Rectangle: + // [0] = min.x, [1] = min.y, [2] = max.x, [3] = max.y + // Circle: + // [0] = center.x, [1] = center.y, [3] = radius + std::array data; + // [0] = min z, [1] = max z + std::array zs; + }; + +private: + PrintVolume m_print_volume; + + // z range for clipping in shaders + std::array m_z_range; + + // plane coeffs for clipping in shaders + std::array m_clipping_plane; + + struct Slope + { + // toggle for slope rendering + bool active{ false }; + float normal_z; + }; + + Slope m_slope; + bool m_show_sinking_contours{ false }; +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + bool m_show_non_manifold_edges{ true }; +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + +public: + GLVolumePtrs volumes; + + GLVolumeCollection() { set_default_slope_normal_z(); } + ~GLVolumeCollection() { clear(); } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + std::vector load_object( + const ModelObject* model_object, + int obj_idx, + const std::vector& instance_idxs); + + int load_object_volume( + const ModelObject* model_object, + int obj_idx, + int volume_idx, + int instance_idx); + + // Load SLA auxiliary GLVolumes (for support trees or pad). + void load_object_auxiliary( + const SLAPrintObject* print_object, + int obj_idx, + // pairs of + const std::vector>& instances, + SLAPrintObjectStep milestone, + // Timestamp of the last change of the milestone + size_t timestamp); + +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + int load_wipe_tower_preview( + float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); +#else + int load_wipe_tower_preview( + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#else + std::vector load_object( + const ModelObject *model_object, + int obj_idx, + const std::vector &instance_idxs, + bool opengl_initialized); + + int load_object_volume( + const ModelObject *model_object, + int obj_idx, + int volume_idx, + int instance_idx, + bool opengl_initialized); + + // Load SLA auxiliary GLVolumes (for support trees or pad). + void load_object_auxiliary( + const SLAPrintObject *print_object, + int obj_idx, + // pairs of + const std::vector>& instances, + SLAPrintObjectStep milestone, + // Timestamp of the last change of the milestone + size_t timestamp, + bool opengl_initialized); + +#if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL + int load_wipe_tower_preview( + float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); +#else + int load_wipe_tower_preview( + int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, bool opengl_initialized); +#endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLVolume* new_toolpath_volume(const ColorRGBA& rgba); + GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba); +#else + GLVolume* new_toolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); + GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba, size_t reserve_vbo_floats = 0); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // Render the volumes by OpenGL. +#if ENABLE_GL_SHADERS_ATTRIBUTES + void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, const Transform3d& projection_matrix, + std::function filter_func = std::function()) const; +#else + void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function filter_func = std::function()) const; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + +#if !ENABLE_LEGACY_OPENGL_REMOVAL + // Finalize the initialization of the geometry & indices, + // upload the geometry and indices to OpenGL VBO objects + // and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs. + void finalize_geometry(bool opengl_initialized) { for (auto* v : volumes) v->finalize_geometry(opengl_initialized); } + // Release the geometry data assigned to the volumes. + // If OpenGL VBOs were allocated, an OpenGL context has to be active to release them. + void release_geometry() { for (auto *v : volumes) v->release_geometry(); } +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + // Clear the geometry + void clear() { for (auto *v : volumes) delete v; volumes.clear(); } + + 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_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; } + void set_clipping_plane(const std::array& coeffs) { m_clipping_plane = coeffs; } + + const std::array& get_z_range() const { return m_z_range; } + const std::array& get_clipping_plane() const { return m_clipping_plane; } + + bool is_slope_active() const { return m_slope.active; } + void set_slope_active(bool active) { m_slope.active = active; } + + float get_slope_normal_z() const { return m_slope.normal_z; } + void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; } + void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); } + void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; } +#if ENABLE_SHOW_NON_MANIFOLD_EDGES + void set_show_non_manifold_edges(bool show) { m_show_non_manifold_edges = show; } +#endif // ENABLE_SHOW_NON_MANIFOLD_EDGES + + // returns true if all the volumes are completely contained in the print volume + // returns the containment state in the given out_state, if non-null + bool check_outside_state(const Slic3r::BuildVolume& build_volume, ModelInstanceEPrintVolumeState* out_state) const; + void reset_outside_state(); + + void update_colors_by_extruder(const DynamicPrintConfig* config); + + // Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection + std::vector get_current_print_zs(bool active_only) const; + + // Return an estimate of the memory consumed by this class. + size_t cpu_memory_used() const; + // Return an estimate of the memory held by GPU vertex buffers. + size_t gpu_memory_used() const; + size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } + // Return CPU, GPU and total memory log line. + std::string log_memory_info() const; + +private: + GLVolumeCollection(const GLVolumeCollection &other); + GLVolumeCollection& operator=(const GLVolumeCollection &); +}; + +GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function filter_func = nullptr); + +struct _3DScene +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL + static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GUI::GLModel::Geometry& geometry); + static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); + static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry); +#else + static void thick_lines_to_verts(const Lines& lines, const std::vector& widths, const std::vector& heights, bool closed, double top_z, GLVolume& volume); + static void thick_lines_to_verts(const Lines3& lines, const std::vector& widths, const std::vector& heights, bool closed, GLVolume& volume); + static void extrusionentity_to_verts(const Polyline& polyline, float width, float height, float print_z, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume); + static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume); + static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume); + static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +}; + +} + +#endif diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 353577944..216c2f472 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1532,7 +1532,11 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); const Geometry::Transformation inst_transform = v->get_instance_transformation(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d inv_inst_transform = inst_transform.get_matrix_no_offset().inverse(); +#else const Transform3d inv_inst_transform = inst_transform.get_matrix(true).inverse(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d instance_offset = v->get_instance_offset(); for (size_t i = 0; i < input_files.size(); ++i) { @@ -1660,7 +1664,11 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode Vec3d(0., 0., 0.5 * mesh_bb.size().z() + instance_bb.min.z() - v->get_instance_offset().z()) : // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - v->get_instance_offset(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + new_volume->set_offset(v->get_instance_transformation().get_matrix_no_offset().inverse() * offset); +#else new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const wxString name = _L("Generic") + "-" + _(type_name); new_volume->name = into_u8(name); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 96429c924..c73f5eb4a 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -354,7 +354,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const double min_z = get_volume_min_z(*volume); if (!is_world_coordinates()) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); +#else const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); @@ -381,7 +385,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const double min_z = selection.get_scaled_instance_bounding_box().min.z(); if (!is_world_coordinates()) { const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); +#else const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 66b6dcf60..a52c85d67 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -1,437 +1,441 @@ -#include "GLGizmoFdmSupports.hpp" - -#include "libslic3r/Model.hpp" - -//#include "slic3r/GUI/3DScene.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/ImGuiWrapper.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/GUI_ObjectList.hpp" -#include "slic3r/GUI/format.hpp" -#include "slic3r/Utils/UndoRedo.hpp" - - -#include - - -namespace Slic3r::GUI { - - - -void GLGizmoFdmSupports::on_shutdown() -{ - m_highlight_by_angle_threshold_deg = 0.f; - m_parent.use_slope(false); - m_parent.toggle_model_objects_visibility(true); -} - - - -std::string GLGizmoFdmSupports::on_get_name() const -{ - return _u8L("Paint-on supports"); -} - - - -bool GLGizmoFdmSupports::on_init() -{ - m_shortcut_key = WXK_CONTROL_L; - - m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; - m_desc["reset_direction"] = _L("Reset direction"); - m_desc["cursor_size"] = _L("Brush size") + ": "; - m_desc["cursor_type"] = _L("Brush shape") + ": "; - m_desc["enforce_caption"] = _L("Left mouse button") + ": "; - m_desc["enforce"] = _L("Enforce supports"); - m_desc["block_caption"] = _L("Right mouse button") + ": "; - m_desc["block"] = _L("Block supports"); - m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; - m_desc["remove"] = _L("Remove selection"); - m_desc["remove_all"] = _L("Remove all selection"); - m_desc["circle"] = _L("Circle"); - m_desc["sphere"] = _L("Sphere"); - m_desc["pointer"] = _L("Triangles"); - m_desc["highlight_by_angle"] = _L("Highlight overhang by angle"); - m_desc["enforce_button"] = _L("Enforce"); - m_desc["cancel"] = _L("Cancel"); - - m_desc["tool_type"] = _L("Tool type") + ": "; - m_desc["tool_brush"] = _L("Brush"); - m_desc["tool_smart_fill"] = _L("Smart fill"); - - m_desc["smart_fill_angle"] = _L("Smart fill angle"); - - m_desc["split_triangles"] = _L("Split triangles"); - m_desc["on_overhangs_only"] = _L("On overhangs only"); - - return true; -} - -void GLGizmoFdmSupports::render_painter_gizmo() -{ - const Selection& selection = m_parent.get_selection(); - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - render_triangles(selection); - m_c->object_clipper()->render_cut(); - m_c->instances_hider()->render_cut(); - render_cursor(); - - glsafe(::glDisable(GL_BLEND)); -} - - - -void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit) -{ - if (! m_c->selection_info()->model_object()) - return; - - const float approx_height = m_imgui->scaled(23.f); - y = std::min(y, bottom_limit - approx_height); - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); - const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); - const float autoset_slider_label_max_width = m_imgui->scaled(7.5f); - const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f); - - const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); - const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); - const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); - - const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); - const float button_enforce_width = m_imgui->calc_text_size(m_desc.at("enforce_button")).x; - const float button_cancel_width = m_imgui->calc_text_size(m_desc.at("cancel")).x; - const float buttons_width = std::max(button_enforce_width, button_cancel_width) + m_imgui->scaled(0.5f); - const float minimal_slider_width = m_imgui->scaled(4.f); - - const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f); - const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); - const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); - - const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); - const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f); - - float caption_max = 0.f; - float total_text_max = 0.f; - for (const auto &t : std::array{"enforce", "block", "remove"}) { - caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); - total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); - } - total_text_max += caption_max + m_imgui->scaled(1.f); - caption_max += m_imgui->scaled(1.f); - - const float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); - const float slider_icon_width = m_imgui->get_slider_icon_size().x; - float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; - window_width = std::max(window_width, total_text_max); - window_width = std::max(window_width, button_width); - window_width = std::max(window_width, split_triangles_checkbox_width); - window_width = std::max(window_width, on_overhangs_only_checkbox_width); - window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); - window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); - window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); - - auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { - m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption); - ImGui::SameLine(caption_max); - m_imgui->text(text); - }; - - for (const auto &t : std::array{"enforce", "block", "remove"}) - draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); - - ImGui::Separator(); - - float position_before_text_y = ImGui::GetCursorPos().y; - ImGui::AlignTextToFramePadding(); - m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width); - ImGui::AlignTextToFramePadding(); - float position_after_text_y = ImGui::GetCursorPos().y; - std::string format_str = std::string("%.f") + I18N::translate_utf8("°", - "Degree sign to use in the respective slider in FDM supports gizmo," - "placed after the number with no whitespace in between."); - ImGui::SameLine(sliders_left_width); - - float slider_height = m_imgui->get_slider_float_height(); - // Makes slider to be aligned to bottom of the multi-line text. - float slider_start_position_y = std::max(position_before_text_y, position_after_text_y - slider_height); - ImGui::SetCursorPosY(slider_start_position_y); - - ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - wxString tooltip = format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when " - "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]); - if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data(), 1.0f, true, tooltip)) { - m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg); - if (! m_parent.is_using_slope()) { - m_parent.use_slope(true); - m_parent.set_as_dirty(); - } - } - - // Restores the cursor position to be below the multi-line text. - ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y)); - - const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; - - m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f); - ImGui::NewLine(); - ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f)); - if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) { - select_facets_by_angle(m_highlight_by_angle_threshold_deg, false); - m_highlight_by_angle_threshold_deg = 0.f; - m_parent.use_slope(false); - } - ImGui::SameLine(window_width - buttons_width); - if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) { - m_highlight_by_angle_threshold_deg = 0.f; - m_parent.use_slope(false); - } - m_imgui->disabled_end(); - - - ImGui::Separator(); - - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["tool_type"]); - - float tool_type_offset = tool_type_radio_left + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + m_imgui->scaled(0.5f)) / 2.f; - ImGui::SameLine(tool_type_offset); - ImGui::PushItemWidth(tool_type_radio_brush); - if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) - m_tool_type = ToolType::BRUSH; - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width); - - ImGui::SameLine(tool_type_offset + tool_type_radio_brush); - ImGui::PushItemWidth(tool_type_radio_smart_fill); - if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) - m_tool_type = ToolType::SMART_FILL; - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); - - m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); - if (ImGui::IsItemHovered()) - m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width); - - ImGui::Separator(); - - if (m_tool_type == ToolType::BRUSH) { - m_imgui->text(m_desc.at("cursor_type")); - ImGui::NewLine(); - - float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f; - ImGui::SameLine(cursor_type_offset); - ImGui::PushItemWidth(cursor_type_radio_sphere); - if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) - m_cursor_type = TriangleSelector::CursorType::SPHERE; - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); - - ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); - ImGui::PushItemWidth(cursor_type_radio_circle); - - if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) - m_cursor_type = TriangleSelector::CursorType::CIRCLE; - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); - - ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); - ImGui::PushItemWidth(cursor_type_radio_pointer); - - if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) - m_cursor_type = TriangleSelector::CursorType::POINTER; - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); - - m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); - - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("cursor_size")); - ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel")); - - m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); - - if (ImGui::IsItemHovered()) - m_imgui->tooltip(_L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width); - - m_imgui->disabled_end(); - } else { - assert(m_tool_type == ToolType::SMART_FILL); - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["smart_fill_angle"] + ":"); - - ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) - for (auto &triangle_selector : m_triangle_selectors) { - triangle_selector->seed_fill_unselect_all_triangles(); - triangle_selector->request_update_render_data(); - } - } - - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); - }); - } - } - - auto clp_dist = float(m_c->object_clipper()->get_position()); - ImGui::SameLine(sliders_left_width); - ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); - if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) - m_c->object_clipper()->set_position(clp_dist, true); - - ImGui::Separator(); - if (m_imgui->button(m_desc.at("remove_all"))) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); - ModelObject *mo = m_c->selection_info()->model_object(); - int idx = -1; - for (ModelVolume *mv : mo->volumes) - if (mv->is_model_part()) { - ++idx; - m_triangle_selectors[idx]->reset(); - m_triangle_selectors[idx]->request_update_render_data(); - } - - update_model_object(); - m_parent.set_as_dirty(); - } - - m_imgui->end(); -} - - - -void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) -{ - float threshold = (float(M_PI)/180.f)*threshold_deg; - const Selection& selection = m_parent.get_selection(); - const ModelObject* mo = m_c->selection_info()->model_object(); - const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; - - int mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - - ++mesh_id; - - const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); - Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized(); - Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized(); - - float dot_limit = limit.dot(down); - - // Now calculate dot product of vert_direction and facets' normals. - int idx = 0; - const indexed_triangle_set &its = mv->mesh().its; - for (const stl_triangle_vertex_indices &face : its.indices) { - if (its_face_normal(its, face).dot(down) > dot_limit) { - m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER); - m_triangle_selectors.back()->request_update_render_data(); - } - ++ idx; - } - } - - Plater::TakeSnapshot snapshot(wxGetApp().plater(), block ? _L("Block supports by angle") - : _L("Add supports by angle")); - update_model_object(); - m_parent.set_as_dirty(); -} - - - -void GLGizmoFdmSupports::update_model_object() const -{ - bool updated = false; - ModelObject* mo = m_c->selection_info()->model_object(); - int idx = -1; - for (ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - ++idx; - updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get()); - } - - if (updated) { - const ModelObjectPtrs& mos = wxGetApp().model().objects; - wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); - - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - } -} - - - -void GLGizmoFdmSupports::update_from_model_object() -{ - wxBusyCursor wait; - - const ModelObject* mo = m_c->selection_info()->model_object(); - m_triangle_selectors.clear(); - - int volume_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - - ++volume_id; - - // This mesh does not account for the possible Z up SLA offset. - const TriangleMesh* mesh = &mv->mesh(); - - m_triangle_selectors.emplace_back(std::make_unique(*mesh)); - // Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize(). - m_triangle_selectors.back()->deserialize(mv->supported_facets.get_data(), false); - m_triangle_selectors.back()->request_update_render_data(); - } -} - - - -PainterGizmoType GLGizmoFdmSupports::get_painter_type() const -{ - return PainterGizmoType::FDM_SUPPORTS; -} - -wxString GLGizmoFdmSupports::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const -{ - wxString action_name; - if (shift_down) - action_name = _L("Remove selection"); - else { - if (button_down == Button::Left) - action_name = _L("Add supports"); - else - action_name = _L("Block supports"); - } - return action_name; -} - -} // namespace Slic3r::GUI +#include "GLGizmoFdmSupports.hpp" + +#include "libslic3r/Model.hpp" + +//#include "slic3r/GUI/3DScene.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/ImGuiWrapper.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/format.hpp" +#include "slic3r/Utils/UndoRedo.hpp" + + +#include + + +namespace Slic3r::GUI { + + + +void GLGizmoFdmSupports::on_shutdown() +{ + m_highlight_by_angle_threshold_deg = 0.f; + m_parent.use_slope(false); + m_parent.toggle_model_objects_visibility(true); +} + + + +std::string GLGizmoFdmSupports::on_get_name() const +{ + return _u8L("Paint-on supports"); +} + + + +bool GLGizmoFdmSupports::on_init() +{ + m_shortcut_key = WXK_CONTROL_L; + + m_desc["clipping_of_view"] = _L("Clipping of view") + ": "; + m_desc["reset_direction"] = _L("Reset direction"); + m_desc["cursor_size"] = _L("Brush size") + ": "; + m_desc["cursor_type"] = _L("Brush shape") + ": "; + m_desc["enforce_caption"] = _L("Left mouse button") + ": "; + m_desc["enforce"] = _L("Enforce supports"); + m_desc["block_caption"] = _L("Right mouse button") + ": "; + m_desc["block"] = _L("Block supports"); + m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; + m_desc["remove"] = _L("Remove selection"); + m_desc["remove_all"] = _L("Remove all selection"); + m_desc["circle"] = _L("Circle"); + m_desc["sphere"] = _L("Sphere"); + m_desc["pointer"] = _L("Triangles"); + m_desc["highlight_by_angle"] = _L("Highlight overhang by angle"); + m_desc["enforce_button"] = _L("Enforce"); + m_desc["cancel"] = _L("Cancel"); + + m_desc["tool_type"] = _L("Tool type") + ": "; + m_desc["tool_brush"] = _L("Brush"); + m_desc["tool_smart_fill"] = _L("Smart fill"); + + m_desc["smart_fill_angle"] = _L("Smart fill angle"); + + m_desc["split_triangles"] = _L("Split triangles"); + m_desc["on_overhangs_only"] = _L("On overhangs only"); + + return true; +} + +void GLGizmoFdmSupports::render_painter_gizmo() +{ + const Selection& selection = m_parent.get_selection(); + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + render_triangles(selection); + m_c->object_clipper()->render_cut(); + m_c->instances_hider()->render_cut(); + render_cursor(); + + glsafe(::glDisable(GL_BLEND)); +} + + + +void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit) +{ + if (! m_c->selection_info()->model_object()) + return; + + const float approx_height = m_imgui->scaled(23.f); + y = std::min(y, bottom_limit - approx_height); + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + + m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + + // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, + m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f); + const float smart_fill_slider_left = m_imgui->calc_text_size(m_desc.at("smart_fill_angle")).x + m_imgui->scaled(1.f); + const float autoset_slider_label_max_width = m_imgui->scaled(7.5f); + const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("highlight_by_angle"), autoset_slider_label_max_width).x + m_imgui->scaled(1.f); + + const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f); + const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f); + + const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f); + const float button_enforce_width = m_imgui->calc_text_size(m_desc.at("enforce_button")).x; + const float button_cancel_width = m_imgui->calc_text_size(m_desc.at("cancel")).x; + const float buttons_width = std::max(button_enforce_width, button_cancel_width) + m_imgui->scaled(0.5f); + const float minimal_slider_width = m_imgui->scaled(4.f); + + const float tool_type_radio_left = m_imgui->calc_text_size(m_desc["tool_type"]).x + m_imgui->scaled(1.f); + const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f); + const float tool_type_radio_smart_fill = m_imgui->calc_text_size(m_desc["tool_smart_fill"]).x + m_imgui->scaled(2.5f); + + const float split_triangles_checkbox_width = m_imgui->calc_text_size(m_desc["split_triangles"]).x + m_imgui->scaled(2.5f); + const float on_overhangs_only_checkbox_width = m_imgui->calc_text_size(m_desc["on_overhangs_only"]).x + m_imgui->scaled(2.5f); + + float caption_max = 0.f; + float total_text_max = 0.f; + for (const auto &t : std::array{"enforce", "block", "remove"}) { + caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc[t + "_caption"]).x); + total_text_max = std::max(total_text_max, m_imgui->calc_text_size(m_desc[t]).x); + } + total_text_max += caption_max + m_imgui->scaled(1.f); + caption_max += m_imgui->scaled(1.f); + + const float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left)); + const float slider_icon_width = m_imgui->get_slider_icon_size().x; + float window_width = minimal_slider_width + sliders_left_width + slider_icon_width; + window_width = std::max(window_width, total_text_max); + window_width = std::max(window_width, button_width); + window_width = std::max(window_width, split_triangles_checkbox_width); + window_width = std::max(window_width, on_overhangs_only_checkbox_width); + window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer); + window_width = std::max(window_width, tool_type_radio_left + tool_type_radio_brush + tool_type_radio_smart_fill); + window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f)); + + auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) { + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption); + ImGui::SameLine(caption_max); + m_imgui->text(text); + }; + + for (const auto &t : std::array{"enforce", "block", "remove"}) + draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t)); + + ImGui::Separator(); + + float position_before_text_y = ImGui::GetCursorPos().y; + ImGui::AlignTextToFramePadding(); + m_imgui->text_wrapped(m_desc["highlight_by_angle"] + ":", autoset_slider_label_max_width); + ImGui::AlignTextToFramePadding(); + float position_after_text_y = ImGui::GetCursorPos().y; + std::string format_str = std::string("%.f") + I18N::translate_utf8("°", + "Degree sign to use in the respective slider in FDM supports gizmo," + "placed after the number with no whitespace in between."); + ImGui::SameLine(sliders_left_width); + + float slider_height = m_imgui->get_slider_float_height(); + // Makes slider to be aligned to bottom of the multi-line text. + float slider_start_position_y = std::max(position_before_text_y, position_after_text_y - slider_height); + ImGui::SetCursorPosY(slider_start_position_y); + + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + wxString tooltip = format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when " + "the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]); + if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data(), 1.0f, true, tooltip)) { + m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg); + if (! m_parent.is_using_slope()) { + m_parent.use_slope(true); + m_parent.set_as_dirty(); + } + } + + // Restores the cursor position to be below the multi-line text. + ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y)); + + const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; + + m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f); + ImGui::NewLine(); + ImGui::SameLine(window_width - 2.f*buttons_width - m_imgui->scaled(0.5f)); + if (m_imgui->button(m_desc["enforce_button"], buttons_width, 0.f)) { + select_facets_by_angle(m_highlight_by_angle_threshold_deg, false); + m_highlight_by_angle_threshold_deg = 0.f; + m_parent.use_slope(false); + } + ImGui::SameLine(window_width - buttons_width); + if (m_imgui->button(m_desc["cancel"], buttons_width, 0.f)) { + m_highlight_by_angle_threshold_deg = 0.f; + m_parent.use_slope(false); + } + m_imgui->disabled_end(); + + + ImGui::Separator(); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["tool_type"]); + + float tool_type_offset = tool_type_radio_left + (window_width - tool_type_radio_left - tool_type_radio_brush - tool_type_radio_smart_fill + m_imgui->scaled(0.5f)) / 2.f; + ImGui::SameLine(tool_type_offset); + ImGui::PushItemWidth(tool_type_radio_brush); + if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == ToolType::BRUSH)) + m_tool_type = ToolType::BRUSH; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints facets according to the chosen painting brush."), max_tooltip_width); + + ImGui::SameLine(tool_type_offset + tool_type_radio_brush); + ImGui::PushItemWidth(tool_type_radio_smart_fill); + if (m_imgui->radio_button(m_desc["tool_smart_fill"], m_tool_type == ToolType::SMART_FILL)) + m_tool_type = ToolType::SMART_FILL; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints neighboring facets whose relative angle is less or equal to set angle."), max_tooltip_width); + + m_imgui->checkbox(m_desc["on_overhangs_only"], m_paint_on_overhangs_only); + if (ImGui::IsItemHovered()) + m_imgui->tooltip(format_wxstr(_L("Allows painting only on facets selected by: \"%1%\""), m_desc["highlight_by_angle"]), max_tooltip_width); + + ImGui::Separator(); + + if (m_tool_type == ToolType::BRUSH) { + m_imgui->text(m_desc.at("cursor_type")); + ImGui::NewLine(); + + float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(1.5f)) / 2.f; + ImGui::SameLine(cursor_type_offset); + ImGui::PushItemWidth(cursor_type_radio_sphere); + if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE)) + m_cursor_type = TriangleSelector::CursorType::SPHERE; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints all facets inside, regardless of their orientation."), max_tooltip_width); + + ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere); + ImGui::PushItemWidth(cursor_type_radio_circle); + + if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE)) + m_cursor_type = TriangleSelector::CursorType::CIRCLE; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Ignores facets facing away from the camera."), max_tooltip_width); + + ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle); + ImGui::PushItemWidth(cursor_type_radio_pointer); + + if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER)) + m_cursor_type = TriangleSelector::CursorType::POINTER; + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Paints only one facet."), max_tooltip_width); + + m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE); + + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("cursor_size")); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel")); + + m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled); + + if (ImGui::IsItemHovered()) + m_imgui->tooltip(_L("Splits bigger facets into smaller ones while the object is painted."), max_tooltip_width); + + m_imgui->disabled_end(); + } else { + assert(m_tool_type == ToolType::SMART_FILL); + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["smart_fill_angle"] + ":"); + + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel"))) + for (auto &triangle_selector : m_triangle_selectors) { + triangle_selector->seed_fill_unselect_all_triangles(); + triangle_selector->request_update_render_data(); + } + } + + ImGui::Separator(); + if (m_c->object_clipper()->get_position() == 0.f) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("clipping_of_view")); + } + else { + if (m_imgui->button(m_desc.at("reset_direction"))) { + wxGetApp().CallAfter([this](){ + m_c->object_clipper()->set_position(-1., false); + }); + } + } + + auto clp_dist = float(m_c->object_clipper()->get_position()); + ImGui::SameLine(sliders_left_width); + ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width); + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel"))) + m_c->object_clipper()->set_position(clp_dist, true); + + ImGui::Separator(); + if (m_imgui->button(m_desc.at("remove_all"))) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset selection"), UndoRedo::SnapshotType::GizmoAction); + ModelObject *mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { + ++idx; + m_triangle_selectors[idx]->reset(); + m_triangle_selectors[idx]->request_update_render_data(); + } + + update_model_object(); + m_parent.set_as_dirty(); + } + + m_imgui->end(); +} + + + +void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) +{ + float threshold = (float(M_PI)/180.f)*threshold_deg; + const Selection& selection = m_parent.get_selection(); + const ModelObject* mo = m_c->selection_info()->model_object(); + const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; + + int mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++mesh_id; + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d trafo_matrix = mi->get_matrix_no_offset() * mv->get_matrix_no_offset(); +#else + const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized(); + Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized(); + + float dot_limit = limit.dot(down); + + // Now calculate dot product of vert_direction and facets' normals. + int idx = 0; + const indexed_triangle_set &its = mv->mesh().its; + for (const stl_triangle_vertex_indices &face : its.indices) { + if (its_face_normal(its, face).dot(down) > dot_limit) { + m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER); + m_triangle_selectors.back()->request_update_render_data(); + } + ++ idx; + } + } + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), block ? _L("Block supports by angle") + : _L("Add supports by angle")); + update_model_object(); + m_parent.set_as_dirty(); +} + + + +void GLGizmoFdmSupports::update_model_object() const +{ + bool updated = false; + ModelObject* mo = m_c->selection_info()->model_object(); + int idx = -1; + for (ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + ++idx; + updated |= mv->supported_facets.set(*m_triangle_selectors[idx].get()); + } + + if (updated) { + const ModelObjectPtrs& mos = wxGetApp().model().objects; + wxGetApp().obj_list()->update_info_items(std::find(mos.begin(), mos.end(), mo) - mos.begin()); + + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); + } +} + + + +void GLGizmoFdmSupports::update_from_model_object() +{ + wxBusyCursor wait; + + const ModelObject* mo = m_c->selection_info()->model_object(); + m_triangle_selectors.clear(); + + int volume_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++volume_id; + + // This mesh does not account for the possible Z up SLA offset. + const TriangleMesh* mesh = &mv->mesh(); + + m_triangle_selectors.emplace_back(std::make_unique(*mesh)); + // Reset of TriangleSelector is done inside TriangleSelectorGUI's constructor, so we don't need it to perform it again in deserialize(). + m_triangle_selectors.back()->deserialize(mv->supported_facets.get_data(), false); + m_triangle_selectors.back()->request_update_render_data(); + } +} + + + +PainterGizmoType GLGizmoFdmSupports::get_painter_type() const +{ + return PainterGizmoType::FDM_SUPPORTS; +} + +wxString GLGizmoFdmSupports::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const +{ + wxString action_name; + if (shift_down) + action_name = _L("Remove selection"); + else { + if (button_down == Button::Left) + action_name = _L("Add supports"); + else + action_name = _L("Block supports"); + } + return action_name; +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index aa291f623..b882570fc 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -1,475 +1,479 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. -#include "GLGizmoFlatten.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#if ENABLE_LEGACY_OPENGL_REMOVAL -#include "slic3r/GUI/GUI_App.hpp" -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES -#include "slic3r/GUI/Plater.hpp" -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" - -#include "libslic3r/Geometry/ConvexHull.hpp" -#include "libslic3r/Model.hpp" - -#include - -#include - -namespace Slic3r { -namespace GUI { - -static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f }; -static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f }; - -GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -{} - -bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event) -{ - if (mouse_event.Moving()) { - // only for sure - m_mouse_left_down = false; - return false; - } - if (mouse_event.LeftDown()) { - if (m_hover_id != -1) { - m_mouse_left_down = true; - Selection &selection = m_parent.get_selection(); - if (selection.is_single_full_instance()) { - // Rotate the object so the normal points downward: - selection.flattening_rotate(m_planes[m_hover_id].normal); - m_parent.do_rotate(L("Gizmo-Place on Face")); - } - return true; - } - - // fix: prevent restart gizmo when reselect object - // take responsibility for left up - if (m_parent.get_first_hover_volume_idx() >= 0) m_mouse_left_down = true; - - } else if (mouse_event.LeftUp()) { - if (m_mouse_left_down) { - // responsible for mouse left up after selecting plane - m_mouse_left_down = false; - return true; - } - } else if (mouse_event.Leaving()) { - m_mouse_left_down = false; - } - return false; -} - -void GLGizmoFlatten::data_changed() -{ - const Selection & selection = m_parent.get_selection(); - const ModelObject *model_object = nullptr; - if (selection.is_single_full_instance() || - selection.is_from_single_object() ) { - model_object = selection.get_model()->objects[selection.get_object_idx()]; - } - set_flattening_data(model_object); -} - -bool GLGizmoFlatten::on_init() -{ - m_shortcut_key = WXK_CONTROL_F; - return true; -} - -void GLGizmoFlatten::on_set_state() -{ -} - -CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const -{ - return CommonGizmosDataID::SelectionInfo; -} - -std::string GLGizmoFlatten::on_get_name() const -{ - return _u8L("Place on face"); -} - -bool GLGizmoFlatten::on_is_activable() const -{ - // This is assumed in GLCanvas3D::do_rotate, do not change this - // without updating that function too. - return m_parent.get_selection().is_single_full_instance(); -} - -void GLGizmoFlatten::on_render() -{ - const Selection& selection = m_parent.get_selection(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; - - shader->start_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glEnable(GL_BLEND)); - - if (selection.is_single_full_instance()) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_model_matrix = camera.get_view_matrix() * - Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); - glsafe(::glMultMatrixd(m.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (this->is_plane_update_necessary()) - update_planes(); - for (int i = 0; i < (int)m_planes.size(); ++i) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_planes[i].vbo.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); - m_planes[i].vbo.render(); -#else - glsafe(::glColor4fv(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR.data() : DEFAULT_PLANE_COLOR.data())); - if (m_planes[i].vbo.has_VBOs()) - m_planes[i].vbo.render(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glDisable(GL_BLEND)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLGizmoFlatten::on_render_for_picking() -{ - const Selection& selection = m_parent.get_selection(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; - - shader->start_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_BLEND)); - - if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d view_model_matrix = camera.get_view_matrix() * - Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); - glsafe(::glMultMatrixd(m.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (this->is_plane_update_necessary()) - update_planes(); - for (int i = 0; i < (int)m_planes.size(); ++i) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_planes[i].vbo.set_color(picking_color_component(i)); -#else - glsafe(::glColor4fv(picking_color_component(i).data())); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - m_planes[i].vbo.render(); - } -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - - glsafe(::glEnable(GL_CULL_FACE)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) -{ - if (model_object != m_old_model_object) { - m_planes.clear(); - m_planes_valid = false; - } -} - -void GLGizmoFlatten::update_planes() -{ - const ModelObject* mo = m_c->selection_info()->model_object(); - TriangleMesh ch; - for (const ModelVolume* vol : mo->volumes) { - if (vol->type() != ModelVolumeType::MODEL_PART) - continue; - TriangleMesh vol_ch = vol->get_convex_hull(); - vol_ch.transform(vol->get_matrix()); - ch.merge(vol_ch); - } - ch = ch.convex_hull_3d(); - m_planes.clear(); - const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true); - - // Following constants are used for discarding too small polygons. - const float minimal_area = 5.f; // in square mm (world coordinates) - const float minimal_side = 1.f; // mm - - // Now we'll go through all the facets and append Points of facets sharing the same normal. - // This part is still performed in mesh coordinate system. - const int num_of_facets = ch.facets_count(); - const std::vector face_normals = its_face_normals(ch.its); - const std::vector face_neighbors = its_face_neighbors(ch.its); - std::vector facet_queue(num_of_facets, 0); - std::vector facet_visited(num_of_facets, false); - int facet_queue_cnt = 0; - const stl_normal* normal_ptr = nullptr; - int facet_idx = 0; - while (1) { - // Find next unvisited triangle: - for (; facet_idx < num_of_facets; ++ facet_idx) - if (!facet_visited[facet_idx]) { - facet_queue[facet_queue_cnt ++] = facet_idx; - facet_visited[facet_idx] = true; - normal_ptr = &face_normals[facet_idx]; - m_planes.emplace_back(); - break; - } - if (facet_idx == num_of_facets) - break; // Everything was visited already - - while (facet_queue_cnt > 0) { - int facet_idx = facet_queue[-- facet_queue_cnt]; - const stl_normal& this_normal = face_normals[facet_idx]; - if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) { - const Vec3i face = ch.its.indices[facet_idx]; - for (int j=0; j<3; ++j) - m_planes.back().vertices.emplace_back(ch.its.vertices[face[j]].cast()); - - facet_visited[facet_idx] = true; - for (int j = 0; j < 3; ++ j) - if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && ! facet_visited[neighbor_idx]) - facet_queue[facet_queue_cnt ++] = neighbor_idx; - } - } - m_planes.back().normal = normal_ptr->cast(); - - Pointf3s& verts = m_planes.back().vertices; - // Now we'll transform all the points into world coordinates, so that the areas, angles and distances - // make real sense. - verts = transform(verts, inst_matrix); - - // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway): - if (verts.size() == 3 && - ((verts[0] - verts[1]).norm() < minimal_side - || (verts[0] - verts[2]).norm() < minimal_side - || (verts[1] - verts[2]).norm() < minimal_side)) - m_planes.pop_back(); - } - - // Let's prepare transformation of the normal vector from mesh to instance coordinates. - Geometry::Transformation t(inst_matrix); - Vec3d scaling = t.get_scaling_factor(); - t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); - - // Now we'll go through all the polygons, transform the points into xy plane to process them: - for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { - Pointf3s& polygon = m_planes[polygon_id].vertices; - const Vec3d& normal = m_planes[polygon_id].normal; - - // transform the normal according to the instance matrix: - Vec3d normal_transformed = t.get_matrix() * normal; - - // We are going to rotate about z and y to flatten the plane - Eigen::Quaterniond q; - Transform3d m = Transform3d::Identity(); - m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix(); - polygon = transform(polygon, m); - - // Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since - // it works in fixed point representation, we will rescale the polygon to avoid overflows. - // And yes, it is a nasty thing to do. Whoever has time is free to refactor. - Vec3d bb_size = BoundingBoxf3(polygon).size(); - float sf = std::min(1./bb_size(0), 1./bb_size(1)); - Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); - polygon = transform(polygon, tr); - polygon = Slic3r::Geometry::convex_hull(polygon); - polygon = transform(polygon, tr.inverse()); - - // Calculate area of the polygons and discard ones that are too small - float& area = m_planes[polygon_id].area; - area = 0.f; - for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula - area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1); - area = 0.5f * std::abs(area); - - bool discard = false; - if (area < minimal_area) - discard = true; - else { - // We also check the inner angles and discard polygons with angles smaller than the following threshold - const double angle_threshold = ::cos(10.0 * (double)PI / 180.0); - - for (unsigned int i = 0; i < polygon.size(); ++i) { - const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1]; - const Vec3d& curr = polygon[i]; - const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1]; - - if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) { - discard = true; - break; - } - } - } - - if (discard) { - m_planes[polygon_id--] = std::move(m_planes.back()); - m_planes.pop_back(); - continue; - } - - // We will shrink the polygon a little bit so it does not touch the object edges: - Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); - centroid /= (double)polygon.size(); - for (auto& vertex : polygon) - vertex = 0.9f*vertex + 0.1f*centroid; - - // Polygon is now simple and convex, we'll round the corners to make them look nicer. - // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex - // towards their average (controlled by 'aggressivity'). This is repeated k times. - // In next iterations, the neighbours are not always taken at the middle (to increase the - // rounding effect at the corners, where we need it most). - const unsigned int k = 10; // number of iterations - const float aggressivity = 0.2f; // agressivity - const unsigned int N = polygon.size(); - std::vector> neighbours; - if (k != 0) { - Pointf3s points_out(2*k*N); // vector long enough to store the future vertices - for (unsigned int j=0; jvolumes) { - m_volumes_matrices.push_back(vol->get_matrix()); - m_volumes_types.push_back(vol->type()); - } - m_first_instance_scale = mo->instances.front()->get_scaling_factor(); - m_first_instance_mirror = mo->instances.front()->get_mirror(); - m_old_model_object = mo; - - // And finally create respective VBOs. The polygon is convex with - // the vertices in order, so triangulation is trivial. - for (auto& plane : m_planes) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::TriangleFan, GLModel::Geometry::EVertexLayout::P3N3 }; - init_data.reserve_vertices(plane.vertices.size()); - init_data.reserve_indices(plane.vertices.size()); - // vertices + indices - for (size_t i = 0; i < plane.vertices.size(); ++i) { - init_data.add_vertex((Vec3f)plane.vertices[i].cast(), (Vec3f)plane.normal.cast()); - init_data.add_index((unsigned int)i); - } - plane.vbo.init_from(std::move(init_data)); -#else - plane.vbo.reserve(plane.vertices.size()); - for (const auto& vert : plane.vertices) - plane.vbo.push_geometry(vert, plane.normal); - for (size_t i=1; iselection_info()->model_object(); - if (m_state != On || ! mo || mo->instances.empty()) - return false; - - if (! m_planes_valid || mo != m_old_model_object - || mo->volumes.size() != m_volumes_matrices.size()) - return true; - - // We want to recalculate when the scale changes - some planes could (dis)appear. - if (! mo->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale) - || ! mo->instances.front()->get_mirror().isApprox(m_first_instance_mirror)) - return true; - - for (unsigned int i=0; i < mo->volumes.size(); ++i) - if (! mo->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i]) - || mo->volumes[i]->type() != m_volumes_types[i]) - return true; - - return false; -} - -} // namespace GUI -} // namespace Slic3r +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoFlatten.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#if ENABLE_LEGACY_OPENGL_REMOVAL +#include "slic3r/GUI/GUI_App.hpp" +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES +#include "slic3r/GUI/Plater.hpp" +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" + +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Model.hpp" + +#include + +#include + +namespace Slic3r { +namespace GUI { + +static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f }; +static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f }; + +GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +{} + +bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event) +{ + if (mouse_event.Moving()) { + // only for sure + m_mouse_left_down = false; + return false; + } + if (mouse_event.LeftDown()) { + if (m_hover_id != -1) { + m_mouse_left_down = true; + Selection &selection = m_parent.get_selection(); + if (selection.is_single_full_instance()) { + // Rotate the object so the normal points downward: + selection.flattening_rotate(m_planes[m_hover_id].normal); + m_parent.do_rotate(L("Gizmo-Place on Face")); + } + return true; + } + + // fix: prevent restart gizmo when reselect object + // take responsibility for left up + if (m_parent.get_first_hover_volume_idx() >= 0) m_mouse_left_down = true; + + } else if (mouse_event.LeftUp()) { + if (m_mouse_left_down) { + // responsible for mouse left up after selecting plane + m_mouse_left_down = false; + return true; + } + } else if (mouse_event.Leaving()) { + m_mouse_left_down = false; + } + return false; +} + +void GLGizmoFlatten::data_changed() +{ + const Selection & selection = m_parent.get_selection(); + const ModelObject *model_object = nullptr; + if (selection.is_single_full_instance() || + selection.is_from_single_object() ) { + model_object = selection.get_model()->objects[selection.get_object_idx()]; + } + set_flattening_data(model_object); +} + +bool GLGizmoFlatten::on_init() +{ + m_shortcut_key = WXK_CONTROL_F; + return true; +} + +void GLGizmoFlatten::on_set_state() +{ +} + +CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const +{ + return CommonGizmosDataID::SelectionInfo; +} + +std::string GLGizmoFlatten::on_get_name() const +{ + return _u8L("Place on face"); +} + +bool GLGizmoFlatten::on_is_activable() const +{ + // This is assumed in GLCanvas3D::do_rotate, do not change this + // without updating that function too. + return m_parent.get_selection().is_single_full_instance(); +} + +void GLGizmoFlatten::on_render() +{ + const Selection& selection = m_parent.get_selection(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glEnable(GL_BLEND)); + + if (selection.is_single_full_instance()) { + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_model_matrix = camera.get_view_matrix() * + Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); + glsafe(::glMultMatrixd(m.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + if (this->is_plane_update_necessary()) + update_planes(); + for (int i = 0; i < (int)m_planes.size(); ++i) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_planes[i].vbo.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); + m_planes[i].vbo.render(); +#else + glsafe(::glColor4fv(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR.data() : DEFAULT_PLANE_COLOR.data())); + if (m_planes[i].vbo.has_VBOs()) + m_planes[i].vbo.render(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLGizmoFlatten::on_render_for_picking() +{ + const Selection& selection = m_parent.get_selection(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glDisable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_BLEND)); + + if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) { + const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d view_model_matrix = camera.get_view_matrix() * + Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); + glsafe(::glMultMatrixd(m.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + if (this->is_plane_update_necessary()) + update_planes(); + for (int i = 0; i < (int)m_planes.size(); ++i) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_planes[i].vbo.set_color(picking_color_component(i)); +#else + glsafe(::glColor4fv(picking_color_component(i).data())); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + m_planes[i].vbo.render(); + } +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + + glsafe(::glEnable(GL_CULL_FACE)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) +{ + if (model_object != m_old_model_object) { + m_planes.clear(); + m_planes_valid = false; + } +} + +void GLGizmoFlatten::update_planes() +{ + const ModelObject* mo = m_c->selection_info()->model_object(); + TriangleMesh ch; + for (const ModelVolume* vol : mo->volumes) { + if (vol->type() != ModelVolumeType::MODEL_PART) + continue; + TriangleMesh vol_ch = vol->get_convex_hull(); + vol_ch.transform(vol->get_matrix()); + ch.merge(vol_ch); + } + ch = ch.convex_hull_3d(); + m_planes.clear(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d inst_matrix = mo->instances.front()->get_matrix_no_offset(); +#else + const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + + // Following constants are used for discarding too small polygons. + const float minimal_area = 5.f; // in square mm (world coordinates) + const float minimal_side = 1.f; // mm + + // Now we'll go through all the facets and append Points of facets sharing the same normal. + // This part is still performed in mesh coordinate system. + const int num_of_facets = ch.facets_count(); + const std::vector face_normals = its_face_normals(ch.its); + const std::vector face_neighbors = its_face_neighbors(ch.its); + std::vector facet_queue(num_of_facets, 0); + std::vector facet_visited(num_of_facets, false); + int facet_queue_cnt = 0; + const stl_normal* normal_ptr = nullptr; + int facet_idx = 0; + while (1) { + // Find next unvisited triangle: + for (; facet_idx < num_of_facets; ++ facet_idx) + if (!facet_visited[facet_idx]) { + facet_queue[facet_queue_cnt ++] = facet_idx; + facet_visited[facet_idx] = true; + normal_ptr = &face_normals[facet_idx]; + m_planes.emplace_back(); + break; + } + if (facet_idx == num_of_facets) + break; // Everything was visited already + + while (facet_queue_cnt > 0) { + int facet_idx = facet_queue[-- facet_queue_cnt]; + const stl_normal& this_normal = face_normals[facet_idx]; + if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) { + const Vec3i face = ch.its.indices[facet_idx]; + for (int j=0; j<3; ++j) + m_planes.back().vertices.emplace_back(ch.its.vertices[face[j]].cast()); + + facet_visited[facet_idx] = true; + for (int j = 0; j < 3; ++ j) + if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && ! facet_visited[neighbor_idx]) + facet_queue[facet_queue_cnt ++] = neighbor_idx; + } + } + m_planes.back().normal = normal_ptr->cast(); + + Pointf3s& verts = m_planes.back().vertices; + // Now we'll transform all the points into world coordinates, so that the areas, angles and distances + // make real sense. + verts = transform(verts, inst_matrix); + + // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway): + if (verts.size() == 3 && + ((verts[0] - verts[1]).norm() < minimal_side + || (verts[0] - verts[2]).norm() < minimal_side + || (verts[1] - verts[2]).norm() < minimal_side)) + m_planes.pop_back(); + } + + // Let's prepare transformation of the normal vector from mesh to instance coordinates. + Geometry::Transformation t(inst_matrix); + Vec3d scaling = t.get_scaling_factor(); + t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); + + // Now we'll go through all the polygons, transform the points into xy plane to process them: + for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { + Pointf3s& polygon = m_planes[polygon_id].vertices; + const Vec3d& normal = m_planes[polygon_id].normal; + + // transform the normal according to the instance matrix: + Vec3d normal_transformed = t.get_matrix() * normal; + + // We are going to rotate about z and y to flatten the plane + Eigen::Quaterniond q; + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(normal_transformed, Vec3d::UnitZ()).toRotationMatrix(); + polygon = transform(polygon, m); + + // Now to remove the inner points. We'll misuse Geometry::convex_hull for that, but since + // it works in fixed point representation, we will rescale the polygon to avoid overflows. + // And yes, it is a nasty thing to do. Whoever has time is free to refactor. + Vec3d bb_size = BoundingBoxf3(polygon).size(); + float sf = std::min(1./bb_size(0), 1./bb_size(1)); + Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); + polygon = transform(polygon, tr); + polygon = Slic3r::Geometry::convex_hull(polygon); + polygon = transform(polygon, tr.inverse()); + + // Calculate area of the polygons and discard ones that are too small + float& area = m_planes[polygon_id].area; + area = 0.f; + for (unsigned int i = 0; i < polygon.size(); i++) // Shoelace formula + area += polygon[i](0)*polygon[i + 1 < polygon.size() ? i + 1 : 0](1) - polygon[i + 1 < polygon.size() ? i + 1 : 0](0)*polygon[i](1); + area = 0.5f * std::abs(area); + + bool discard = false; + if (area < minimal_area) + discard = true; + else { + // We also check the inner angles and discard polygons with angles smaller than the following threshold + const double angle_threshold = ::cos(10.0 * (double)PI / 180.0); + + for (unsigned int i = 0; i < polygon.size(); ++i) { + const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1]; + const Vec3d& curr = polygon[i]; + const Vec3d& next = polygon[(i == polygon.size() - 1) ? 0 : i + 1]; + + if ((prec - curr).normalized().dot((next - curr).normalized()) > angle_threshold) { + discard = true; + break; + } + } + } + + if (discard) { + m_planes[polygon_id--] = std::move(m_planes.back()); + m_planes.pop_back(); + continue; + } + + // We will shrink the polygon a little bit so it does not touch the object edges: + Vec3d centroid = std::accumulate(polygon.begin(), polygon.end(), Vec3d(0.0, 0.0, 0.0)); + centroid /= (double)polygon.size(); + for (auto& vertex : polygon) + vertex = 0.9f*vertex + 0.1f*centroid; + + // Polygon is now simple and convex, we'll round the corners to make them look nicer. + // The algorithm takes a vertex, calculates middles of respective sides and moves the vertex + // towards their average (controlled by 'aggressivity'). This is repeated k times. + // In next iterations, the neighbours are not always taken at the middle (to increase the + // rounding effect at the corners, where we need it most). + const unsigned int k = 10; // number of iterations + const float aggressivity = 0.2f; // agressivity + const unsigned int N = polygon.size(); + std::vector> neighbours; + if (k != 0) { + Pointf3s points_out(2*k*N); // vector long enough to store the future vertices + for (unsigned int j=0; jvolumes) { + m_volumes_matrices.push_back(vol->get_matrix()); + m_volumes_types.push_back(vol->type()); + } + m_first_instance_scale = mo->instances.front()->get_scaling_factor(); + m_first_instance_mirror = mo->instances.front()->get_mirror(); + m_old_model_object = mo; + + // And finally create respective VBOs. The polygon is convex with + // the vertices in order, so triangulation is trivial. + for (auto& plane : m_planes) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::TriangleFan, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_vertices(plane.vertices.size()); + init_data.reserve_indices(plane.vertices.size()); + // vertices + indices + for (size_t i = 0; i < plane.vertices.size(); ++i) { + init_data.add_vertex((Vec3f)plane.vertices[i].cast(), (Vec3f)plane.normal.cast()); + init_data.add_index((unsigned int)i); + } + plane.vbo.init_from(std::move(init_data)); +#else + plane.vbo.reserve(plane.vertices.size()); + for (const auto& vert : plane.vertices) + plane.vbo.push_geometry(vert, plane.normal); + for (size_t i=1; iselection_info()->model_object(); + if (m_state != On || ! mo || mo->instances.empty()) + return false; + + if (! m_planes_valid || mo != m_old_model_object + || mo->volumes.size() != m_volumes_matrices.size()) + return true; + + // We want to recalculate when the scale changes - some planes could (dis)appear. + if (! mo->instances.front()->get_scaling_factor().isApprox(m_first_instance_scale) + || ! mo->instances.front()->get_mirror().isApprox(m_first_instance_mirror)) + return true; + + for (unsigned int i=0; i < mo->volumes.size(); ++i) + if (! mo->volumes[i]->get_matrix().isApprox(m_volumes_matrices[i]) + || mo->volumes[i]->type() != m_volumes_types[i]) + return true; + + return false; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 88b319f25..a60da3591 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -1,988 +1,992 @@ -#include "GLGizmoHollow.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/Camera.hpp" -#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" - -#include - -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/GUI_ObjectSettings.hpp" -#include "slic3r/GUI/GUI_ObjectList.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "libslic3r/PresetBundle.hpp" - -#include "libslic3r/Model.hpp" - - -namespace Slic3r { -namespace GUI { - -GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -{ -} - - -bool GLGizmoHollow::on_init() -{ - m_shortcut_key = WXK_CONTROL_H; - m_desc["enable"] = _(L("Hollow this object")); - m_desc["preview"] = _(L("Preview hollowed and drilled model")); - m_desc["offset"] = _(L("Offset")) + ": "; - m_desc["quality"] = _(L("Quality")) + ": "; - m_desc["closing_distance"] = _(L("Closing distance")) + ": "; - m_desc["hole_diameter"] = _(L("Hole diameter")) + ": "; - m_desc["hole_depth"] = _(L("Hole depth")) + ": "; - m_desc["remove_selected"] = _(L("Remove selected holes")); - m_desc["remove_all"] = _(L("Remove all holes")); - m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": "; - m_desc["reset_direction"] = _(L("Reset direction")); - m_desc["show_supports"] = _(L("Show supports")); - - return true; -} - -void GLGizmoHollow::data_changed() -{ - if (! m_c->selection_info()) - return; - - const ModelObject* mo = m_c->selection_info()->model_object(); - if (m_state == On && mo) { - if (m_old_mo_id != mo->id()) { - reload_cache(); - m_old_mo_id = mo->id(); - } - if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) - m_holes_in_drilled_mesh = mo->sla_drain_holes; - } -} - - - -void GLGizmoHollow::on_render() -{ - if (!m_cylinder.is_initialized()) - m_cylinder.init_from(its_make_cylinder(1.0, 1.0)); - - const Selection& selection = m_parent.get_selection(); - const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info(); - - // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off - if (m_state == On - && (sel_info->model_object() != selection.get_model()->objects[selection.get_object_idx()] - || sel_info->get_active_instance() != selection.get_instance_idx())) { - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); - return; - } - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - if (selection.is_from_single_instance()) - render_points(selection, false); - - m_selection_rectangle.render(m_parent); - m_c->object_clipper()->render_cut(); - m_c->supports_clipper()->render_cut(); - - glsafe(::glDisable(GL_BLEND)); -} - - -void GLGizmoHollow::on_render_for_picking() -{ - const Selection& selection = m_parent.get_selection(); -//#if ENABLE_RENDER_PICKING_PASS -// m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); -//#endif - - glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, true); -} - -void GLGizmoHollow::render_points(const Selection& selection, bool picking) -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - shader->start_using(); - ScopeGuard guard([shader]() { shader->stop_using(); }); -#else - GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); - if (shader) - shader->start_using(); - ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - Geometry::Transformation trafo = vol->get_instance_transformation() * vol->get_volume_transformation(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); - const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix(); - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d& view_matrix = camera.get_view_matrix(); - const Transform3d& projection_matrix = camera.get_projection_matrix(); - - shader->set_uniform("projection_matrix", projection_matrix); -#else - const Transform3d& instance_scaling_matrix_inverse = trafo.get_matrix(true, true, false, true).inverse(); - const Transform3d& instance_matrix = trafo.get_matrix(); - - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); - glsafe(::glMultMatrixd(instance_matrix.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - ColorRGBA render_color; - const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - const size_t cache_size = drain_holes.size(); - - for (size_t i = 0; i < cache_size; ++i) { - const sla::DrainHole& drain_hole = drain_holes[i]; - const bool point_selected = m_selected[i]; - - if (is_mesh_point_clipped(drain_hole.pos.cast())) - continue; - - // First decide about the color of the point. - if (picking) - render_color = picking_color_component(i); - else { - if (size_t(m_hover_id) == i) - render_color = ColorRGBA::CYAN(); - else if (m_c->hollowed_mesh() && - i < m_c->hollowed_mesh()->get_drainholes().size() && - m_c->hollowed_mesh()->get_drainholes()[i].failed) { - render_color = { 1.0f, 0.0f, 0.0f, 0.5f }; - } - else // neither hover nor picking - render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_cylinder.set_color(render_color); -#else - const_cast(&m_cylinder)->set_color(-1, render_color); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z())); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (vol->is_left_handed()) - glFrontFace(GL_CW); - - // Matrices set, we can render the point mark now. - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); - const Eigen::AngleAxisd aa(q); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = view_matrix * instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) * - Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); - glsafe(::glTranslated(0., 0., -drain_hole.height)); - glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_cylinder.render(); - - if (vol->is_left_handed()) - glFrontFace(GL_CCW); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -} - -bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const -{ - if (m_c->object_clipper()->get_position() == 0.) - return false; - - auto sel_info = m_c->selection_info(); - int active_inst = m_c->selection_info()->get_active_instance(); - const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; - const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix(); - - Vec3d transformed_point = trafo * point; - transformed_point(2) += sel_info->get_sla_shift(); - return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); -} - - - -// 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 GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) -{ - if (! m_c->raycaster()->raycaster()) - return false; - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - - double clp_dist = m_c->object_clipper()->get_position(); - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - - // The raycaster query - Vec3f hit; - Vec3f normal; - if (m_c->raycaster()->raycaster()->unproject_on_mesh( - mouse_pos, - trafo.get_matrix(), - camera, - hit, - normal, - clp_dist != 0. ? clp : nullptr)) - { - if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) { - // in this case the raycaster sees the hollowed and drilled mesh. - // if the point lies on the surface created by the hole, we want - // to ignore it. - for (const sla::DrainHole& hole : m_holes_in_drilled_mesh) { - sla::DrainHole outer(hole); - outer.radius *= 1.001f; - outer.height *= 1.001f; - if (outer.is_inside(hit)) - return false; - } - } - - // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit, normal); - return true; - } - else - return false; -} - -// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. -// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is -// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo -// concludes that the event was not intended for it, it should return false. -bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) -{ - ModelObject* mo = m_c->selection_info()->model_object(); - int active_inst = m_c->selection_info()->get_active_instance(); - - - // left down with shift - show the selection rectangle: - if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { - if (m_hover_id == -1) { - if (shift_down || alt_down) { - m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); - } - } - else { - if (m_selected[m_hover_id]) - unselect_point(m_hover_id); - else { - if (!alt_down) - select_point(m_hover_id); - } - } - - return true; - } - - // left down without selection rectangle - place point on the mesh: - if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { - // If any point is in hover state, this should initiate its move - return control back to GLCanvas: - if (m_hover_id != -1) - return false; - - // If there is some selection, don't add new point and deselect everything instead. - if (m_selection_empty) { - std::pair pos_and_normal; - if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add drainage hole"))); - - mo->sla_drain_holes.emplace_back(pos_and_normal.first, - -pos_and_normal.second, m_new_hole_radius, m_new_hole_height); - m_selected.push_back(false); - assert(m_selected.size() == mo->sla_drain_holes.size()); - m_parent.set_as_dirty(); - m_wait_for_up_event = true; - } - else - return false; - } - else - select_point(NoPoints); - - return true; - } - - // left up with selection rectangle - select points inside the rectangle: - if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { - // Is this a selection or deselection rectangle? - GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); - - // First collect positions of all the points in world coordinates. - Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - std::vector points; - for (unsigned int i=0; isla_drain_holes.size(); ++i) - points.push_back(trafo.get_matrix() * mo->sla_drain_holes[i].pos.cast()); - - // Now ask the rectangle which of the points are inside. - std::vector points_inside; - std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); - for (size_t idx : points_idxs) - points_inside.push_back(points[idx].cast()); - - // Only select/deselect points that are actually visible - for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( - trafo, wxGetApp().plater()->get_camera(), points_inside, - m_c->object_clipper()->get_clipping_plane())) - { - if (rectangle_status == GLSelectionRectangle::EState::Deselect) - unselect_point(points_idxs[idx]); - else - select_point(points_idxs[idx]); - } - return true; - } - - // left up with no selection rectangle - if (action == SLAGizmoEventType::LeftUp) { - if (m_wait_for_up_event) { - m_wait_for_up_event = false; - return true; - } - } - - // dragging the selection rectangle: - if (action == SLAGizmoEventType::Dragging) { - if (m_wait_for_up_event) - return true; // point has been placed and the button not released yet - // this prevents GLCanvas from starting scene rotation - - if (m_selection_rectangle.is_dragging()) { - m_selection_rectangle.dragging(mouse_position); - return true; - } - - return false; - } - - if (action == SLAGizmoEventType::Delete) { - // delete key pressed - delete_selected_points(); - return true; - } - - if (action == SLAGizmoEventType::RightDown) { - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - delete_selected_points(); - return true; - } - return false; - } - - if (action == SLAGizmoEventType::SelectAll) { - select_point(AllPoints); - return true; - } - - if (action == SLAGizmoEventType::MouseWheelUp && control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - - if (action == SLAGizmoEventType::MouseWheelDown && control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::max(0., pos - 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - - if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); - return true; - } - - return false; -} - -void GLGizmoHollow::delete_selected_points() -{ - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete drainage hole"))); - sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - - for (unsigned int idx=0; idx wx/types.h - Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); - Vec2d mouse_pos = mouse_coord.cast(); - - static bool pending_right_up = false; - if (mouse_event.LeftDown()) { - bool control_down = mouse_event.CmdDown(); - bool grabber_contains_mouse = (get_hover_id() != -1); - if ((!control_down || grabber_contains_mouse) && - gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) - // the gizmo got the event and took some action, there is no need - // to do anything more - return true; - } else if (mouse_event.Dragging()) { - if (m_parent.get_move_volume_id() != -1) - // don't allow dragging objects with the Sla gizmo on - return true; - - bool control_down = mouse_event.CmdDown(); - if (control_down) { - // CTRL has been pressed while already dragging -> stop current action - if (mouse_event.LeftIsDown()) - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); - else if (mouse_event.RightIsDown()) { - pending_right_up = false; - } - } else if(gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) { - // the gizmo got the event and took some action, no need to do - // anything more here - m_parent.set_as_dirty(); - return true; - } - } else if (mouse_event.LeftUp()) { - if (!m_parent.is_mouse_dragging()) { - bool control_down = mouse_event.CmdDown(); - // in case gizmo is selected, we just pass the LeftUp event - // and stop processing - neither object moving or selecting is - // suppressed in that case - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); - return true; - } - } else if (mouse_event.RightDown()) { - if (m_parent.get_selection().get_object_idx() != -1 && - gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) { - // we need to set the following right up as processed to avoid showing - // the context menu if the user release the mouse over the object - pending_right_up = true; - // event was taken care of by the SlaSupports gizmo - return true; - } - } else if (mouse_event.RightUp()) { - if (pending_right_up) { - pending_right_up = false; - return true; - } - } - return false; -} - -void GLGizmoHollow::hollow_mesh(bool postpone_error_messages) -{ - wxGetApp().CallAfter([this, postpone_error_messages]() { - wxGetApp().plater()->reslice_SLA_hollowing( - *m_c->selection_info()->model_object(), postpone_error_messages); - }); -} - - -std::vector> -GLGizmoHollow::get_config_options(const std::vector& keys) const -{ - std::vector> out; - const ModelObject* mo = m_c->selection_info()->model_object(); - - if (! mo) - return out; - - const DynamicPrintConfig& object_cfg = mo->config.get(); - const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - std::unique_ptr default_cfg = nullptr; - - for (const std::string& key : keys) { - if (object_cfg.has(key)) - out.emplace_back(object_cfg.option(key), &object_cfg.def()->options.at(key)); // at() needed for const map - else - if (print_cfg.has(key)) - out.emplace_back(print_cfg.option(key), &print_cfg.def()->options.at(key)); - else { // we must get it from defaults - if (default_cfg == nullptr) - default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); - out.emplace_back(default_cfg->option(key), &default_cfg->def()->options.at(key)); - } - } - - return out; -} - - -void GLGizmoHollow::on_render_input_window(float x, float y, float bottom_limit) -{ - ModelObject* mo = m_c->selection_info()->model_object(); - if (! mo) - return; - - bool first_run = true; // This is a hack to redraw the button when all points are removed, - // so it is not delayed until the background process finishes. - - ConfigOptionMode current_mode = wxGetApp().get_mode(); - - std::vector opts_keys = {"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"}; - auto opts = get_config_options(opts_keys); - auto* offset_cfg = static_cast(opts[0].first); - float offset = offset_cfg->value; - double offset_min = opts[0].second->min; - double offset_max = opts[0].second->max; - - auto* quality_cfg = static_cast(opts[1].first); - float quality = quality_cfg->value; - double quality_min = opts[1].second->min; - double quality_max = opts[1].second->max; - ConfigOptionMode quality_mode = opts[1].second->mode; - - auto* closing_d_cfg = static_cast(opts[2].first); - float closing_d = closing_d_cfg->value; - double closing_d_min = opts[2].second->min; - double closing_d_max = opts[2].second->max; - ConfigOptionMode closing_d_mode = opts[2].second->mode; - - m_desc["offset"] = _(opts[0].second->label) + ":"; - m_desc["quality"] = _(opts[1].second->label) + ":"; - m_desc["closing_distance"] = _(opts[2].second->label) + ":"; - - -RENDER_AGAIN: - const float approx_height = m_imgui->scaled(20.0f); - y = std::min(y, bottom_limit - approx_height); - m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, - m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(0.5f); - - const float settings_sliders_left = - std::max(std::max({m_imgui->calc_text_size(m_desc.at("offset")).x, - m_imgui->calc_text_size(m_desc.at("quality")).x, - m_imgui->calc_text_size(m_desc.at("closing_distance")).x, - m_imgui->calc_text_size(m_desc.at("hole_diameter")).x, - m_imgui->calc_text_size(m_desc.at("hole_depth")).x}) + m_imgui->scaled(0.5f), clipping_slider_left); - - const float diameter_slider_left = settings_sliders_left; //m_imgui->calc_text_size(m_desc.at("hole_diameter")).x + m_imgui->scaled(1.f); - const float minimal_slider_width = m_imgui->scaled(4.f); - - const float button_preview_width = m_imgui->calc_button_size(m_desc.at("preview")).x; - - float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left}); - window_width = std::max(window_width, button_preview_width); - - if (m_imgui->button(m_desc["preview"])) - hollow_mesh(); - - bool config_changed = false; - - ImGui::Separator(); - - { - auto opts = get_config_options({"hollowing_enable"}); - m_enable_hollowing = static_cast(opts[0].first)->value; - if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) { - mo->config.set("hollowing_enable", m_enable_hollowing); - wxGetApp().obj_list()->update_and_show_object_settings_item(); - config_changed = true; - } - } - - m_imgui->disabled_begin(! m_enable_hollowing); - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("offset")); - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); - ImGui::PushItemWidth(window_width - settings_sliders_left); - m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm", 1.0f, true, _L(opts[0].second->tooltip)); - - bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider - bool slider_edited =m_imgui->get_last_slider_status().edited; // someone is dragging the slider - bool slider_released =m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider - - if (current_mode >= quality_mode) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("quality")); - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); - m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f", 1.0f, true, _L(opts[1].second->tooltip)); - - slider_clicked |= m_imgui->get_last_slider_status().clicked; - slider_edited |= m_imgui->get_last_slider_status().edited; - slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; - } - - if (current_mode >= closing_d_mode) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("closing_distance")); - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); - m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm", 1.0f, true, _L(opts[2].second->tooltip)); - - slider_clicked |= m_imgui->get_last_slider_status().clicked; - slider_edited |= m_imgui->get_last_slider_status().edited; - slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; - } - - if (slider_clicked) { - m_offset_stash = offset; - m_quality_stash = quality; - m_closing_d_stash = closing_d; - } - if (slider_edited || slider_released) { - if (slider_released) { - mo->config.set("hollowing_min_thickness", m_offset_stash); - mo->config.set("hollowing_quality", m_quality_stash); - mo->config.set("hollowing_closing_distance", m_closing_d_stash); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Hollowing parameter change"))); - } - mo->config.set("hollowing_min_thickness", offset); - mo->config.set("hollowing_quality", quality); - mo->config.set("hollowing_closing_distance", closing_d); - if (slider_released) { - wxGetApp().obj_list()->update_and_show_object_settings_item(); - config_changed = true; - } - } - - m_imgui->disabled_end(); - - bool force_refresh = false; - bool remove_selected = false; - bool remove_all = false; - - ImGui::Separator(); - - float diameter_upper_cap = 60.; - if (m_new_hole_radius * 2.f > diameter_upper_cap) - m_new_hole_radius = diameter_upper_cap / 2.f; - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("hole_diameter")); - ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); - ImGui::PushItemWidth(window_width - diameter_slider_left); - - float diam = 2.f * m_new_hole_radius; - m_imgui->slider_float("##hole_diameter", &diam, 1.f, 25.f, "%.1f mm", 1.f, false); - // Let's clamp the value (which could have been entered by keyboard) to a larger range - // than the slider. This allows entering off-scale values and still protects against - //complete non-sense. - diam = std::clamp(diam, 0.1f, diameter_upper_cap); - m_new_hole_radius = diam / 2.f; - bool clicked = m_imgui->get_last_slider_status().clicked; - bool edited = m_imgui->get_last_slider_status().edited; - bool deactivated = m_imgui->get_last_slider_status().deactivated_after_edit; - - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc["hole_depth"]); - ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); - m_imgui->slider_float("##hole_depth", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false); - // Same as above: - m_new_hole_height = std::clamp(m_new_hole_height, 0.f, 100.f); - - clicked |= m_imgui->get_last_slider_status().clicked; - edited |= m_imgui->get_last_slider_status().edited; - deactivated |= m_imgui->get_last_slider_status().deactivated_after_edit;; - - // Following is a nasty way to: - // - save the initial value of the slider before one starts messing with it - // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene - // - take correct undo/redo snapshot after the user is done with moving the slider - if (! m_selection_empty) { - if (clicked) { - m_holes_stash = mo->sla_drain_holes; - } - if (edited) { - for (size_t idx=0; idxsla_drain_holes[idx].radius = m_new_hole_radius; - mo->sla_drain_holes[idx].height = m_new_hole_height; - } - } - if (deactivated) { - // momentarily restore the old value to take snapshot - sla::DrainHoles new_holes = mo->sla_drain_holes; - mo->sla_drain_holes = m_holes_stash; - float backup_rad = m_new_hole_radius; - float backup_hei = m_new_hole_height; - for (size_t i=0; isla_drain_holes = new_holes; - } - } - - m_imgui->disabled_begin(m_selection_empty); - remove_selected = m_imgui->button(m_desc.at("remove_selected")); - m_imgui->disabled_end(); - - m_imgui->disabled_begin(mo->sla_drain_holes.empty()); - remove_all = m_imgui->button(m_desc.at("remove_all")); - m_imgui->disabled_end(); - - // Following is rendered in both editing and non-editing mode: - // m_imgui->text(""); - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); - }); - } - } - - ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); - ImGui::PushItemWidth(window_width - settings_sliders_left); - float clp_dist = m_c->object_clipper()->get_position(); - if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) - m_c->object_clipper()->set_position(clp_dist, true); - - // make sure supports are shown/hidden as appropriate - bool show_sups = m_c->instances_hider()->are_supports_shown(); - if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) { - m_c->instances_hider()->show_supports(show_sups); - force_refresh = true; - } - - m_imgui->end(); - - - if (remove_selected || remove_all) { - force_refresh = false; - m_parent.set_as_dirty(); - - if (remove_all) { - select_point(AllPoints); - delete_selected_points(); - } - if (remove_selected) - delete_selected_points(); - - if (first_run) { - first_run = false; - goto RENDER_AGAIN; - } - } - - if (force_refresh) - m_parent.set_as_dirty(); - - if (config_changed) - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); -} - -bool GLGizmoHollow::on_is_activable() const -{ - const Selection& selection = m_parent.get_selection(); - - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA - || !selection.is_from_single_instance()) - return false; - - // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. - const Selection::IndicesList& list = selection.get_volume_idxs(); - for (const auto& idx : list) - if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) - return false; - - return true; -} - -bool GLGizmoHollow::on_is_selectable() const -{ - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); -} - -std::string GLGizmoHollow::on_get_name() const -{ - return _u8L("Hollow and drill"); -} - - -CommonGizmosDataID GLGizmoHollow::on_get_requirements() const -{ - return CommonGizmosDataID( - int(CommonGizmosDataID::SelectionInfo) - | int(CommonGizmosDataID::InstancesHider) - | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); -} - - -void GLGizmoHollow::on_set_state() -{ - if (m_state == m_old_state) - return; - - if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); - m_old_state = m_state; -} - - - -void GLGizmoHollow::on_start_dragging() -{ - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - m_hole_before_drag = m_c->selection_info()->model_object()->sla_drain_holes[m_hover_id].pos; - } - else - m_hole_before_drag = Vec3f::Zero(); -} - - -void GLGizmoHollow::on_stop_dragging() -{ - sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - if (m_hover_id != -1) { - Vec3f backup = drain_holes[m_hover_id].pos; - - if (m_hole_before_drag != Vec3f::Zero() // some point was touched - && backup != m_hole_before_drag) // and it was moved, not just selected - { - drain_holes[m_hover_id].pos = m_hole_before_drag; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move drainage hole"))); - drain_holes[m_hover_id].pos = backup; - } - } - m_hole_before_drag = Vec3f::Zero(); -} - - -void GLGizmoHollow::on_dragging(const UpdateData &data) -{ - assert(m_hover_id != -1); - std::pair pos_and_normal; - if (!unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) - return; - sla::DrainHoles &drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - drain_holes[m_hover_id].pos = pos_and_normal.first; - drain_holes[m_hover_id].normal = -pos_and_normal.second; -} - - -void GLGizmoHollow::on_load(cereal::BinaryInputArchive& ar) -{ - ar(m_new_hole_radius, - m_new_hole_height, - m_selected, - m_selection_empty - ); -} - - - -void GLGizmoHollow::on_save(cereal::BinaryOutputArchive& ar) const -{ - ar(m_new_hole_radius, - m_new_hole_height, - m_selected, - m_selection_empty - ); -} - - - -void GLGizmoHollow::select_point(int i) -{ - const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - - if (i == AllPoints || i == NoPoints) { - m_selected.assign(m_selected.size(), i == AllPoints); - m_selection_empty = (i == NoPoints); - - if (i == AllPoints) { - m_new_hole_radius = drain_holes[0].radius; - m_new_hole_height = drain_holes[0].height; - } - } - else { - while (size_t(i) >= m_selected.size()) - m_selected.push_back(false); - m_selected[i] = true; - m_selection_empty = false; - m_new_hole_radius = drain_holes[i].radius; - m_new_hole_height = drain_holes[i].height; - } -} - - -void GLGizmoHollow::unselect_point(int i) -{ - m_selected[i] = false; - m_selection_empty = true; - for (const bool sel : m_selected) { - if (sel) { - m_selection_empty = false; - break; - } - } -} - -void GLGizmoHollow::reload_cache() -{ - m_selected.clear(); - m_selected.assign(m_c->selection_info()->model_object()->sla_drain_holes.size(), false); -} - - -void GLGizmoHollow::on_set_hover_id() -{ - if (int(m_c->selection_info()->model_object()->sla_drain_holes.size()) <= m_hover_id) - m_hover_id = -1; -} - - - - -} // namespace GUI -} // namespace Slic3r +#include "GLGizmoHollow.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" + +#include + +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI_ObjectSettings.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "libslic3r/PresetBundle.hpp" + +#include "libslic3r/Model.hpp" + + +namespace Slic3r { +namespace GUI { + +GLGizmoHollow::GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +{ +} + + +bool GLGizmoHollow::on_init() +{ + m_shortcut_key = WXK_CONTROL_H; + m_desc["enable"] = _(L("Hollow this object")); + m_desc["preview"] = _(L("Preview hollowed and drilled model")); + m_desc["offset"] = _(L("Offset")) + ": "; + m_desc["quality"] = _(L("Quality")) + ": "; + m_desc["closing_distance"] = _(L("Closing distance")) + ": "; + m_desc["hole_diameter"] = _(L("Hole diameter")) + ": "; + m_desc["hole_depth"] = _(L("Hole depth")) + ": "; + m_desc["remove_selected"] = _(L("Remove selected holes")); + m_desc["remove_all"] = _(L("Remove all holes")); + m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": "; + m_desc["reset_direction"] = _(L("Reset direction")); + m_desc["show_supports"] = _(L("Show supports")); + + return true; +} + +void GLGizmoHollow::data_changed() +{ + if (! m_c->selection_info()) + return; + + const ModelObject* mo = m_c->selection_info()->model_object(); + if (m_state == On && mo) { + if (m_old_mo_id != mo->id()) { + reload_cache(); + m_old_mo_id = mo->id(); + } + if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) + m_holes_in_drilled_mesh = mo->sla_drain_holes; + } +} + + + +void GLGizmoHollow::on_render() +{ + if (!m_cylinder.is_initialized()) + m_cylinder.init_from(its_make_cylinder(1.0, 1.0)); + + const Selection& selection = m_parent.get_selection(); + const CommonGizmosDataObjects::SelectionInfo* sel_info = m_c->selection_info(); + + // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off + if (m_state == On + && (sel_info->model_object() != selection.get_model()->objects[selection.get_object_idx()] + || sel_info->get_active_instance() != selection.get_instance_idx())) { + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); + return; + } + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + if (selection.is_from_single_instance()) + render_points(selection, false); + + m_selection_rectangle.render(m_parent); + m_c->object_clipper()->render_cut(); + m_c->supports_clipper()->render_cut(); + + glsafe(::glDisable(GL_BLEND)); +} + + +void GLGizmoHollow::on_render_for_picking() +{ + const Selection& selection = m_parent.get_selection(); +//#if ENABLE_RENDER_PICKING_PASS +// m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); +//#endif + + glsafe(::glEnable(GL_DEPTH_TEST)); + render_points(selection, true); +} + +void GLGizmoHollow::render_points(const Selection& selection, bool picking) +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = picking ? wxGetApp().get_shader("flat") : wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); + ScopeGuard guard([shader]() { shader->stop_using(); }); +#else + GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); + if (shader) + shader->start_using(); + ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + Geometry::Transformation trafo = vol->get_instance_transformation() * vol->get_volume_transformation(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_scaling_factor_matrix().inverse(); +#else + const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix(); + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d& projection_matrix = camera.get_projection_matrix(); + + shader->set_uniform("projection_matrix", projection_matrix); +#else + const Transform3d& instance_scaling_matrix_inverse = trafo.get_matrix(true, true, false, true).inverse(); + const Transform3d& instance_matrix = trafo.get_matrix(); + + glsafe(::glPushMatrix()); + glsafe(::glTranslated(0.0, 0.0, m_c->selection_info()->get_sla_shift())); + glsafe(::glMultMatrixd(instance_matrix.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + ColorRGBA render_color; + const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; + const size_t cache_size = drain_holes.size(); + + for (size_t i = 0; i < cache_size; ++i) { + const sla::DrainHole& drain_hole = drain_holes[i]; + const bool point_selected = m_selected[i]; + + if (is_mesh_point_clipped(drain_hole.pos.cast())) + continue; + + // First decide about the color of the point. + if (picking) + render_color = picking_color_component(i); + else { + if (size_t(m_hover_id) == i) + render_color = ColorRGBA::CYAN(); + else if (m_c->hollowed_mesh() && + i < m_c->hollowed_mesh()->get_drainholes().size() && + m_c->hollowed_mesh()->get_drainholes()[i].failed) { + render_color = { 1.0f, 0.0f, 0.0f, 0.5f }; + } + else // neither hover nor picking + render_color = point_selected ? ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f) : ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_cylinder.set_color(render_color); +#else + const_cast(&m_cylinder)->set_color(-1, render_color); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z())); + glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (vol->is_left_handed()) + glFrontFace(GL_CW); + + // Matrices set, we can render the point mark now. + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); + const Eigen::AngleAxisd aa(q); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = view_matrix * instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) * + Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); + glsafe(::glTranslated(0., 0., -drain_hole.height)); + glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_cylinder.render(); + + if (vol->is_left_handed()) + glFrontFace(GL_CCW); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +} + +bool GLGizmoHollow::is_mesh_point_clipped(const Vec3d& point) const +{ + if (m_c->object_clipper()->get_position() == 0.) + return false; + + auto sel_info = m_c->selection_info(); + int active_inst = m_c->selection_info()->get_active_instance(); + const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; + const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix(); + + Vec3d transformed_point = trafo * point; + transformed_point(2) += sel_info->get_sla_shift(); + return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); +} + + + +// 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 GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) +{ + if (! m_c->raycaster()->raycaster()) + return false; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Selection& selection = m_parent.get_selection(); + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); + + double clp_dist = m_c->object_clipper()->get_position(); + const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); + + // The raycaster query + Vec3f hit; + Vec3f normal; + if (m_c->raycaster()->raycaster()->unproject_on_mesh( + mouse_pos, + trafo.get_matrix(), + camera, + hit, + normal, + clp_dist != 0. ? clp : nullptr)) + { + if (m_c->hollowed_mesh() && m_c->hollowed_mesh()->get_hollowed_mesh()) { + // in this case the raycaster sees the hollowed and drilled mesh. + // if the point lies on the surface created by the hole, we want + // to ignore it. + for (const sla::DrainHole& hole : m_holes_in_drilled_mesh) { + sla::DrainHole outer(hole); + outer.radius *= 1.001f; + outer.height *= 1.001f; + if (outer.is_inside(hit)) + return false; + } + } + + // Return both the point and the facet normal. + pos_and_normal = std::make_pair(hit, normal); + return true; + } + else + return false; +} + +// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. +// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is +// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo +// concludes that the event was not intended for it, it should return false. +bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + ModelObject* mo = m_c->selection_info()->model_object(); + int active_inst = m_c->selection_info()->get_active_instance(); + + + // left down with shift - show the selection rectangle: + if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { + if (m_hover_id == -1) { + if (shift_down || alt_down) { + m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); + } + } + else { + if (m_selected[m_hover_id]) + unselect_point(m_hover_id); + else { + if (!alt_down) + select_point(m_hover_id); + } + } + + return true; + } + + // left down without selection rectangle - place point on the mesh: + if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { + // If any point is in hover state, this should initiate its move - return control back to GLCanvas: + if (m_hover_id != -1) + return false; + + // If there is some selection, don't add new point and deselect everything instead. + if (m_selection_empty) { + std::pair pos_and_normal; + if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add drainage hole"))); + + mo->sla_drain_holes.emplace_back(pos_and_normal.first, + -pos_and_normal.second, m_new_hole_radius, m_new_hole_height); + m_selected.push_back(false); + assert(m_selected.size() == mo->sla_drain_holes.size()); + m_parent.set_as_dirty(); + m_wait_for_up_event = true; + } + else + return false; + } + else + select_point(NoPoints); + + return true; + } + + // left up with selection rectangle - select points inside the rectangle: + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { + // Is this a selection or deselection rectangle? + GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); + + // First collect positions of all the points in world coordinates. + Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation(); + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); + std::vector points; + for (unsigned int i=0; isla_drain_holes.size(); ++i) + points.push_back(trafo.get_matrix() * mo->sla_drain_holes[i].pos.cast()); + + // Now ask the rectangle which of the points are inside. + std::vector points_inside; + std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); + for (size_t idx : points_idxs) + points_inside.push_back(points[idx].cast()); + + // Only select/deselect points that are actually visible + for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( + trafo, wxGetApp().plater()->get_camera(), points_inside, + m_c->object_clipper()->get_clipping_plane())) + { + if (rectangle_status == GLSelectionRectangle::EState::Deselect) + unselect_point(points_idxs[idx]); + else + select_point(points_idxs[idx]); + } + return true; + } + + // left up with no selection rectangle + if (action == SLAGizmoEventType::LeftUp) { + if (m_wait_for_up_event) { + m_wait_for_up_event = false; + return true; + } + } + + // dragging the selection rectangle: + if (action == SLAGizmoEventType::Dragging) { + if (m_wait_for_up_event) + return true; // point has been placed and the button not released yet + // this prevents GLCanvas from starting scene rotation + + if (m_selection_rectangle.is_dragging()) { + m_selection_rectangle.dragging(mouse_position); + return true; + } + + return false; + } + + if (action == SLAGizmoEventType::Delete) { + // delete key pressed + delete_selected_points(); + return true; + } + + if (action == SLAGizmoEventType::RightDown) { + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + delete_selected_points(); + return true; + } + return false; + } + + if (action == SLAGizmoEventType::SelectAll) { + select_point(AllPoints); + return true; + } + + if (action == SLAGizmoEventType::MouseWheelUp && control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = std::min(1., pos + 0.01); + m_c->object_clipper()->set_position(pos, true); + return true; + } + + if (action == SLAGizmoEventType::MouseWheelDown && control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = std::max(0., pos - 0.01); + m_c->object_clipper()->set_position(pos, true); + return true; + } + + if (action == SLAGizmoEventType::ResetClippingPlane) { + m_c->object_clipper()->set_position(-1., false); + return true; + } + + return false; +} + +void GLGizmoHollow::delete_selected_points() +{ + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete drainage hole"))); + sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; + + for (unsigned int idx=0; idx wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + + static bool pending_right_up = false; + if (mouse_event.LeftDown()) { + bool control_down = mouse_event.CmdDown(); + bool grabber_contains_mouse = (get_hover_id() != -1); + if ((!control_down || grabber_contains_mouse) && + gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) + // the gizmo got the event and took some action, there is no need + // to do anything more + return true; + } else if (mouse_event.Dragging()) { + if (m_parent.get_move_volume_id() != -1) + // don't allow dragging objects with the Sla gizmo on + return true; + + bool control_down = mouse_event.CmdDown(); + if (control_down) { + // CTRL has been pressed while already dragging -> stop current action + if (mouse_event.LeftIsDown()) + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); + else if (mouse_event.RightIsDown()) { + pending_right_up = false; + } + } else if(gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) { + // the gizmo got the event and took some action, no need to do + // anything more here + m_parent.set_as_dirty(); + return true; + } + } else if (mouse_event.LeftUp()) { + if (!m_parent.is_mouse_dragging()) { + bool control_down = mouse_event.CmdDown(); + // in case gizmo is selected, we just pass the LeftUp event + // and stop processing - neither object moving or selecting is + // suppressed in that case + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); + return true; + } + } else if (mouse_event.RightDown()) { + if (m_parent.get_selection().get_object_idx() != -1 && + gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) { + // we need to set the following right up as processed to avoid showing + // the context menu if the user release the mouse over the object + pending_right_up = true; + // event was taken care of by the SlaSupports gizmo + return true; + } + } else if (mouse_event.RightUp()) { + if (pending_right_up) { + pending_right_up = false; + return true; + } + } + return false; +} + +void GLGizmoHollow::hollow_mesh(bool postpone_error_messages) +{ + wxGetApp().CallAfter([this, postpone_error_messages]() { + wxGetApp().plater()->reslice_SLA_hollowing( + *m_c->selection_info()->model_object(), postpone_error_messages); + }); +} + + +std::vector> +GLGizmoHollow::get_config_options(const std::vector& keys) const +{ + std::vector> out; + const ModelObject* mo = m_c->selection_info()->model_object(); + + if (! mo) + return out; + + const DynamicPrintConfig& object_cfg = mo->config.get(); + const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + std::unique_ptr default_cfg = nullptr; + + for (const std::string& key : keys) { + if (object_cfg.has(key)) + out.emplace_back(object_cfg.option(key), &object_cfg.def()->options.at(key)); // at() needed for const map + else + if (print_cfg.has(key)) + out.emplace_back(print_cfg.option(key), &print_cfg.def()->options.at(key)); + else { // we must get it from defaults + if (default_cfg == nullptr) + default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); + out.emplace_back(default_cfg->option(key), &default_cfg->def()->options.at(key)); + } + } + + return out; +} + + +void GLGizmoHollow::on_render_input_window(float x, float y, float bottom_limit) +{ + ModelObject* mo = m_c->selection_info()->model_object(); + if (! mo) + return; + + bool first_run = true; // This is a hack to redraw the button when all points are removed, + // so it is not delayed until the background process finishes. + + ConfigOptionMode current_mode = wxGetApp().get_mode(); + + std::vector opts_keys = {"hollowing_min_thickness", "hollowing_quality", "hollowing_closing_distance"}; + auto opts = get_config_options(opts_keys); + auto* offset_cfg = static_cast(opts[0].first); + float offset = offset_cfg->value; + double offset_min = opts[0].second->min; + double offset_max = opts[0].second->max; + + auto* quality_cfg = static_cast(opts[1].first); + float quality = quality_cfg->value; + double quality_min = opts[1].second->min; + double quality_max = opts[1].second->max; + ConfigOptionMode quality_mode = opts[1].second->mode; + + auto* closing_d_cfg = static_cast(opts[2].first); + float closing_d = closing_d_cfg->value; + double closing_d_min = opts[2].second->min; + double closing_d_max = opts[2].second->max; + ConfigOptionMode closing_d_mode = opts[2].second->mode; + + m_desc["offset"] = _(opts[0].second->label) + ":"; + m_desc["quality"] = _(opts[1].second->label) + ":"; + m_desc["closing_distance"] = _(opts[2].second->label) + ":"; + + +RENDER_AGAIN: + const float approx_height = m_imgui->scaled(20.0f); + y = std::min(y, bottom_limit - approx_height); + m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + + m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + + // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, + m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(0.5f); + + const float settings_sliders_left = + std::max(std::max({m_imgui->calc_text_size(m_desc.at("offset")).x, + m_imgui->calc_text_size(m_desc.at("quality")).x, + m_imgui->calc_text_size(m_desc.at("closing_distance")).x, + m_imgui->calc_text_size(m_desc.at("hole_diameter")).x, + m_imgui->calc_text_size(m_desc.at("hole_depth")).x}) + m_imgui->scaled(0.5f), clipping_slider_left); + + const float diameter_slider_left = settings_sliders_left; //m_imgui->calc_text_size(m_desc.at("hole_diameter")).x + m_imgui->scaled(1.f); + const float minimal_slider_width = m_imgui->scaled(4.f); + + const float button_preview_width = m_imgui->calc_button_size(m_desc.at("preview")).x; + + float window_width = minimal_slider_width + std::max({settings_sliders_left, clipping_slider_left, diameter_slider_left}); + window_width = std::max(window_width, button_preview_width); + + if (m_imgui->button(m_desc["preview"])) + hollow_mesh(); + + bool config_changed = false; + + ImGui::Separator(); + + { + auto opts = get_config_options({"hollowing_enable"}); + m_enable_hollowing = static_cast(opts[0].first)->value; + if (m_imgui->checkbox(m_desc["enable"], m_enable_hollowing)) { + mo->config.set("hollowing_enable", m_enable_hollowing); + wxGetApp().obj_list()->update_and_show_object_settings_item(); + config_changed = true; + } + } + + m_imgui->disabled_begin(! m_enable_hollowing); + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("offset")); + ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + ImGui::PushItemWidth(window_width - settings_sliders_left); + m_imgui->slider_float("##offset", &offset, offset_min, offset_max, "%.1f mm", 1.0f, true, _L(opts[0].second->tooltip)); + + bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider + bool slider_edited =m_imgui->get_last_slider_status().edited; // someone is dragging the slider + bool slider_released =m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider + + if (current_mode >= quality_mode) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("quality")); + ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + m_imgui->slider_float("##quality", &quality, quality_min, quality_max, "%.1f", 1.0f, true, _L(opts[1].second->tooltip)); + + slider_clicked |= m_imgui->get_last_slider_status().clicked; + slider_edited |= m_imgui->get_last_slider_status().edited; + slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; + } + + if (current_mode >= closing_d_mode) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("closing_distance")); + ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + m_imgui->slider_float("##closing_distance", &closing_d, closing_d_min, closing_d_max, "%.1f mm", 1.0f, true, _L(opts[2].second->tooltip)); + + slider_clicked |= m_imgui->get_last_slider_status().clicked; + slider_edited |= m_imgui->get_last_slider_status().edited; + slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; + } + + if (slider_clicked) { + m_offset_stash = offset; + m_quality_stash = quality; + m_closing_d_stash = closing_d; + } + if (slider_edited || slider_released) { + if (slider_released) { + mo->config.set("hollowing_min_thickness", m_offset_stash); + mo->config.set("hollowing_quality", m_quality_stash); + mo->config.set("hollowing_closing_distance", m_closing_d_stash); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Hollowing parameter change"))); + } + mo->config.set("hollowing_min_thickness", offset); + mo->config.set("hollowing_quality", quality); + mo->config.set("hollowing_closing_distance", closing_d); + if (slider_released) { + wxGetApp().obj_list()->update_and_show_object_settings_item(); + config_changed = true; + } + } + + m_imgui->disabled_end(); + + bool force_refresh = false; + bool remove_selected = false; + bool remove_all = false; + + ImGui::Separator(); + + float diameter_upper_cap = 60.; + if (m_new_hole_radius * 2.f > diameter_upper_cap) + m_new_hole_radius = diameter_upper_cap / 2.f; + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("hole_diameter")); + ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); + ImGui::PushItemWidth(window_width - diameter_slider_left); + + float diam = 2.f * m_new_hole_radius; + m_imgui->slider_float("##hole_diameter", &diam, 1.f, 25.f, "%.1f mm", 1.f, false); + // Let's clamp the value (which could have been entered by keyboard) to a larger range + // than the slider. This allows entering off-scale values and still protects against + //complete non-sense. + diam = std::clamp(diam, 0.1f, diameter_upper_cap); + m_new_hole_radius = diam / 2.f; + bool clicked = m_imgui->get_last_slider_status().clicked; + bool edited = m_imgui->get_last_slider_status().edited; + bool deactivated = m_imgui->get_last_slider_status().deactivated_after_edit; + + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc["hole_depth"]); + ImGui::SameLine(diameter_slider_left, m_imgui->get_item_spacing().x); + m_imgui->slider_float("##hole_depth", &m_new_hole_height, 0.f, 10.f, "%.1f mm", 1.f, false); + // Same as above: + m_new_hole_height = std::clamp(m_new_hole_height, 0.f, 100.f); + + clicked |= m_imgui->get_last_slider_status().clicked; + edited |= m_imgui->get_last_slider_status().edited; + deactivated |= m_imgui->get_last_slider_status().deactivated_after_edit;; + + // Following is a nasty way to: + // - save the initial value of the slider before one starts messing with it + // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene + // - take correct undo/redo snapshot after the user is done with moving the slider + if (! m_selection_empty) { + if (clicked) { + m_holes_stash = mo->sla_drain_holes; + } + if (edited) { + for (size_t idx=0; idxsla_drain_holes[idx].radius = m_new_hole_radius; + mo->sla_drain_holes[idx].height = m_new_hole_height; + } + } + if (deactivated) { + // momentarily restore the old value to take snapshot + sla::DrainHoles new_holes = mo->sla_drain_holes; + mo->sla_drain_holes = m_holes_stash; + float backup_rad = m_new_hole_radius; + float backup_hei = m_new_hole_height; + for (size_t i=0; isla_drain_holes = new_holes; + } + } + + m_imgui->disabled_begin(m_selection_empty); + remove_selected = m_imgui->button(m_desc.at("remove_selected")); + m_imgui->disabled_end(); + + m_imgui->disabled_begin(mo->sla_drain_holes.empty()); + remove_all = m_imgui->button(m_desc.at("remove_all")); + m_imgui->disabled_end(); + + // Following is rendered in both editing and non-editing mode: + // m_imgui->text(""); + ImGui::Separator(); + if (m_c->object_clipper()->get_position() == 0.f) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("clipping_of_view")); + } + else { + if (m_imgui->button(m_desc.at("reset_direction"))) { + wxGetApp().CallAfter([this](){ + m_c->object_clipper()->set_position(-1., false); + }); + } + } + + ImGui::SameLine(settings_sliders_left, m_imgui->get_item_spacing().x); + ImGui::PushItemWidth(window_width - settings_sliders_left); + float clp_dist = m_c->object_clipper()->get_position(); + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) + m_c->object_clipper()->set_position(clp_dist, true); + + // make sure supports are shown/hidden as appropriate + bool show_sups = m_c->instances_hider()->are_supports_shown(); + if (m_imgui->checkbox(m_desc["show_supports"], show_sups)) { + m_c->instances_hider()->show_supports(show_sups); + force_refresh = true; + } + + m_imgui->end(); + + + if (remove_selected || remove_all) { + force_refresh = false; + m_parent.set_as_dirty(); + + if (remove_all) { + select_point(AllPoints); + delete_selected_points(); + } + if (remove_selected) + delete_selected_points(); + + if (first_run) { + first_run = false; + goto RENDER_AGAIN; + } + } + + if (force_refresh) + m_parent.set_as_dirty(); + + if (config_changed) + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); +} + +bool GLGizmoHollow::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA + || !selection.is_from_single_instance()) + return false; + + // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. + const Selection::IndicesList& list = selection.get_volume_idxs(); + for (const auto& idx : list) + if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) + return false; + + return true; +} + +bool GLGizmoHollow::on_is_selectable() const +{ + return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); +} + +std::string GLGizmoHollow::on_get_name() const +{ + return _u8L("Hollow and drill"); +} + + +CommonGizmosDataID GLGizmoHollow::on_get_requirements() const +{ + return CommonGizmosDataID( + int(CommonGizmosDataID::SelectionInfo) + | int(CommonGizmosDataID::InstancesHider) + | int(CommonGizmosDataID::Raycaster) + | int(CommonGizmosDataID::HollowedMesh) + | int(CommonGizmosDataID::ObjectClipper) + | int(CommonGizmosDataID::SupportsClipper)); +} + + +void GLGizmoHollow::on_set_state() +{ + if (m_state == m_old_state) + return; + + if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); + m_old_state = m_state; +} + + + +void GLGizmoHollow::on_start_dragging() +{ + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + m_hole_before_drag = m_c->selection_info()->model_object()->sla_drain_holes[m_hover_id].pos; + } + else + m_hole_before_drag = Vec3f::Zero(); +} + + +void GLGizmoHollow::on_stop_dragging() +{ + sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; + if (m_hover_id != -1) { + Vec3f backup = drain_holes[m_hover_id].pos; + + if (m_hole_before_drag != Vec3f::Zero() // some point was touched + && backup != m_hole_before_drag) // and it was moved, not just selected + { + drain_holes[m_hover_id].pos = m_hole_before_drag; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move drainage hole"))); + drain_holes[m_hover_id].pos = backup; + } + } + m_hole_before_drag = Vec3f::Zero(); +} + + +void GLGizmoHollow::on_dragging(const UpdateData &data) +{ + assert(m_hover_id != -1); + std::pair pos_and_normal; + if (!unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) + return; + sla::DrainHoles &drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; + drain_holes[m_hover_id].pos = pos_and_normal.first; + drain_holes[m_hover_id].normal = -pos_and_normal.second; +} + + +void GLGizmoHollow::on_load(cereal::BinaryInputArchive& ar) +{ + ar(m_new_hole_radius, + m_new_hole_height, + m_selected, + m_selection_empty + ); +} + + + +void GLGizmoHollow::on_save(cereal::BinaryOutputArchive& ar) const +{ + ar(m_new_hole_radius, + m_new_hole_height, + m_selected, + m_selection_empty + ); +} + + + +void GLGizmoHollow::select_point(int i) +{ + const sla::DrainHoles& drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; + + if (i == AllPoints || i == NoPoints) { + m_selected.assign(m_selected.size(), i == AllPoints); + m_selection_empty = (i == NoPoints); + + if (i == AllPoints) { + m_new_hole_radius = drain_holes[0].radius; + m_new_hole_height = drain_holes[0].height; + } + } + else { + while (size_t(i) >= m_selected.size()) + m_selected.push_back(false); + m_selected[i] = true; + m_selection_empty = false; + m_new_hole_radius = drain_holes[i].radius; + m_new_hole_height = drain_holes[i].height; + } +} + + +void GLGizmoHollow::unselect_point(int i) +{ + m_selected[i] = false; + m_selection_empty = true; + for (const bool sel : m_selected) { + if (sel) { + m_selection_empty = false; + break; + } + } +} + +void GLGizmoHollow::reload_cache() +{ + m_selected.clear(); + m_selected.assign(m_c->selection_info()->model_object()->sla_drain_holes.size(), false); +} + + +void GLGizmoHollow::on_set_hover_id() +{ + if (int(m_c->selection_info()->model_object()->sla_drain_holes.size()) <= m_hover_id) + m_hover_id = -1; +} + + + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 14c41ce54..52dd207e9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -90,32 +90,32 @@ bool GLGizmoMove3D::on_is_activable() const void GLGizmoMove3D::on_start_dragging() { - if (m_hover_id != -1) { - m_displacement = Vec3d::Zero(); + assert(m_hover_id != -1); + + m_displacement = Vec3d::Zero(); #if ENABLE_WORLD_COORDINATE - const Selection& selection = m_parent.get_selection(); - const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); - if (coordinates_type == ECoordinatesType::World) - m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; - else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); - m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center; - } - else { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); - m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; - } - m_starting_box_center = m_center; - m_starting_box_bottom_center = m_center; - m_starting_box_bottom_center.z() = m_bounding_box.min.z(); -#else - const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); - m_starting_drag_position = m_grabbers[m_hover_id].center; - m_starting_box_center = box.center(); - m_starting_box_bottom_center = box.center(); - m_starting_box_bottom_center.z() = box.min.z(); -#endif // ENABLE_WORLD_COORDINATE + const Selection& selection = m_parent.get_selection(); + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + if (coordinates_type == ECoordinatesType::World) + m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; + else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center; } + else { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; + } + m_starting_box_center = m_center; + m_starting_box_bottom_center = m_center; + m_starting_box_bottom_center.z() = m_bounding_box.min.z(); +#else + const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); + m_starting_drag_position = m_grabbers[m_hover_id].center; + m_starting_box_center = box.center(); + m_starting_box_bottom_center = box.center(); + m_starting_box_bottom_center.z() = box.min.z(); +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoMove3D::on_stop_dragging() @@ -134,7 +134,11 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data) m_displacement.z() = calc_projection(data); Selection &selection = m_parent.get_selection(); +#if ENABLE_WORLD_COORDINATE + selection.translate(m_displacement, wxGetApp().obj_manipul()->get_coordinates_type()); +#else selection.translate(m_displacement); +#endif // ENABLE_WORLD_COORDINATE } void GLGizmoMove3D::on_render() @@ -148,9 +152,18 @@ void GLGizmoMove3D::on_render() glsafe(::glEnable(GL_DEPTH_TEST)); #if ENABLE_WORLD_COORDINATE +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPushMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES calc_selection_box_and_center(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d base_matrix = local_transform(m_parent.get_selection()); + for (int i = 0; i < 3; ++i) { + m_grabbers[i].matrix = base_matrix; + } +#else transform_to_local(m_parent.get_selection()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES const Vec3d zero = Vec3d::Zero(); const Vec3d half_box_size = 0.5 * m_bounding_box.size(); @@ -236,7 +249,7 @@ void GLGizmoMove3D::on_render() #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -270,8 +283,12 @@ void GLGizmoMove3D::on_render() #if !ENABLE_GIZMO_GRABBER_REFACTOR for (unsigned int i = 0; i < 3; ++i) { if (m_grabbers[i].enabled) +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_grabber_extension((Axis)i, base_matrix, m_bounding_box, false); +#else render_grabber_extension((Axis)i, m_bounding_box, false); - } +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + } #endif // !ENABLE_GIZMO_GRABBER_REFACTOR #else render_grabbers(box); @@ -292,7 +309,7 @@ void GLGizmoMove3D::on_render() #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()* base_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -329,7 +346,11 @@ void GLGizmoMove3D::on_render() } #if !ENABLE_GIZMO_GRABBER_REFACTOR #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_grabber_extension((Axis)m_hover_id, base_matrix, m_bounding_box, false); +#else render_grabber_extension((Axis)m_hover_id, m_bounding_box, false); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #else render_grabber_extension((Axis)m_hover_id, box, false); #endif // ENABLE_WORLD_COORDINATE @@ -337,7 +358,9 @@ void GLGizmoMove3D::on_render() } #if ENABLE_WORLD_COORDINATE +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES #endif // ENABLE_WORLD_COORDINATE } @@ -346,15 +369,30 @@ void GLGizmoMove3D::on_render_for_picking() glsafe(::glDisable(GL_DEPTH_TEST)); #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d base_matrix = local_transform(m_parent.get_selection()); + for (int i = 0; i < 3; ++i) { + m_grabbers[i].matrix = base_matrix; + } +#else glsafe(::glPushMatrix()); transform_to_local(m_parent.get_selection()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES render_grabbers_for_picking(m_bounding_box); +#if ENABLE_GL_SHADERS_ATTRIBUTES +#if !ENABLE_GIZMO_GRABBER_REFACTOR + render_grabber_extension(X, base_matrix, m_bounding_box, true); + render_grabber_extension(Y, base_matrix, m_bounding_box, true); + render_grabber_extension(Z, base_matrix, m_bounding_box, true); +#endif // !ENABLE_GIZMO_GRABBER_REFACTOR +#else #if !ENABLE_GIZMO_GRABBER_REFACTOR render_grabber_extension(X, m_bounding_box, true); render_grabber_extension(Y, m_bounding_box, true); render_grabber_extension(Z, m_bounding_box, true); #endif // !ENABLE_GIZMO_GRABBER_REFACTOR glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #else const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); render_grabbers_for_picking(box); @@ -393,9 +431,14 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const } #if !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE && ENABLE_GL_SHADERS_ATTRIBUTES +void GLGizmoMove3D::render_grabber_extension(Axis axis, const Transform3d& base_matrix, const BoundingBoxf3& box, bool picking) +#else void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) +#endif // ENABLE_WORLD_COORDINATE && ENABLE_GL_SHADERS_ATTRIBUTES { - const float mean_size = float((box.size().x() + box.size().y() + box.size().z()) / 3.0); + const Vec3d box_size = box.size(); + const float mean_size = float((box_size.x() + box_size.y() + box_size.z()) / 3.0); const double size = m_dragging ? double(m_grabbers[axis].get_dragging_half_size(mean_size)) : double(m_grabbers[axis].get_half_size(mean_size)); #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -420,7 +463,7 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); - Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::assemble_transform(m_grabbers[axis].center); + Transform3d view_model_matrix = camera.get_view_matrix() * base_matrix * Geometry::assemble_transform(m_grabbers[axis].center); if (axis == X) view_model_matrix = view_model_matrix * Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()); else if (axis == Y) @@ -454,6 +497,28 @@ void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box #endif // !ENABLE_GIZMO_GRABBER_REFACTOR #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES +Transform3d GLGizmoMove3D::local_transform(const Selection& selection) const +{ + Transform3d ret = Geometry::assemble_transform(m_center); + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); +#else + Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix(); +#else + orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + ret = ret * orient_matrix; + } + return ret; +} +#else void GLGizmoMove3D::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); @@ -466,6 +531,7 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const glsafe(::glMultMatrixd(orient_matrix.data())); } } +#endif // ENABLE_GL_SHADERS_ATTRIBUTES void GLGizmoMove3D::calc_selection_box_and_center() { @@ -477,7 +543,12 @@ void GLGizmoMove3D::calc_selection_box_and_center() } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + 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()); +#else m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_center = v.world_matrix() * m_bounding_box.center(); } else { @@ -487,8 +558,13 @@ void GLGizmoMove3D::calc_selection_box_and_center() const GLVolume& v = *selection.get_volume(id); m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix()); + m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor() * m_bounding_box.center(); +#else m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } } #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index c1368cf62..85f3c4785 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -71,7 +71,11 @@ protected: private: double calc_projection(const UpdateData& data) const; #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES + Transform3d local_transform(const Selection& selection) const; +#else void transform_to_local(const Selection& selection) const; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES void calc_selection_box_and_center(); #endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 0cba59c6d..c7731f1ff 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -1,1383 +1,1407 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. -#include "GLGizmoPainterBase.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" - -#include - -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/Camera.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/OpenGLManager.hpp" -#include "slic3r/Utils/UndoRedo.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/TriangleMesh.hpp" - -#include -#include - -namespace Slic3r::GUI { - -#if ENABLE_LEGACY_OPENGL_REMOVAL -std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; -#else -std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -{ -} - -GLGizmoPainterBase::~GLGizmoPainterBase() -{ -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (s_sphere != nullptr) - s_sphere.reset(); -#else - if (s_sphere != nullptr && s_sphere->has_VBOs()) - s_sphere->release_geometry(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -void GLGizmoPainterBase::data_changed() -{ - if (m_state != On) - return; - - const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; - const Selection & selection = m_parent.get_selection(); - if (mo && selection.is_from_single_instance() - && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) - { - update_from_model_object(); - m_old_mo_id = mo->id(); - m_old_volumes_size = mo->volumes.size(); - m_schedule_update = false; - } -} - -GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const -{ - ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}}; - // Take care of the clipping plane. The normal of the clipping plane is - // saved with opposite sign than we need to pass to OpenGL (FIXME) - if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) { - const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane(); - for (size_t i = 0; i < 3; ++i) - clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]); - clp_data_out.clp_dataf[3] = float(clp->get_data()[3]); - } - - // z_range is calculated in the same way as in GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) - if (m_c->get_canvas()->get_use_clipping_planes()) { - const std::array &clps = m_c->get_canvas()->get_clipping_planes(); - clp_data_out.z_range = {float(-clps[0].get_data()[3]), float(clps[1].get_data()[3])}; - } - - return clp_data_out; -} - -void GLGizmoPainterBase::render_triangles(const Selection& selection) const -{ - auto* shader = wxGetApp().get_shader("gouraud"); - if (! shader) - return; - shader->start_using(); - shader->set_uniform("slope.actived", false); - shader->set_uniform("print_volume.type", 0); - shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf); - ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); - - const ModelObject *mo = m_c->selection_info()->model_object(); - int mesh_id = -1; - for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) - continue; - - ++mesh_id; - - const Transform3d trafo_matrix = - mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * - mv->get_matrix(); - - bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; - if (is_left_handed) - glsafe(::glFrontFace(GL_CW)); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d matrix = camera.get_view_matrix() * trafo_matrix; - shader->set_uniform("view_model_matrix", matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); - shader->set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(trafo_matrix.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - // For printers with multiple extruders, it is necessary to pass trafo_matrix - // to the shader input variable print_box.volume_world_matrix before - // rendering the painted triangles. When this matrix is not set, the - // wrong transformation matrix is used for "Clipping of view". - shader->set_uniform("volume_world_matrix", trafo_matrix); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix); -#else - m_triangle_selectors[mesh_id]->render(m_imgui); - - glsafe(::glPopMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (is_left_handed) - glsafe(::glFrontFace(GL_CCW)); - } -} - -void GLGizmoPainterBase::render_cursor() -{ - // First check that the mouse pointer is on an object. - const ModelObject* mo = m_c->selection_info()->model_object(); - const Selection& selection = m_parent.get_selection(); - const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; - const Camera& camera = wxGetApp().plater()->get_camera(); - - // Precalculate transformations of individual meshes. - std::vector trafo_matrices; - for (const ModelVolume* mv : mo->volumes) { - if (mv->is_model_part()) - trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); - } - // Raycast and return if there's no hit. - update_raycast_cache(m_parent.get_local_mouse_position(), camera, trafo_matrices); - if (m_rr.mesh_id == -1) - return; - - if (m_tool_type == ToolType::BRUSH) { - if (m_cursor_type == TriangleSelector::SPHERE) - render_cursor_sphere(trafo_matrices[m_rr.mesh_id]); - else if (m_cursor_type == TriangleSelector::CIRCLE) - render_cursor_circle(); - } -} - -void GLGizmoPainterBase::render_cursor_circle() -{ -#if !ENABLE_GL_SHADERS_ATTRIBUTES - const Camera &camera = wxGetApp().plater()->get_camera(); - const float zoom = float(camera.get_zoom()); - const float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - const Size cnv_size = m_parent.get_canvas_size(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const float cnv_width = float(cnv_size.get_width()); - const float cnv_height = float(cnv_size.get_height()); - if (cnv_width == 0.0f || cnv_height == 0.0f) - return; - - const float cnv_inv_width = 1.0f / cnv_width; - const float cnv_inv_height = 1.0f / cnv_height; - - const Vec2d center = m_parent.get_local_mouse_position(); - const float radius = m_cursor_radius * float(wxGetApp().plater()->get_camera().get_zoom()); -#else - const float cnv_half_width = 0.5f * float(cnv_size.get_width()); - const float cnv_half_height = 0.5f * float(cnv_size.get_height()); - if (cnv_half_width == 0.0f || cnv_half_height == 0.0f) - return; - const Vec2d mouse_pos(m_parent.get_local_mouse_position().x(), m_parent.get_local_mouse_position().y()); - Vec2d center(mouse_pos.x() - cnv_half_width, cnv_half_height - mouse_pos.y()); - center = center * inv_zoom; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - glsafe(::glLineWidth(1.5f)); -#if !ENABLE_LEGACY_OPENGL_REMOVAL - static const std::array color = { 0.f, 1.f, 0.3f }; - glsafe(::glColor3fv(color.data())); -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glDisable(GL_DEPTH_TEST)); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPushMatrix()); - glsafe(::glLoadIdentity()); - // ensure that the circle is renderered inside the frustrum - glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); - // ensure that the overlay fits the frustrum near z plane - const double gui_scale = camera.get_gui_scale(); - glsafe(::glScaled(gui_scale, gui_scale, 1.0)); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - glsafe(::glPushAttrib(GL_ENABLE_BIT)); - glsafe(::glLineStipple(4, 0xAAAA)); - glsafe(::glEnable(GL_LINE_STIPPLE)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - radius) > EPSILON) { - m_old_cursor_radius = radius; -#else - if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - m_cursor_radius) > EPSILON) { - m_old_cursor_radius = m_cursor_radius; -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_old_center = center; - m_circle.reset(); - - GLModel::Geometry init_data; - static const unsigned int StepsCount = 32; - static const float StepSize = 2.0f * float(PI) / float(StepsCount); - init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 }; - init_data.color = { 0.0f, 1.0f, 0.3f, 1.0f }; - init_data.reserve_vertices(StepsCount); - init_data.reserve_indices(StepsCount); - - // vertices + indices - for (unsigned int i = 0; i < StepsCount; ++i) { - const float angle = float(i) * StepSize; -#if ENABLE_GL_SHADERS_ATTRIBUTES - init_data.add_vertex(Vec2f(2.0f * ((center.x() + ::cos(angle) * radius) * cnv_inv_width - 0.5f), - -2.0f * ((center.y() + ::sin(angle) * radius) * cnv_inv_height - 0.5f))); -#else - init_data.add_vertex(Vec2f(center.x() + ::cos(angle) * m_cursor_radius, center.y() + ::sin(angle) * m_cursor_radius)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - init_data.add_index(i); - } - - m_circle.init_from(std::move(init_data)); - } - - GLShaderProgram* shader = GUI::wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - shader->set_uniform("view_model_matrix", Transform3d::Identity()); - shader->set_uniform("projection_matrix", Transform3d::Identity()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_circle.render(); - shader->stop_using(); - } -#else - ::glBegin(GL_LINE_LOOP); - for (double angle=0; angle<2*M_PI; angle+=M_PI/20.) - ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle))); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glPopAttrib()); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glEnable(GL_DEPTH_TEST)); -} - - -void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const -{ - if (s_sphere == nullptr) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - s_sphere = std::make_shared(); - s_sphere->init_from(its_make_sphere(1.0, double(PI) / 12.0)); -#else - s_sphere = std::make_shared(); - s_sphere->load_its_flat_shading(its_make_sphere(1.0, double(PI) / 12.0)); - s_sphere->finalize_geometry(true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader == nullptr) - return; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse(); - const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed(); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixd(trafo.data())); - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glTranslatef(m_rr.hit.x(), m_rr.hit.y(), m_rr.hit.z())); - glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data())); - glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius)); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - if (is_left_handed) - glFrontFace(GL_CW); - - ColorRGBA render_color = { 0.0f, 0.0f, 0.0f, 0.25f }; - if (m_button_down == Button::Left) - render_color = this->get_cursor_sphere_left_button_color(); - else if (m_button_down == Button::Right) - render_color = this->get_cursor_sphere_right_button_color(); -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->start_using(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - Transform3d view_model_matrix = camera.get_view_matrix() * trafo * - Geometry::assemble_transform(m_rr.hit.cast()) * complete_scaling_matrix_inverse * - Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), m_cursor_radius * Vec3d::Ones()); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - assert(s_sphere != nullptr); - s_sphere->set_color(render_color); -#else - glsafe(::glColor4fv(render_color.data())); - - assert(s_sphere != nullptr); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - s_sphere->render(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - if (is_left_handed) - glFrontFace(GL_CCW); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -} - - -bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const -{ - if (m_c->object_clipper()->get_position() == 0.) - return false; - - auto sel_info = m_c->selection_info(); - Vec3d transformed_point = trafo * point; - transformed_point(2) += sel_info->get_sla_shift(); - return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); -} - -// Interpolate points between the previous and current mouse positions, which are then projected onto the object. -// Returned projected mouse positions are grouped by mesh_idx. It may contain multiple std::vector -// with the same mesh_idx, but all items in std::vector always have the same mesh_idx. -std::vector> GLGizmoPainterBase::get_projected_mouse_positions(const Vec2d &mouse_position, const double resolution, const std::vector &trafo_matrices) const -{ - // List of mouse positions that will be used as seeds for painting. - std::vector mouse_positions{mouse_position}; - if (m_last_mouse_click != Vec2d::Zero()) { - // In case current mouse position is far from the last one, - // add several positions from between into the list, so there - // are no gaps in the painted region. - if (size_t patches_in_between = size_t((mouse_position - m_last_mouse_click).norm() / resolution); patches_in_between > 0) { - const Vec2d diff = (m_last_mouse_click - mouse_position) / (patches_in_between + 1); - for (size_t patch_idx = 1; patch_idx <= patches_in_between; ++patch_idx) - mouse_positions.emplace_back(mouse_position + patch_idx * diff); - mouse_positions.emplace_back(m_last_mouse_click); - } - } - - const Camera &camera = wxGetApp().plater()->get_camera(); - std::vector mesh_hit_points; - mesh_hit_points.reserve(mouse_positions.size()); - - // In mesh_hit_points only the last item could have mesh_id == -1, any other items mustn't. - for (const Vec2d &mp : mouse_positions) { - update_raycast_cache(mp, camera, trafo_matrices); - mesh_hit_points.push_back({m_rr.hit, m_rr.mesh_id, m_rr.facet}); - if (m_rr.mesh_id == -1) - break; - } - - // Divide mesh_hit_points into groups with the same mesh_idx. It may contain multiple groups with the same mesh_idx. - std::vector> mesh_hit_points_by_mesh; - for (size_t prev_mesh_hit_point = 0, curr_mesh_hit_point = 0; curr_mesh_hit_point < mesh_hit_points.size(); ++curr_mesh_hit_point) { - size_t next_mesh_hit_point = curr_mesh_hit_point + 1; - if (next_mesh_hit_point >= mesh_hit_points.size() || mesh_hit_points[curr_mesh_hit_point].mesh_idx != mesh_hit_points[next_mesh_hit_point].mesh_idx) { - mesh_hit_points_by_mesh.emplace_back(); - mesh_hit_points_by_mesh.back().insert(mesh_hit_points_by_mesh.back().end(), mesh_hit_points.begin() + int(prev_mesh_hit_point), mesh_hit_points.begin() + int(next_mesh_hit_point)); - prev_mesh_hit_point = next_mesh_hit_point; - } - } - - auto on_same_facet = [](std::vector &hit_points) -> bool { - for (const ProjectedMousePosition &mesh_hit_point : hit_points) - if (mesh_hit_point.facet_idx != hit_points.front().facet_idx) - return false; - return true; - }; - - struct Plane - { - Vec3d origin; - Vec3d first_axis; - Vec3d second_axis; - }; - auto find_plane = [](std::vector &hit_points) -> std::optional { - assert(hit_points.size() >= 3); - for (size_t third_idx = 2; third_idx < hit_points.size(); ++third_idx) { - const Vec3d &first_point = hit_points[third_idx - 2].mesh_hit.cast(); - const Vec3d &second_point = hit_points[third_idx - 1].mesh_hit.cast(); - const Vec3d &third_point = hit_points[third_idx].mesh_hit.cast(); - - const Vec3d first_vec = first_point - second_point; - const Vec3d second_vec = third_point - second_point; - - // If three points aren't collinear, then there exists only one plane going through all points. - if (first_vec.cross(second_vec).squaredNorm() > sqr(EPSILON)) { - const Vec3d first_axis_vec_n = first_vec.normalized(); - // Make second_vec perpendicular to first_axis_vec_n using Gram–Schmidt orthogonalization process - const Vec3d second_axis_vec_n = (second_vec - (first_vec.dot(second_vec) / first_vec.dot(first_vec)) * first_vec).normalized(); - return Plane{second_point, first_axis_vec_n, second_axis_vec_n}; - } - } - - return std::nullopt; - }; - - for(std::vector &hit_points : mesh_hit_points_by_mesh) { - assert(!hit_points.empty()); - if (hit_points.back().mesh_idx == -1) - break; - - if (hit_points.size() <= 2) - continue; - - if (on_same_facet(hit_points)) { - hit_points = {hit_points.front(), hit_points.back()}; - } else if (std::optional plane = find_plane(hit_points); plane) { - Polyline polyline; - polyline.points.reserve(hit_points.size()); - // Project hit_points into its plane to simplified them in the next step. - for (auto &hit_point : hit_points) { - const Vec3d &point = hit_point.mesh_hit.cast(); - const double x_cord = plane->first_axis.dot(point - plane->origin); - const double y_cord = plane->second_axis.dot(point - plane->origin); - polyline.points.emplace_back(scale_(x_cord), scale_(y_cord)); - } - - polyline.simplify(scale_(m_cursor_radius) / 10.); - - const int mesh_idx = hit_points.front().mesh_idx; - std::vector new_hit_points; - new_hit_points.reserve(polyline.points.size()); - // Project 2D simplified hit_points beck to 3D. - for (const Point &point : polyline.points) { - const double x_cord = unscale(point.x()); - const double y_cord = unscale(point.y()); - const Vec3d new_hit_point = plane->origin + x_cord * plane->first_axis + y_cord * plane->second_axis; - const int facet_idx = m_c->raycaster()->raycasters()[mesh_idx]->get_closest_facet(new_hit_point.cast()); - new_hit_points.push_back({new_hit_point.cast(), mesh_idx, size_t(facet_idx)}); - } - - hit_points = new_hit_points; - } else { - hit_points = {hit_points.front(), hit_points.back()}; - } - } - - return mesh_hit_points_by_mesh; -} - -// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. -// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is -// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo -// concludes that the event was not intended for it, it should return false. -bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) -{ - if (action == SLAGizmoEventType::MouseWheelUp - || action == SLAGizmoEventType::MouseWheelDown) { - if (control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = action == SLAGizmoEventType::MouseWheelDown - ? std::max(0., pos - 0.01) - : std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - else if (alt_down) { - if (m_tool_type == ToolType::BRUSH && (m_cursor_type == TriangleSelector::CursorType::SPHERE || m_cursor_type == TriangleSelector::CursorType::CIRCLE)) { - m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - this->get_cursor_radius_step(), this->get_cursor_radius_min()) - : std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max()); - m_parent.set_as_dirty(); - return true; - } else if (m_tool_type == ToolType::SMART_FILL) { - m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin) - : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); - m_parent.set_as_dirty(); - if (m_rr.mesh_id != -1) { - const Selection &selection = m_parent.get_selection(); - const ModelObject *mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true); - const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix(); - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle, - m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); - m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); - m_seed_fill_last_mesh_id = m_rr.mesh_id; - } - return true; - } - - return false; - } - } - - if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); - return true; - } - - if (action == SLAGizmoEventType::LeftDown - || action == SLAGizmoEventType::RightDown - || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) { - - if (m_triangle_selectors.empty()) - return false; - - EnforcerBlockerType new_state = EnforcerBlockerType::NONE; - if (! shift_down) { - if (action == SLAGizmoEventType::Dragging) - new_state = m_button_down == Button::Left ? this->get_left_button_state_type() : this->get_right_button_state_type(); - else - new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type(); - } - - const Camera &camera = wxGetApp().plater()->get_camera(); - const Selection &selection = m_parent.get_selection(); - const ModelObject *mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d instance_trafo = mi->get_transformation().get_matrix(); - const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); - - // Precalculate transformations of individual meshes. - std::vector trafo_matrices; - std::vector trafo_matrices_not_translate; - for (const ModelVolume *mv : mo->volumes) - if (mv->is_model_part()) { - trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); - trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); - } - - std::vector> projected_mouse_positions_by_mesh = get_projected_mouse_positions(mouse_position, 1., trafo_matrices); - m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved - - for (const std::vector &projected_mouse_positions : projected_mouse_positions_by_mesh) { - assert(!projected_mouse_positions.empty()); - const int mesh_idx = projected_mouse_positions.front().mesh_idx; - const bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); - - // The mouse button click detection is enabled when there is a valid hit. - // Missing the object entirely - // shall not capture the mouse. - if (mesh_idx != -1) - if (m_button_down == Button::None) - m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); - - // In case we have no valid hit, we can return. The event will be stopped when - // dragging while painting (to prevent scene rotations and moving the object) - if (mesh_idx == -1) - return dragging_while_painting; - - const Transform3d &trafo_matrix = trafo_matrices[mesh_idx]; - const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[mesh_idx]; - - // Calculate direction from camera to the hit (in mesh coords): - Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); - - assert(mesh_idx < int(m_triangle_selectors.size())); - const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix); - if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) { - for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) { - assert(projected_mouse_position.mesh_idx == mesh_idx); - const Vec3f mesh_hit = projected_mouse_position.mesh_hit; - const int facet_idx = int(projected_mouse_position.facet_idx); - m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state); - if (m_tool_type == ToolType::SMART_FILL) - m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, - m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); - else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) - m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true); - else if (m_tool_type == ToolType::BUCKET_FILL) - m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true); - - m_seed_fill_last_mesh_id = -1; - } - } else if (m_tool_type == ToolType::BRUSH) { - assert(m_cursor_type == TriangleSelector::CursorType::CIRCLE || m_cursor_type == TriangleSelector::CursorType::SPHERE); - - if (projected_mouse_positions.size() == 1) { - const ProjectedMousePosition &first_position = projected_mouse_positions.front(); - std::unique_ptr cursor = TriangleSelector::SinglePointCursor::cursor_factory(first_position.mesh_hit, - camera_pos, m_cursor_radius, - m_cursor_type, trafo_matrix, clp); - m_triangle_selectors[mesh_idx]->select_patch(int(first_position.facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, - m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); - } else { - for (auto first_position_it = projected_mouse_positions.cbegin(); first_position_it != projected_mouse_positions.cend() - 1; ++first_position_it) { - auto second_position_it = first_position_it + 1; - std::unique_ptr cursor = TriangleSelector::DoublePointCursor::cursor_factory(first_position_it->mesh_hit, second_position_it->mesh_hit, camera_pos, m_cursor_radius, m_cursor_type, trafo_matrix, clp); - m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); - } - } - } - - m_triangle_selectors[mesh_idx]->request_update_render_data(); - m_last_mouse_click = mouse_position; - } - - return true; - } - - if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) { - if (m_triangle_selectors.empty()) - return false; - - const Camera &camera = wxGetApp().plater()->get_camera(); - const Selection &selection = m_parent.get_selection(); - const ModelObject *mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - const Transform3d instance_trafo = mi->get_transformation().get_matrix(); - const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); - - // Precalculate transformations of individual meshes. - std::vector trafo_matrices; - std::vector trafo_matrices_not_translate; - for (const ModelVolume *mv : mo->volumes) - if (mv->is_model_part()) { - trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); - trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); - } - - // Now "click" into all the prepared points and spill paint around them. - update_raycast_cache(mouse_position, camera, trafo_matrices); - - auto seed_fill_unselect_all = [this]() { - for (auto &triangle_selector : m_triangle_selectors) { - triangle_selector->seed_fill_unselect_all_triangles(); - triangle_selector->request_update_render_data(); - } - }; - - if (m_rr.mesh_id == -1) { - // Clean selected by seed fill for all triangles in all meshes when a mouse isn't pointing on any mesh. - seed_fill_unselect_all(); - m_seed_fill_last_mesh_id = -1; - - // In case we have no valid hit, we can return. - return false; - } - - // The mouse moved from one object's volume to another one. So it is needed to unselect all triangles selected by seed fill. - if(m_rr.mesh_id != m_seed_fill_last_mesh_id) - seed_fill_unselect_all(); - - const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id]; - const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id]; - - assert(m_rr.mesh_id < int(m_triangle_selectors.size())); - const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix); - if (m_tool_type == ToolType::SMART_FILL) - m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle, - m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); - else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) - m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, false); - else if (m_tool_type == ToolType::BUCKET_FILL) - m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, true); - m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); - m_seed_fill_last_mesh_id = m_rr.mesh_id; - return true; - } - - if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) - && m_button_down != Button::None) { - // Take snapshot and update ModelVolume data. - wxString action_name = this->handle_snapshot_action_name(shift_down, m_button_down); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), action_name, UndoRedo::SnapshotType::GizmoAction); - update_model_object(); - - m_button_down = Button::None; - m_last_mouse_click = Vec2d::Zero(); - return true; - } - - return false; -} - -bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event) -{ - // wxCoord == int --> wx/types.h - Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); - Vec2d mouse_pos = mouse_coord.cast(); - - if (mouse_event.Moving()) { - gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false); - return false; - } - - // when control is down we allow scene pan and rotation even when clicking - // over some object - bool control_down = mouse_event.CmdDown(); - bool grabber_contains_mouse = (get_hover_id() != -1); - - const Selection &selection = m_parent.get_selection(); - int selected_object_idx = selection.get_object_idx(); - if (mouse_event.LeftDown()) { - if ((!control_down || grabber_contains_mouse) && - gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) - // the gizmo got the event and took some action, there is no need - // to do anything more - return true; - } else if (mouse_event.RightDown()){ - if (!control_down && selected_object_idx != -1 && - gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) - // event was taken care of - return true; - } else if (mouse_event.Dragging()) { - if (m_parent.get_move_volume_id() != -1) - // don't allow dragging objects with the Sla gizmo on - return true; - if (!control_down && gizmo_event(SLAGizmoEventType::Dragging, - mouse_pos, mouse_event.ShiftDown(), - mouse_event.AltDown(), false)) { - // the gizmo got the event and took some action, no need to do - // anything more here - m_parent.set_as_dirty(); - return true; - } - if(control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())) - { - // CTRL has been pressed while already dragging -> stop current action - if (mouse_event.LeftIsDown()) - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); - else if (mouse_event.RightIsDown()) - gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); - return false; - } - } else if (mouse_event.LeftUp()) { - if (!m_parent.is_mouse_dragging()) { - // in case SLA/FDM gizmo is selected, we just pass the LeftUp - // event and stop processing - neither object moving or selecting - // is suppressed in that case - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); - return true; - } - } else if (mouse_event.RightUp()) { - if (!m_parent.is_mouse_dragging()) { - gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); - return true; - } - } - return false; -} - -void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position, - const Camera& camera, - const std::vector& trafo_matrices) const -{ - if (m_rr.mouse_position == mouse_position) { - // Same query as last time - the answer is already in the cache. - return; - } - - Vec3f normal = Vec3f::Zero(); - Vec3f hit = Vec3f::Zero(); - size_t facet = 0; - Vec3f closest_hit = Vec3f::Zero(); - double closest_hit_squared_distance = std::numeric_limits::max(); - size_t closest_facet = 0; - int closest_hit_mesh_id = -1; - - // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh - for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) { - - if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( - mouse_position, - trafo_matrices[mesh_id], - camera, - hit, - normal, - m_c->object_clipper()->get_clipping_plane(), - &facet)) - { - // In case this hit is clipped, skip it. - if (is_mesh_point_clipped(hit.cast(), trafo_matrices[mesh_id])) - continue; - - // Is this hit the closest to the camera so far? - double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); - if (hit_squared_distance < closest_hit_squared_distance) { - closest_hit_squared_distance = hit_squared_distance; - closest_facet = facet; - closest_hit_mesh_id = mesh_id; - closest_hit = hit; - } - } - } - - m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_facet}; -} - -bool GLGizmoPainterBase::on_is_activable() const -{ - const Selection& selection = m_parent.get_selection(); - - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF - || !selection.is_single_full_instance() || wxGetApp().get_mode() == comSimple) - return false; - - // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. - const Selection::IndicesList& list = selection.get_volume_idxs(); - return std::all_of(list.cbegin(), list.cend(), [&selection](unsigned int idx) { return !selection.get_volume(idx)->is_outside; }); -} - -bool GLGizmoPainterBase::on_is_selectable() const -{ - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF - && wxGetApp().get_mode() != comSimple ); -} - - -CommonGizmosDataID GLGizmoPainterBase::on_get_requirements() const -{ - return CommonGizmosDataID( - int(CommonGizmosDataID::SelectionInfo) - | int(CommonGizmosDataID::InstancesHider) - | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::ObjectClipper)); -} - - -void GLGizmoPainterBase::on_set_state() -{ - if (m_state == m_old_state) - return; - - if (m_state == On && m_old_state != On) { // the gizmo was just turned on - on_opening(); - } - if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - // we are actually shutting down - on_shutdown(); - m_old_mo_id = -1; - //m_iva.release_geometry(); - m_triangle_selectors.clear(); - } - m_old_state = m_state; -} - - - -void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&) -{ - // We should update the gizmo from current ModelObject, but it is not - // possible at this point. That would require having updated selection and - // common gizmos data, which is not done at this point. Instead, save - // a flag to do the update in set_painter_gizmo_data, which will be called - // soon after. - m_schedule_update = true; -} - -TriangleSelector::ClippingPlane GLGizmoPainterBase::get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const { - const ::Slic3r::GUI::ClippingPlane *const clipping_plane = m_c->object_clipper()->get_clipping_plane(); - if (clipping_plane == nullptr || !clipping_plane->is_active()) - return {}; - - const Vec3d clp_normal = clipping_plane->get_normal(); - const double clp_offset = clipping_plane->get_offset(); - - const Transform3d trafo_normal = Transform3d(trafo.linear().transpose()); - const Transform3d trafo_inv = trafo.inverse(); - - Vec3d point_on_plane = clp_normal * clp_offset; - Vec3d point_on_plane_transformed = trafo_inv * point_on_plane; - Vec3d normal_transformed = trafo_normal * clp_normal; - auto offset_transformed = float(point_on_plane_transformed.dot(normal_transformed)); - - return TriangleSelector::ClippingPlane({float(normal_transformed.x()), float(normal_transformed.y()), float(normal_transformed.z()), offset_transformed}); -} - -ColorRGBA TriangleSelectorGUI::get_seed_fill_color(const ColorRGBA& base_color) -{ - return saturate(base_color, 0.75f); -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void TriangleSelectorGUI::render(ImGuiWrapper* imgui, const Transform3d& matrix) -#else -void TriangleSelectorGUI::render(ImGuiWrapper* imgui) -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ - static const ColorRGBA enforcers_color = { 0.47f, 0.47f, 1.0f, 1.0f }; - static const ColorRGBA blockers_color = { 1.0f, 0.44f, 0.44f, 1.0f }; - - if (m_update_render_data) { - update_render_data(); - m_update_render_data = false; - } - - auto* shader = wxGetApp().get_current_shader(); - if (! shader) - return; - - assert(shader->get_name() == "gouraud"); - - for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color), - std::make_pair(&m_iva_blockers, blockers_color)}) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - iva.first->set_color(iva.second); - iva.first->render(); -#else - if (iva.first->has_VBOs()) { - shader->set_uniform("uniform_color", iva.second); - iva.first->render(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (auto& iva : m_iva_seed_fills) { - size_t color_idx = &iva - &m_iva_seed_fills.front(); - const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : - color_idx == 2 ? blockers_color : - GLVolume::NEUTRAL_COLOR); - iva.set_color(color); - iva.render(); - } -#else - for (auto& iva : m_iva_seed_fills) - if (iva.has_VBOs()) { - size_t color_idx = &iva - &m_iva_seed_fills.front(); - const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : - color_idx == 2 ? blockers_color : - GLVolume::NEUTRAL_COLOR); - shader->set_uniform("uniform_color", color); - iva.render(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - render_paint_contour(matrix); -#else - render_paint_contour(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -#else - if (m_paint_contour.has_VBO()) { - ScopeGuard guard_gouraud([shader]() { shader->start_using(); }); - shader->stop_using(); - - auto *contour_shader = wxGetApp().get_shader("mm_contour"); - contour_shader->start_using(); - contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); - m_paint_contour.render(); - contour_shader->stop_using(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG - if (imgui) - render_debug(imgui); - else - assert(false); // If you want debug output, pass ptr to ImGuiWrapper. -#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG -} - -void TriangleSelectorGUI::update_render_data() -{ - int enf_cnt = 0; - int blc_cnt = 0; - std::vector seed_fill_cnt(m_iva_seed_fills.size(), 0); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) { - iva->reset(); - } - - for (auto& iva : m_iva_seed_fills) { - iva.reset(); - } - - GLModel::Geometry iva_enforcers_data; - iva_enforcers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - GLModel::Geometry iva_blockers_data; - iva_blockers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - std::array iva_seed_fills_data; - for (auto& data : iva_seed_fills_data) - data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; -#else - for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) - iva->release_geometry(); - - for (auto &iva : m_iva_seed_fills) - iva.release_geometry(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - // small value used to offset triangles along their normal to avoid z-fighting - static const float offset = 0.001f; - - for (const Triangle &tr : m_triangles) { - if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill())) - continue; - - int tr_state = int(tr.get_state()); -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] : - tr.get_state() == EnforcerBlockerType::ENFORCER ? iva_enforcers_data : - iva_blockers_data; -#else - GLIndexedVertexArray &iva = tr.is_selected_by_seed_fill() ? m_iva_seed_fills[tr_state] : - tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : - m_iva_blockers; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] : - tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : - blc_cnt; - const Vec3f &v0 = m_vertices[tr.verts_idxs[0]].v; - const Vec3f &v1 = m_vertices[tr.verts_idxs[1]].v; - const Vec3f &v2 = m_vertices[tr.verts_idxs[2]].v; - //FIXME the normal may likely be pulled from m_triangle_selectors, but it may not be worth the effort - // or the current implementation may be more cache friendly. - const Vec3f n = (v1 - v0).cross(v2 - v1).normalized(); - // small value used to offset triangles along their normal to avoid z-fighting - const Vec3f offset_n = offset * n; -#if ENABLE_LEGACY_OPENGL_REMOVAL - iva.add_vertex(v0 + offset_n, n); - iva.add_vertex(v1 + offset_n, n); - iva.add_vertex(v2 + offset_n, n); - iva.add_triangle((unsigned int)cnt, (unsigned int)cnt + 1, (unsigned int)cnt + 2); -#else - iva.push_geometry(v0 + offset_n, n); - iva.push_geometry(v1 + offset_n, n); - iva.push_geometry(v2 + offset_n, n); - iva.push_triangle(cnt, cnt + 1, cnt + 2); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - cnt += 3; - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!iva_enforcers_data.is_empty()) - m_iva_enforcers.init_from(std::move(iva_enforcers_data)); - if (!iva_blockers_data.is_empty()) - m_iva_blockers.init_from(std::move(iva_blockers_data)); - for (size_t i = 0; i < m_iva_seed_fills.size(); ++i) { - if (!iva_seed_fills_data[i].is_empty()) - m_iva_seed_fills[i].init_from(std::move(iva_seed_fills_data[i])); - } - - update_paint_contour(); -#else - for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) - iva->finalize_geometry(true); - - for (auto &iva : m_iva_seed_fills) - iva.finalize_geometry(true); - - m_paint_contour.release_geometry(); - std::vector contour_edges = this->get_seed_fill_contour(); - m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6); - for (const Vec2i &edge : contour_edges) { - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.x()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.y()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.z()); - - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.x()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.y()); - m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.z()); - } - - m_paint_contour.contour_indices.assign(m_paint_contour.contour_vertices.size() / 3, 0); - std::iota(m_paint_contour.contour_indices.begin(), m_paint_contour.contour_indices.end(), 0); - m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size(); - - m_paint_contour.finalize_geometry(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - -#if !ENABLE_LEGACY_OPENGL_REMOVAL -void GLPaintContour::render() const -{ - assert(this->m_contour_VBO_id != 0); - assert(this->m_contour_EBO_id != 0); - - glsafe(::glLineWidth(4.0f)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); - - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - - if (this->contour_indices_size > 0) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); - glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); -} - -void GLPaintContour::finalize_geometry() -{ - assert(this->m_contour_VBO_id == 0); - assert(this->m_contour_EBO_id == 0); - - if (!this->contour_vertices.empty()) { - glsafe(::glGenBuffers(1, &this->m_contour_VBO_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - this->contour_vertices.clear(); - } - - if (!this->contour_indices.empty()) { - glsafe(::glGenBuffers(1, &this->m_contour_EBO_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - this->contour_indices.clear(); - } -} - -void GLPaintContour::release_geometry() -{ - if (this->m_contour_VBO_id) { - glsafe(::glDeleteBuffers(1, &this->m_contour_VBO_id)); - this->m_contour_VBO_id = 0; - } - if (this->m_contour_EBO_id) { - glsafe(::glDeleteBuffers(1, &this->m_contour_EBO_id)); - this->m_contour_EBO_id = 0; - } - this->clear(); -} -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - -#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG -void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) -{ - imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"), - ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - static float edge_limit = 1.f; - imgui->text("Edge limit (mm): "); - imgui->slider_float("", &edge_limit, 0.1f, 8.f); - set_edge_limit(edge_limit); - imgui->checkbox("Show split triangles: ", m_show_triangles); - imgui->checkbox("Show invalid triangles: ", m_show_invalid); - - int valid_triangles = m_triangles.size() - m_invalid_triangles; - imgui->text("Valid triangles: " + std::to_string(valid_triangles) + - "/" + std::to_string(m_triangles.size())); - imgui->text("Vertices: " + std::to_string(m_vertices.size())); - if (imgui->button("Force garbage collection")) - garbage_collect(); - - if (imgui->button("Serialize - deserialize")) { - auto map = serialize(); - deserialize(map); - } - - imgui->end(); - - if (! m_show_triangles) - return; - - enum vtype { - ORIGINAL = 0, - SPLIT, - INVALID - }; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (auto& va : m_varrays) - va.reset(); -#else - for (auto& va : m_varrays) - va.release_geometry(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - std::array cnts; - - ::glScalef(1.01f, 1.01f, 1.01f); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - std::array varrays_data; - for (auto& data : varrays_data) - data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - for (int tr_id=0; tr_idadd_vertex(m_vertices[tr.verts_idxs[i]].v, Vec3f(0.0f, 0.0f, 1.0f)); - } - va->add_uint_triangle((unsigned int)*cnt, (unsigned int)*cnt + 1, (unsigned int)*cnt + 2); -#else - for (int i = 0; i < 3; ++i) - va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), - double(m_vertices[tr.verts_idxs[i]].v[1]), - double(m_vertices[tr.verts_idxs[i]].v[2]), - 0., 0., 1.); - va->push_triangle(*cnt, - *cnt + 1, - *cnt + 2); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - *cnt += 3; - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - for (int i = 0; i < 3; ++i) { - if (!varrays_data[i].is_empty()) - m_varrays[i].init_from(std::move(varrays_data[i])); - } -#else -// for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) -// iva->finalize_geometry(true); -// -// for (auto& iva : m_iva_seed_fills) -// iva.finalize_geometry(true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); - if (curr_shader != nullptr) - curr_shader->stop_using(); - - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); - -#if ENABLE_GL_SHADERS_ATTRIBUTES - 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()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - for (vtype i : {ORIGINAL, SPLIT, INVALID}) { -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLModel& va = m_varrays[i]; - switch (i) { - case ORIGINAL: va.set_color({ 0.0f, 0.0f, 1.0f, 1.0f }); break; - case SPLIT: va.set_color({ 1.0f, 0.0f, 0.0f, 1.0f }); break; - case INVALID: va.set_color({ 1.0f, 1.0f, 0.0f, 1.0f }); break; - } - va.render(); -#else - GLIndexedVertexArray& va = m_varrays[i]; - va.finalize_geometry(true); - if (va.has_VBOs()) { - switch (i) { - case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; - case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break; - case INVALID : ::glColor3f(1.f, 1.f, 0.f); break; - } - va.render(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); - } - - if (curr_shader != nullptr) - curr_shader->start_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} -#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG - -#if ENABLE_LEGACY_OPENGL_REMOVAL -void TriangleSelectorGUI::update_paint_contour() -{ - m_paint_contour.reset(); - - GLModel::Geometry init_data; - const std::vector contour_edges = this->get_seed_fill_contour(); - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.reserve_vertices(2 * contour_edges.size()); - init_data.reserve_indices(2 * contour_edges.size()); -#if ENABLE_GL_SHADERS_ATTRIBUTES - init_data.color = ColorRGBA::WHITE(); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -// - // vertices + indices - unsigned int vertices_count = 0; - for (const Vec2i& edge : contour_edges) { - init_data.add_vertex(m_vertices[edge(0)].v); - init_data.add_vertex(m_vertices[edge(1)].v); - vertices_count += 2; - init_data.add_line(vertices_count - 2, vertices_count - 1); - } - - if (!init_data.is_empty()) - m_paint_contour.init_from(std::move(init_data)); -} - -#if ENABLE_GL_SHADERS_ATTRIBUTES -void TriangleSelectorGUI::render_paint_contour(const Transform3d& matrix) -#else -void TriangleSelectorGUI::render_paint_contour() -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -{ - auto* curr_shader = wxGetApp().get_current_shader(); - if (curr_shader != nullptr) - curr_shader->stop_using(); - - auto* contour_shader = wxGetApp().get_shader("mm_contour"); - if (contour_shader != nullptr) { - contour_shader->start_using(); - - contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - contour_shader->set_uniform("view_model_matrix", camera.get_view_matrix() * matrix); - contour_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - m_paint_contour.render(); - contour_shader->stop_using(); - } - - if (curr_shader != nullptr) - curr_shader->start_using(); -} -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - -} // namespace Slic3r::GUI +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoPainterBase.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" + +#include + +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/OpenGLManager.hpp" +#include "slic3r/Utils/UndoRedo.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/TriangleMesh.hpp" + +#include +#include + +namespace Slic3r::GUI { + +#if ENABLE_LEGACY_OPENGL_REMOVAL +std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; +#else +std::shared_ptr GLGizmoPainterBase::s_sphere = nullptr; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +{ +} + +GLGizmoPainterBase::~GLGizmoPainterBase() +{ +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (s_sphere != nullptr) + s_sphere.reset(); +#else + if (s_sphere != nullptr && s_sphere->has_VBOs()) + s_sphere->release_geometry(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +void GLGizmoPainterBase::data_changed() +{ + if (m_state != On) + return; + + const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; + const Selection & selection = m_parent.get_selection(); + if (mo && selection.is_from_single_instance() + && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) + { + update_from_model_object(); + m_old_mo_id = mo->id(); + m_old_volumes_size = mo->volumes.size(); + m_schedule_update = false; + } +} + +GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_plane_data() const +{ + ClippingPlaneDataWrapper clp_data_out{{0.f, 0.f, 1.f, FLT_MAX}, {-FLT_MAX, FLT_MAX}}; + // Take care of the clipping plane. The normal of the clipping plane is + // saved with opposite sign than we need to pass to OpenGL (FIXME) + if (bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.; clipping_plane_active) { + const ClippingPlane *clp = m_c->object_clipper()->get_clipping_plane(); + for (size_t i = 0; i < 3; ++i) + clp_data_out.clp_dataf[i] = -1.f * float(clp->get_data()[i]); + clp_data_out.clp_dataf[3] = float(clp->get_data()[3]); + } + + // z_range is calculated in the same way as in GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) + if (m_c->get_canvas()->get_use_clipping_planes()) { + const std::array &clps = m_c->get_canvas()->get_clipping_planes(); + clp_data_out.z_range = {float(-clps[0].get_data()[3]), float(clps[1].get_data()[3])}; + } + + return clp_data_out; +} + +void GLGizmoPainterBase::render_triangles(const Selection& selection) const +{ + auto* shader = wxGetApp().get_shader("gouraud"); + if (! shader) + return; + shader->start_using(); + shader->set_uniform("slope.actived", false); + shader->set_uniform("print_volume.type", 0); + shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf); + ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); + + const ModelObject *mo = m_c->selection_info()->model_object(); + int mesh_id = -1; + for (const ModelVolume* mv : mo->volumes) { + if (! mv->is_model_part()) + continue; + + ++mesh_id; + + const Transform3d trafo_matrix = + mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * + mv->get_matrix(); + + bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d matrix = camera.get_view_matrix() * trafo_matrix; + shader->set_uniform("view_model_matrix", matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("normal_matrix", (Matrix3d)matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo_matrix.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + // For printers with multiple extruders, it is necessary to pass trafo_matrix + // to the shader input variable print_box.volume_world_matrix before + // rendering the painted triangles. When this matrix is not set, the + // wrong transformation matrix is used for "Clipping of view". + shader->set_uniform("volume_world_matrix", trafo_matrix); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + m_triangle_selectors[mesh_id]->render(m_imgui, trafo_matrix); +#else + m_triangle_selectors[mesh_id]->render(m_imgui); + + glsafe(::glPopMatrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); + } +} + +void GLGizmoPainterBase::render_cursor() +{ + // First check that the mouse pointer is on an object. + const ModelObject* mo = m_c->selection_info()->model_object(); + const Selection& selection = m_parent.get_selection(); + const ModelInstance* mi = mo->instances[selection.get_instance_idx()]; + const Camera& camera = wxGetApp().plater()->get_camera(); + + // Precalculate transformations of individual meshes. + std::vector trafo_matrices; + for (const ModelVolume* mv : mo->volumes) { + if (mv->is_model_part()) + trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); + } + // Raycast and return if there's no hit. + update_raycast_cache(m_parent.get_local_mouse_position(), camera, trafo_matrices); + if (m_rr.mesh_id == -1) + return; + + if (m_tool_type == ToolType::BRUSH) { + if (m_cursor_type == TriangleSelector::SPHERE) + render_cursor_sphere(trafo_matrices[m_rr.mesh_id]); + else if (m_cursor_type == TriangleSelector::CIRCLE) + render_cursor_circle(); + } +} + +void GLGizmoPainterBase::render_cursor_circle() +{ +#if !ENABLE_GL_SHADERS_ATTRIBUTES + const Camera &camera = wxGetApp().plater()->get_camera(); + const float zoom = float(camera.get_zoom()); + const float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + const Size cnv_size = m_parent.get_canvas_size(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const float cnv_width = float(cnv_size.get_width()); + const float cnv_height = float(cnv_size.get_height()); + if (cnv_width == 0.0f || cnv_height == 0.0f) + return; + + const float cnv_inv_width = 1.0f / cnv_width; + const float cnv_inv_height = 1.0f / cnv_height; + + const Vec2d center = m_parent.get_local_mouse_position(); + const float radius = m_cursor_radius * float(wxGetApp().plater()->get_camera().get_zoom()); +#else + const float cnv_half_width = 0.5f * float(cnv_size.get_width()); + const float cnv_half_height = 0.5f * float(cnv_size.get_height()); + if (cnv_half_width == 0.0f || cnv_half_height == 0.0f) + return; + const Vec2d mouse_pos(m_parent.get_local_mouse_position().x(), m_parent.get_local_mouse_position().y()); + Vec2d center(mouse_pos.x() - cnv_half_width, cnv_half_height - mouse_pos.y()); + center = center * inv_zoom; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + glsafe(::glLineWidth(1.5f)); +#if !ENABLE_LEGACY_OPENGL_REMOVAL + static const std::array color = { 0.f, 1.f, 0.3f }; + glsafe(::glColor3fv(color.data())); +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glDisable(GL_DEPTH_TEST)); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPushMatrix()); + glsafe(::glLoadIdentity()); + // ensure that the circle is renderered inside the frustrum + glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); + // ensure that the overlay fits the frustrum near z plane + const double gui_scale = camera.get_gui_scale(); + glsafe(::glScaled(gui_scale, gui_scale, 1.0)); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + glsafe(::glPushAttrib(GL_ENABLE_BIT)); + glsafe(::glLineStipple(4, 0xAAAA)); + glsafe(::glEnable(GL_LINE_STIPPLE)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - radius) > EPSILON) { + m_old_cursor_radius = radius; +#else + if (!m_circle.is_initialized() || !m_old_center.isApprox(center) || std::abs(m_old_cursor_radius - m_cursor_radius) > EPSILON) { + m_old_cursor_radius = m_cursor_radius; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_old_center = center; + m_circle.reset(); + + GLModel::Geometry init_data; + static const unsigned int StepsCount = 32; + static const float StepSize = 2.0f * float(PI) / float(StepsCount); + init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 }; + init_data.color = { 0.0f, 1.0f, 0.3f, 1.0f }; + init_data.reserve_vertices(StepsCount); + init_data.reserve_indices(StepsCount); + + // vertices + indices + for (unsigned int i = 0; i < StepsCount; ++i) { + const float angle = float(i) * StepSize; +#if ENABLE_GL_SHADERS_ATTRIBUTES + init_data.add_vertex(Vec2f(2.0f * ((center.x() + ::cos(angle) * radius) * cnv_inv_width - 0.5f), + -2.0f * ((center.y() + ::sin(angle) * radius) * cnv_inv_height - 0.5f))); +#else + init_data.add_vertex(Vec2f(center.x() + ::cos(angle) * m_cursor_radius, center.y() + ::sin(angle) * m_cursor_radius)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + init_data.add_index(i); + } + + m_circle.init_from(std::move(init_data)); + } + + GLShaderProgram* shader = GUI::wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + shader->set_uniform("view_model_matrix", Transform3d::Identity()); + shader->set_uniform("projection_matrix", Transform3d::Identity()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_circle.render(); + shader->stop_using(); + } +#else + ::glBegin(GL_LINE_LOOP); + for (double angle=0; angle<2*M_PI; angle+=M_PI/20.) + ::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle))); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glPopAttrib()); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glEnable(GL_DEPTH_TEST)); +} + + +void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const +{ + if (s_sphere == nullptr) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + s_sphere = std::make_shared(); + s_sphere->init_from(its_make_sphere(1.0, double(PI) / 12.0)); +#else + s_sphere = std::make_shared(); + s_sphere->load_its_flat_shading(its_make_sphere(1.0, double(PI) / 12.0)); + s_sphere->finalize_geometry(true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_scaling_factor_matrix().inverse(); +#else + const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed(); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPushMatrix()); + glsafe(::glMultMatrixd(trafo.data())); + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. + glsafe(::glTranslatef(m_rr.hit.x(), m_rr.hit.y(), m_rr.hit.z())); + glsafe(::glMultMatrixd(complete_scaling_matrix_inverse.data())); + glsafe(::glScaled(m_cursor_radius, m_cursor_radius, m_cursor_radius)); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + if (is_left_handed) + glFrontFace(GL_CW); + + ColorRGBA render_color = { 0.0f, 0.0f, 0.0f, 0.25f }; + if (m_button_down == Button::Left) + render_color = this->get_cursor_sphere_left_button_color(); + else if (m_button_down == Button::Right) + render_color = this->get_cursor_sphere_right_button_color(); +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->start_using(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d view_model_matrix = camera.get_view_matrix() * trafo * + Geometry::assemble_transform(m_rr.hit.cast()) * complete_scaling_matrix_inverse * + Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), m_cursor_radius * Vec3d::Ones()); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + assert(s_sphere != nullptr); + s_sphere->set_color(render_color); +#else + glsafe(::glColor4fv(render_color.data())); + + assert(s_sphere != nullptr); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + s_sphere->render(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + if (is_left_handed) + glFrontFace(GL_CCW); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +} + + +bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const +{ + if (m_c->object_clipper()->get_position() == 0.) + return false; + + auto sel_info = m_c->selection_info(); + Vec3d transformed_point = trafo * point; + transformed_point(2) += sel_info->get_sla_shift(); + return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); +} + +// Interpolate points between the previous and current mouse positions, which are then projected onto the object. +// Returned projected mouse positions are grouped by mesh_idx. It may contain multiple std::vector +// with the same mesh_idx, but all items in std::vector always have the same mesh_idx. +std::vector> GLGizmoPainterBase::get_projected_mouse_positions(const Vec2d &mouse_position, const double resolution, const std::vector &trafo_matrices) const +{ + // List of mouse positions that will be used as seeds for painting. + std::vector mouse_positions{mouse_position}; + if (m_last_mouse_click != Vec2d::Zero()) { + // In case current mouse position is far from the last one, + // add several positions from between into the list, so there + // are no gaps in the painted region. + if (size_t patches_in_between = size_t((mouse_position - m_last_mouse_click).norm() / resolution); patches_in_between > 0) { + const Vec2d diff = (m_last_mouse_click - mouse_position) / (patches_in_between + 1); + for (size_t patch_idx = 1; patch_idx <= patches_in_between; ++patch_idx) + mouse_positions.emplace_back(mouse_position + patch_idx * diff); + mouse_positions.emplace_back(m_last_mouse_click); + } + } + + const Camera &camera = wxGetApp().plater()->get_camera(); + std::vector mesh_hit_points; + mesh_hit_points.reserve(mouse_positions.size()); + + // In mesh_hit_points only the last item could have mesh_id == -1, any other items mustn't. + for (const Vec2d &mp : mouse_positions) { + update_raycast_cache(mp, camera, trafo_matrices); + mesh_hit_points.push_back({m_rr.hit, m_rr.mesh_id, m_rr.facet}); + if (m_rr.mesh_id == -1) + break; + } + + // Divide mesh_hit_points into groups with the same mesh_idx. It may contain multiple groups with the same mesh_idx. + std::vector> mesh_hit_points_by_mesh; + for (size_t prev_mesh_hit_point = 0, curr_mesh_hit_point = 0; curr_mesh_hit_point < mesh_hit_points.size(); ++curr_mesh_hit_point) { + size_t next_mesh_hit_point = curr_mesh_hit_point + 1; + if (next_mesh_hit_point >= mesh_hit_points.size() || mesh_hit_points[curr_mesh_hit_point].mesh_idx != mesh_hit_points[next_mesh_hit_point].mesh_idx) { + mesh_hit_points_by_mesh.emplace_back(); + mesh_hit_points_by_mesh.back().insert(mesh_hit_points_by_mesh.back().end(), mesh_hit_points.begin() + int(prev_mesh_hit_point), mesh_hit_points.begin() + int(next_mesh_hit_point)); + prev_mesh_hit_point = next_mesh_hit_point; + } + } + + auto on_same_facet = [](std::vector &hit_points) -> bool { + for (const ProjectedMousePosition &mesh_hit_point : hit_points) + if (mesh_hit_point.facet_idx != hit_points.front().facet_idx) + return false; + return true; + }; + + struct Plane + { + Vec3d origin; + Vec3d first_axis; + Vec3d second_axis; + }; + auto find_plane = [](std::vector &hit_points) -> std::optional { + assert(hit_points.size() >= 3); + for (size_t third_idx = 2; third_idx < hit_points.size(); ++third_idx) { + const Vec3d &first_point = hit_points[third_idx - 2].mesh_hit.cast(); + const Vec3d &second_point = hit_points[third_idx - 1].mesh_hit.cast(); + const Vec3d &third_point = hit_points[third_idx].mesh_hit.cast(); + + const Vec3d first_vec = first_point - second_point; + const Vec3d second_vec = third_point - second_point; + + // If three points aren't collinear, then there exists only one plane going through all points. + if (first_vec.cross(second_vec).squaredNorm() > sqr(EPSILON)) { + const Vec3d first_axis_vec_n = first_vec.normalized(); + // Make second_vec perpendicular to first_axis_vec_n using Gram–Schmidt orthogonalization process + const Vec3d second_axis_vec_n = (second_vec - (first_vec.dot(second_vec) / first_vec.dot(first_vec)) * first_vec).normalized(); + return Plane{second_point, first_axis_vec_n, second_axis_vec_n}; + } + } + + return std::nullopt; + }; + + for(std::vector &hit_points : mesh_hit_points_by_mesh) { + assert(!hit_points.empty()); + if (hit_points.back().mesh_idx == -1) + break; + + if (hit_points.size() <= 2) + continue; + + if (on_same_facet(hit_points)) { + hit_points = {hit_points.front(), hit_points.back()}; + } else if (std::optional plane = find_plane(hit_points); plane) { + Polyline polyline; + polyline.points.reserve(hit_points.size()); + // Project hit_points into its plane to simplified them in the next step. + for (auto &hit_point : hit_points) { + const Vec3d &point = hit_point.mesh_hit.cast(); + const double x_cord = plane->first_axis.dot(point - plane->origin); + const double y_cord = plane->second_axis.dot(point - plane->origin); + polyline.points.emplace_back(scale_(x_cord), scale_(y_cord)); + } + + polyline.simplify(scale_(m_cursor_radius) / 10.); + + const int mesh_idx = hit_points.front().mesh_idx; + std::vector new_hit_points; + new_hit_points.reserve(polyline.points.size()); + // Project 2D simplified hit_points beck to 3D. + for (const Point &point : polyline.points) { + const double x_cord = unscale(point.x()); + const double y_cord = unscale(point.y()); + const Vec3d new_hit_point = plane->origin + x_cord * plane->first_axis + y_cord * plane->second_axis; + const int facet_idx = m_c->raycaster()->raycasters()[mesh_idx]->get_closest_facet(new_hit_point.cast()); + new_hit_points.push_back({new_hit_point.cast(), mesh_idx, size_t(facet_idx)}); + } + + hit_points = new_hit_points; + } else { + hit_points = {hit_points.front(), hit_points.back()}; + } + } + + return mesh_hit_points_by_mesh; +} + +// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. +// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is +// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo +// concludes that the event was not intended for it, it should return false. +bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + if (action == SLAGizmoEventType::MouseWheelUp + || action == SLAGizmoEventType::MouseWheelDown) { + if (control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = action == SLAGizmoEventType::MouseWheelDown + ? std::max(0., pos - 0.01) + : std::min(1., pos + 0.01); + m_c->object_clipper()->set_position(pos, true); + return true; + } + else if (alt_down) { + if (m_tool_type == ToolType::BRUSH && (m_cursor_type == TriangleSelector::CursorType::SPHERE || m_cursor_type == TriangleSelector::CursorType::CIRCLE)) { + m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - this->get_cursor_radius_step(), this->get_cursor_radius_min()) + : std::min(m_cursor_radius + this->get_cursor_radius_step(), this->get_cursor_radius_max()); + m_parent.set_as_dirty(); + return true; + } else if (m_tool_type == ToolType::SMART_FILL) { + m_smart_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_smart_fill_angle - SmartFillAngleStep, SmartFillAngleMin) + : std::min(m_smart_fill_angle + SmartFillAngleStep, SmartFillAngleMax); + m_parent.set_as_dirty(); + if (m_rr.mesh_id != -1) { + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset(); +#else + const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix(); + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); + m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); + m_seed_fill_last_mesh_id = m_rr.mesh_id; + } + return true; + } + + return false; + } + } + + if (action == SLAGizmoEventType::ResetClippingPlane) { + m_c->object_clipper()->set_position(-1., false); + return true; + } + + if (action == SLAGizmoEventType::LeftDown + || action == SLAGizmoEventType::RightDown + || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) { + + if (m_triangle_selectors.empty()) + return false; + + EnforcerBlockerType new_state = EnforcerBlockerType::NONE; + if (! shift_down) { + if (action == SLAGizmoEventType::Dragging) + new_state = m_button_down == Button::Left ? this->get_left_button_state_type() : this->get_right_button_state_type(); + else + new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type(); + } + + const Camera &camera = wxGetApp().plater()->get_camera(); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d instance_trafo = mi->get_transformation().get_matrix(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix_no_offset(); +#else + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + + // Precalculate transformations of individual meshes. + std::vector trafo_matrices; + std::vector trafo_matrices_not_translate; + for (const ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { + trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix_no_offset()); +#else + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + } + + std::vector> projected_mouse_positions_by_mesh = get_projected_mouse_positions(mouse_position, 1., trafo_matrices); + m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved + + for (const std::vector &projected_mouse_positions : projected_mouse_positions_by_mesh) { + assert(!projected_mouse_positions.empty()); + const int mesh_idx = projected_mouse_positions.front().mesh_idx; + const bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); + + // The mouse button click detection is enabled when there is a valid hit. + // Missing the object entirely + // shall not capture the mouse. + if (mesh_idx != -1) + if (m_button_down == Button::None) + m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); + + // In case we have no valid hit, we can return. The event will be stopped when + // dragging while painting (to prevent scene rotations and moving the object) + if (mesh_idx == -1) + return dragging_while_painting; + + const Transform3d &trafo_matrix = trafo_matrices[mesh_idx]; + const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[mesh_idx]; + + // Calculate direction from camera to the hit (in mesh coords): + Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); + + assert(mesh_idx < int(m_triangle_selectors.size())); + const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix); + if (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) { + for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) { + assert(projected_mouse_position.mesh_idx == mesh_idx); + const Vec3f mesh_hit = projected_mouse_position.mesh_hit; + const int facet_idx = int(projected_mouse_position.facet_idx); + m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state); + if (m_tool_type == ToolType::SMART_FILL) + m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); + else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) + m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true); + else if (m_tool_type == ToolType::BUCKET_FILL) + m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true); + + m_seed_fill_last_mesh_id = -1; + } + } else if (m_tool_type == ToolType::BRUSH) { + assert(m_cursor_type == TriangleSelector::CursorType::CIRCLE || m_cursor_type == TriangleSelector::CursorType::SPHERE); + + if (projected_mouse_positions.size() == 1) { + const ProjectedMousePosition &first_position = projected_mouse_positions.front(); + std::unique_ptr cursor = TriangleSelector::SinglePointCursor::cursor_factory(first_position.mesh_hit, + camera_pos, m_cursor_radius, + m_cursor_type, trafo_matrix, clp); + m_triangle_selectors[mesh_idx]->select_patch(int(first_position.facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, + m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); + } else { + for (auto first_position_it = projected_mouse_positions.cbegin(); first_position_it != projected_mouse_positions.cend() - 1; ++first_position_it) { + auto second_position_it = first_position_it + 1; + std::unique_ptr cursor = TriangleSelector::DoublePointCursor::cursor_factory(first_position_it->mesh_hit, second_position_it->mesh_hit, camera_pos, m_cursor_radius, m_cursor_type, trafo_matrix, clp); + m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); + } + } + } + + m_triangle_selectors[mesh_idx]->request_update_render_data(); + m_last_mouse_click = mouse_position; + } + + return true; + } + + if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SMART_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) { + if (m_triangle_selectors.empty()) + return false; + + const Camera &camera = wxGetApp().plater()->get_camera(); + const Selection &selection = m_parent.get_selection(); + const ModelObject *mo = m_c->selection_info()->model_object(); + const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; + const Transform3d instance_trafo = mi->get_transformation().get_matrix(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix_no_offset(); +#else + const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + + // Precalculate transformations of individual meshes. + std::vector trafo_matrices; + std::vector trafo_matrices_not_translate; + for (const ModelVolume *mv : mo->volumes) + if (mv->is_model_part()) { + trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix_no_offset()); +#else + trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + } + + // Now "click" into all the prepared points and spill paint around them. + update_raycast_cache(mouse_position, camera, trafo_matrices); + + auto seed_fill_unselect_all = [this]() { + for (auto &triangle_selector : m_triangle_selectors) { + triangle_selector->seed_fill_unselect_all_triangles(); + triangle_selector->request_update_render_data(); + } + }; + + if (m_rr.mesh_id == -1) { + // Clean selected by seed fill for all triangles in all meshes when a mouse isn't pointing on any mesh. + seed_fill_unselect_all(); + m_seed_fill_last_mesh_id = -1; + + // In case we have no valid hit, we can return. + return false; + } + + // The mouse moved from one object's volume to another one. So it is needed to unselect all triangles selected by seed fill. + if(m_rr.mesh_id != m_seed_fill_last_mesh_id) + seed_fill_unselect_all(); + + const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id]; + const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id]; + + assert(m_rr.mesh_id < int(m_triangle_selectors.size())); + const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix); + if (m_tool_type == ToolType::SMART_FILL) + m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle, + m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f); + else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER) + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, false); + else if (m_tool_type == ToolType::BUCKET_FILL) + m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), clp, true); + m_triangle_selectors[m_rr.mesh_id]->request_update_render_data(); + m_seed_fill_last_mesh_id = m_rr.mesh_id; + return true; + } + + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) + && m_button_down != Button::None) { + // Take snapshot and update ModelVolume data. + wxString action_name = this->handle_snapshot_action_name(shift_down, m_button_down); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), action_name, UndoRedo::SnapshotType::GizmoAction); + update_model_object(); + + m_button_down = Button::None; + m_last_mouse_click = Vec2d::Zero(); + return true; + } + + return false; +} + +bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event) +{ + // wxCoord == int --> wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + + if (mouse_event.Moving()) { + gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false); + return false; + } + + // when control is down we allow scene pan and rotation even when clicking + // over some object + bool control_down = mouse_event.CmdDown(); + bool grabber_contains_mouse = (get_hover_id() != -1); + + const Selection &selection = m_parent.get_selection(); + int selected_object_idx = selection.get_object_idx(); + if (mouse_event.LeftDown()) { + if ((!control_down || grabber_contains_mouse) && + gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) + // the gizmo got the event and took some action, there is no need + // to do anything more + return true; + } else if (mouse_event.RightDown()){ + if (!control_down && selected_object_idx != -1 && + gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) + // event was taken care of + return true; + } else if (mouse_event.Dragging()) { + if (m_parent.get_move_volume_id() != -1) + // don't allow dragging objects with the Sla gizmo on + return true; + if (!control_down && gizmo_event(SLAGizmoEventType::Dragging, + mouse_pos, mouse_event.ShiftDown(), + mouse_event.AltDown(), false)) { + // the gizmo got the event and took some action, no need to do + // anything more here + m_parent.set_as_dirty(); + return true; + } + if(control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())) + { + // CTRL has been pressed while already dragging -> stop current action + if (mouse_event.LeftIsDown()) + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); + else if (mouse_event.RightIsDown()) + gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); + return false; + } + } else if (mouse_event.LeftUp()) { + if (!m_parent.is_mouse_dragging()) { + // in case SLA/FDM gizmo is selected, we just pass the LeftUp + // event and stop processing - neither object moving or selecting + // is suppressed in that case + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); + return true; + } + } else if (mouse_event.RightUp()) { + if (!m_parent.is_mouse_dragging()) { + gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), control_down); + return true; + } + } + return false; +} + +void GLGizmoPainterBase::update_raycast_cache(const Vec2d& mouse_position, + const Camera& camera, + const std::vector& trafo_matrices) const +{ + if (m_rr.mouse_position == mouse_position) { + // Same query as last time - the answer is already in the cache. + return; + } + + Vec3f normal = Vec3f::Zero(); + Vec3f hit = Vec3f::Zero(); + size_t facet = 0; + Vec3f closest_hit = Vec3f::Zero(); + double closest_hit_squared_distance = std::numeric_limits::max(); + size_t closest_facet = 0; + int closest_hit_mesh_id = -1; + + // Cast a ray on all meshes, pick the closest hit and save it for the respective mesh + for (int mesh_id = 0; mesh_id < int(trafo_matrices.size()); ++mesh_id) { + + if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh( + mouse_position, + trafo_matrices[mesh_id], + camera, + hit, + normal, + m_c->object_clipper()->get_clipping_plane(), + &facet)) + { + // In case this hit is clipped, skip it. + if (is_mesh_point_clipped(hit.cast(), trafo_matrices[mesh_id])) + continue; + + // Is this hit the closest to the camera so far? + double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast()).squaredNorm(); + if (hit_squared_distance < closest_hit_squared_distance) { + closest_hit_squared_distance = hit_squared_distance; + closest_facet = facet; + closest_hit_mesh_id = mesh_id; + closest_hit = hit; + } + } + } + + m_rr = {mouse_position, closest_hit_mesh_id, closest_hit, closest_facet}; +} + +bool GLGizmoPainterBase::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF + || !selection.is_single_full_instance() || wxGetApp().get_mode() == comSimple) + return false; + + // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. + const Selection::IndicesList& list = selection.get_volume_idxs(); + return std::all_of(list.cbegin(), list.cend(), [&selection](unsigned int idx) { return !selection.get_volume(idx)->is_outside; }); +} + +bool GLGizmoPainterBase::on_is_selectable() const +{ + return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF + && wxGetApp().get_mode() != comSimple ); +} + + +CommonGizmosDataID GLGizmoPainterBase::on_get_requirements() const +{ + return CommonGizmosDataID( + int(CommonGizmosDataID::SelectionInfo) + | int(CommonGizmosDataID::InstancesHider) + | int(CommonGizmosDataID::Raycaster) + | int(CommonGizmosDataID::ObjectClipper)); +} + + +void GLGizmoPainterBase::on_set_state() +{ + if (m_state == m_old_state) + return; + + if (m_state == On && m_old_state != On) { // the gizmo was just turned on + on_opening(); + } + if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off + // we are actually shutting down + on_shutdown(); + m_old_mo_id = -1; + //m_iva.release_geometry(); + m_triangle_selectors.clear(); + } + m_old_state = m_state; +} + + + +void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&) +{ + // We should update the gizmo from current ModelObject, but it is not + // possible at this point. That would require having updated selection and + // common gizmos data, which is not done at this point. Instead, save + // a flag to do the update in set_painter_gizmo_data, which will be called + // soon after. + m_schedule_update = true; +} + +TriangleSelector::ClippingPlane GLGizmoPainterBase::get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const { + const ::Slic3r::GUI::ClippingPlane *const clipping_plane = m_c->object_clipper()->get_clipping_plane(); + if (clipping_plane == nullptr || !clipping_plane->is_active()) + return {}; + + const Vec3d clp_normal = clipping_plane->get_normal(); + const double clp_offset = clipping_plane->get_offset(); + + const Transform3d trafo_normal = Transform3d(trafo.linear().transpose()); + const Transform3d trafo_inv = trafo.inverse(); + + Vec3d point_on_plane = clp_normal * clp_offset; + Vec3d point_on_plane_transformed = trafo_inv * point_on_plane; + Vec3d normal_transformed = trafo_normal * clp_normal; + auto offset_transformed = float(point_on_plane_transformed.dot(normal_transformed)); + + return TriangleSelector::ClippingPlane({float(normal_transformed.x()), float(normal_transformed.y()), float(normal_transformed.z()), offset_transformed}); +} + +ColorRGBA TriangleSelectorGUI::get_seed_fill_color(const ColorRGBA& base_color) +{ + return saturate(base_color, 0.75f); +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void TriangleSelectorGUI::render(ImGuiWrapper* imgui, const Transform3d& matrix) +#else +void TriangleSelectorGUI::render(ImGuiWrapper* imgui) +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ + static const ColorRGBA enforcers_color = { 0.47f, 0.47f, 1.0f, 1.0f }; + static const ColorRGBA blockers_color = { 1.0f, 0.44f, 0.44f, 1.0f }; + + if (m_update_render_data) { + update_render_data(); + m_update_render_data = false; + } + + auto* shader = wxGetApp().get_current_shader(); + if (! shader) + return; + + assert(shader->get_name() == "gouraud"); + + for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color), + std::make_pair(&m_iva_blockers, blockers_color)}) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + iva.first->set_color(iva.second); + iva.first->render(); +#else + if (iva.first->has_VBOs()) { + shader->set_uniform("uniform_color", iva.second); + iva.first->render(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (auto& iva : m_iva_seed_fills) { + size_t color_idx = &iva - &m_iva_seed_fills.front(); + const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : + color_idx == 2 ? blockers_color : + GLVolume::NEUTRAL_COLOR); + iva.set_color(color); + iva.render(); + } +#else + for (auto& iva : m_iva_seed_fills) + if (iva.has_VBOs()) { + size_t color_idx = &iva - &m_iva_seed_fills.front(); + const ColorRGBA& color = TriangleSelectorGUI::get_seed_fill_color(color_idx == 1 ? enforcers_color : + color_idx == 2 ? blockers_color : + GLVolume::NEUTRAL_COLOR); + shader->set_uniform("uniform_color", color); + iva.render(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + render_paint_contour(matrix); +#else + render_paint_contour(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#else + if (m_paint_contour.has_VBO()) { + ScopeGuard guard_gouraud([shader]() { shader->start_using(); }); + shader->stop_using(); + + auto *contour_shader = wxGetApp().get_shader("mm_contour"); + contour_shader->start_using(); + contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); + m_paint_contour.render(); + contour_shader->stop_using(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + if (imgui) + render_debug(imgui); + else + assert(false); // If you want debug output, pass ptr to ImGuiWrapper. +#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +} + +void TriangleSelectorGUI::update_render_data() +{ + int enf_cnt = 0; + int blc_cnt = 0; + std::vector seed_fill_cnt(m_iva_seed_fills.size(), 0); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) { + iva->reset(); + } + + for (auto& iva : m_iva_seed_fills) { + iva.reset(); + } + + GLModel::Geometry iva_enforcers_data; + iva_enforcers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + GLModel::Geometry iva_blockers_data; + iva_blockers_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + std::array iva_seed_fills_data; + for (auto& data : iva_seed_fills_data) + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; +#else + for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) + iva->release_geometry(); + + for (auto &iva : m_iva_seed_fills) + iva.release_geometry(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + // small value used to offset triangles along their normal to avoid z-fighting + static const float offset = 0.001f; + + for (const Triangle &tr : m_triangles) { + if (!tr.valid() || tr.is_split() || (tr.get_state() == EnforcerBlockerType::NONE && !tr.is_selected_by_seed_fill())) + continue; + + int tr_state = int(tr.get_state()); +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLModel::Geometry &iva = tr.is_selected_by_seed_fill() ? iva_seed_fills_data[tr_state] : + tr.get_state() == EnforcerBlockerType::ENFORCER ? iva_enforcers_data : + iva_blockers_data; +#else + GLIndexedVertexArray &iva = tr.is_selected_by_seed_fill() ? m_iva_seed_fills[tr_state] : + tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : + m_iva_blockers; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + int &cnt = tr.is_selected_by_seed_fill() ? seed_fill_cnt[tr_state] : + tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : + blc_cnt; + const Vec3f &v0 = m_vertices[tr.verts_idxs[0]].v; + const Vec3f &v1 = m_vertices[tr.verts_idxs[1]].v; + const Vec3f &v2 = m_vertices[tr.verts_idxs[2]].v; + //FIXME the normal may likely be pulled from m_triangle_selectors, but it may not be worth the effort + // or the current implementation may be more cache friendly. + const Vec3f n = (v1 - v0).cross(v2 - v1).normalized(); + // small value used to offset triangles along their normal to avoid z-fighting + const Vec3f offset_n = offset * n; +#if ENABLE_LEGACY_OPENGL_REMOVAL + iva.add_vertex(v0 + offset_n, n); + iva.add_vertex(v1 + offset_n, n); + iva.add_vertex(v2 + offset_n, n); + iva.add_triangle((unsigned int)cnt, (unsigned int)cnt + 1, (unsigned int)cnt + 2); +#else + iva.push_geometry(v0 + offset_n, n); + iva.push_geometry(v1 + offset_n, n); + iva.push_geometry(v2 + offset_n, n); + iva.push_triangle(cnt, cnt + 1, cnt + 2); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + cnt += 3; + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (!iva_enforcers_data.is_empty()) + m_iva_enforcers.init_from(std::move(iva_enforcers_data)); + if (!iva_blockers_data.is_empty()) + m_iva_blockers.init_from(std::move(iva_blockers_data)); + for (size_t i = 0; i < m_iva_seed_fills.size(); ++i) { + if (!iva_seed_fills_data[i].is_empty()) + m_iva_seed_fills[i].init_from(std::move(iva_seed_fills_data[i])); + } + + update_paint_contour(); +#else + for (auto *iva : {&m_iva_enforcers, &m_iva_blockers}) + iva->finalize_geometry(true); + + for (auto &iva : m_iva_seed_fills) + iva.finalize_geometry(true); + + m_paint_contour.release_geometry(); + std::vector contour_edges = this->get_seed_fill_contour(); + m_paint_contour.contour_vertices.reserve(contour_edges.size() * 6); + for (const Vec2i &edge : contour_edges) { + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.x()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.y()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(0)].v.z()); + + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.x()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.y()); + m_paint_contour.contour_vertices.emplace_back(m_vertices[edge(1)].v.z()); + } + + m_paint_contour.contour_indices.assign(m_paint_contour.contour_vertices.size() / 3, 0); + std::iota(m_paint_contour.contour_indices.begin(), m_paint_contour.contour_indices.end(), 0); + m_paint_contour.contour_indices_size = m_paint_contour.contour_indices.size(); + + m_paint_contour.finalize_geometry(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + +#if !ENABLE_LEGACY_OPENGL_REMOVAL +void GLPaintContour::render() const +{ + assert(this->m_contour_VBO_id != 0); + assert(this->m_contour_EBO_id != 0); + + glsafe(::glLineWidth(4.0f)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); + glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr)); + + glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); + + if (this->contour_indices_size > 0) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); + glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } + + glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +} + +void GLPaintContour::finalize_geometry() +{ + assert(this->m_contour_VBO_id == 0); + assert(this->m_contour_EBO_id == 0); + + if (!this->contour_vertices.empty()) { + glsafe(::glGenBuffers(1, &this->m_contour_VBO_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->m_contour_VBO_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + this->contour_vertices.clear(); + } + + if (!this->contour_indices.empty()) { + glsafe(::glGenBuffers(1, &this->m_contour_EBO_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->m_contour_EBO_id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + this->contour_indices.clear(); + } +} + +void GLPaintContour::release_geometry() +{ + if (this->m_contour_VBO_id) { + glsafe(::glDeleteBuffers(1, &this->m_contour_VBO_id)); + this->m_contour_VBO_id = 0; + } + if (this->m_contour_EBO_id) { + glsafe(::glDeleteBuffers(1, &this->m_contour_EBO_id)); + this->m_contour_EBO_id = 0; + } + this->clear(); +} +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) +{ + imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"), + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + static float edge_limit = 1.f; + imgui->text("Edge limit (mm): "); + imgui->slider_float("", &edge_limit, 0.1f, 8.f); + set_edge_limit(edge_limit); + imgui->checkbox("Show split triangles: ", m_show_triangles); + imgui->checkbox("Show invalid triangles: ", m_show_invalid); + + int valid_triangles = m_triangles.size() - m_invalid_triangles; + imgui->text("Valid triangles: " + std::to_string(valid_triangles) + + "/" + std::to_string(m_triangles.size())); + imgui->text("Vertices: " + std::to_string(m_vertices.size())); + if (imgui->button("Force garbage collection")) + garbage_collect(); + + if (imgui->button("Serialize - deserialize")) { + auto map = serialize(); + deserialize(map); + } + + imgui->end(); + + if (! m_show_triangles) + return; + + enum vtype { + ORIGINAL = 0, + SPLIT, + INVALID + }; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (auto& va : m_varrays) + va.reset(); +#else + for (auto& va : m_varrays) + va.release_geometry(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + std::array cnts; + + ::glScalef(1.01f, 1.01f, 1.01f); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + std::array varrays_data; + for (auto& data : varrays_data) + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3, GLModel::Geometry::EIndexType::UINT }; +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + for (int tr_id=0; tr_idadd_vertex(m_vertices[tr.verts_idxs[i]].v, Vec3f(0.0f, 0.0f, 1.0f)); + } + va->add_uint_triangle((unsigned int)*cnt, (unsigned int)*cnt + 1, (unsigned int)*cnt + 2); +#else + for (int i = 0; i < 3; ++i) + va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), + double(m_vertices[tr.verts_idxs[i]].v[1]), + double(m_vertices[tr.verts_idxs[i]].v[2]), + 0., 0., 1.); + va->push_triangle(*cnt, + *cnt + 1, + *cnt + 2); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + *cnt += 3; + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + for (int i = 0; i < 3; ++i) { + if (!varrays_data[i].is_empty()) + m_varrays[i].init_from(std::move(varrays_data[i])); + } +#else +// for (auto* iva : { &m_iva_enforcers, &m_iva_blockers }) +// iva->finalize_geometry(true); +// +// for (auto& iva : m_iva_seed_fills) +// iva.finalize_geometry(true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + +#if ENABLE_GL_SHADERS_ATTRIBUTES + 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()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + for (vtype i : {ORIGINAL, SPLIT, INVALID}) { +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLModel& va = m_varrays[i]; + switch (i) { + case ORIGINAL: va.set_color({ 0.0f, 0.0f, 1.0f, 1.0f }); break; + case SPLIT: va.set_color({ 1.0f, 0.0f, 0.0f, 1.0f }); break; + case INVALID: va.set_color({ 1.0f, 1.0f, 0.0f, 1.0f }); break; + } + va.render(); +#else + GLIndexedVertexArray& va = m_varrays[i]; + va.finalize_geometry(true); + if (va.has_VBOs()) { + switch (i) { + case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; + case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break; + case INVALID : ::glColor3f(1.f, 1.f, 0.f); break; + } + va.render(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} +#endif // PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void TriangleSelectorGUI::update_paint_contour() +{ + m_paint_contour.reset(); + + GLModel::Geometry init_data; + const std::vector contour_edges = this->get_seed_fill_contour(); + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.reserve_vertices(2 * contour_edges.size()); + init_data.reserve_indices(2 * contour_edges.size()); +#if ENABLE_GL_SHADERS_ATTRIBUTES + init_data.color = ColorRGBA::WHITE(); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +// + // vertices + indices + unsigned int vertices_count = 0; + for (const Vec2i& edge : contour_edges) { + init_data.add_vertex(m_vertices[edge(0)].v); + init_data.add_vertex(m_vertices[edge(1)].v); + vertices_count += 2; + init_data.add_line(vertices_count - 2, vertices_count - 1); + } + + if (!init_data.is_empty()) + m_paint_contour.init_from(std::move(init_data)); +} + +#if ENABLE_GL_SHADERS_ATTRIBUTES +void TriangleSelectorGUI::render_paint_contour(const Transform3d& matrix) +#else +void TriangleSelectorGUI::render_paint_contour() +#endif // ENABLE_GL_SHADERS_ATTRIBUTES +{ + auto* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + auto* contour_shader = wxGetApp().get_shader("mm_contour"); + if (contour_shader != nullptr) { + contour_shader->start_using(); + + contour_shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + contour_shader->set_uniform("view_model_matrix", camera.get_view_matrix() * matrix); + contour_shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + m_paint_contour.render(); + contour_shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); +} +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + +} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index c9947ee18..6579a1431 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -190,11 +190,7 @@ void GLGizmoRotate::on_render() glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader != nullptr) { shader->start_using(); @@ -298,7 +294,12 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + 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()); +#else m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_center = v.world_matrix() * m_bounding_box.center(); } else { @@ -308,8 +309,13 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) const GLVolume& v = *selection.get_volume(id); m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix()); + m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor() * m_bounding_box.center(); +#else m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } m_radius = Offset + m_bounding_box.radius(); @@ -322,11 +328,19 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_orient_matrix = Transform3d::Identity(); else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix(); +#else m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_orient_matrix = v.get_instance_transformation().get_rotation_matrix(); +#else m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } } #endif // ENABLE_WORLD_COORDINATE @@ -656,11 +670,7 @@ void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool pick const double size = m_dragging ? double(m_grabbers.front().get_dragging_half_size(mean_size)) : double(m_grabbers.front().get_half_size(mean_size)); #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat_attr" : "gouraud_light_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -739,12 +749,12 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const { case X: { - ret = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.5 * PI, 0.0)) * Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, -0.5 * PI)); + ret = Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()); break; } case Y: { - ret = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, -0.5 * PI)) * Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, -0.5 * PI, 0.0)); + ret = Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitY()); break; } default: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 36e5b0217..05df5f193 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -219,14 +219,24 @@ void GLGizmoScale3D::on_render() } #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_scaling_factor_matrix()); +#else m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(true, true, false, true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #endif // ENABLE_WORLD_COORDINATE // gets transform from first selected volume const GLVolume& v = *selection.get_volume(*idxs.begin()); #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d inst_trafo = v.get_instance_transformation().get_matrix_no_scaling_factor(); + m_grabbers_transform = inst_trafo * Geometry::assemble_transform(m_bounding_box.center()); + m_center = inst_trafo * m_bounding_box.center(); +#else m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center()); m_center = selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_instance_center = v.get_instance_offset(); } else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) { @@ -243,8 +253,14 @@ void GLGizmoScale3D::on_render() #endif // ENABLE_WORLD_COORDINATE const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + 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()); +#else m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, false, false, true))); Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES trafo.set_offset(v.world_matrix().translation()); m_grabbers_transform = trafo.get_matrix(); m_center = v.world_matrix() * m_bounding_box.center(); @@ -252,8 +268,14 @@ void GLGizmoScale3D::on_render() } else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + 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()); +#else m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES trafo.set_offset(v.world_matrix().translation()); m_grabbers_transform = trafo.get_matrix(); m_center = v.world_matrix() * m_bounding_box.center(); @@ -352,8 +374,15 @@ void GLGizmoScale3D::on_render() glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d base_matrix = local_transform(selection); + for (int i = 0; i < 10; ++i) { + m_grabbers[i].matrix = base_matrix; + } +#else glsafe(::glPushMatrix()); transform_to_local(selection); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0); #else @@ -557,14 +586,23 @@ void GLGizmoScale3D::on_render_for_picking() { glsafe(::glDisable(GL_DEPTH_TEST)); #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d base_matrix = local_transform(m_parent.get_selection()); + for (int i = 0; i < 10; ++i) { + m_grabbers[i].matrix = base_matrix; + } +#else glsafe(::glPushMatrix()); transform_to_local(m_parent.get_selection()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES render_grabbers_for_picking(m_bounding_box); +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES #else render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); #endif // ENABLE_WORLD_COORDINATE -} + } #if ENABLE_LEGACY_OPENGL_REMOVAL void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color) @@ -773,6 +811,28 @@ double GLGizmoScale3D::calc_ratio(const UpdateData& data) const } #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES +Transform3d GLGizmoScale3D::local_transform(const Selection& selection) const +{ + Transform3d ret = Geometry::assemble_transform(m_center); + if (!wxGetApp().obj_manipul()->is_world_coordinates()) { + const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); +#else + Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix(); +#else + orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + ret = ret * orient_matrix; + } + return ret; +} +#else void GLGizmoScale3D::transform_to_local(const Selection& selection) const { glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); @@ -784,6 +844,7 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const glsafe(::glMultMatrixd(orient_matrix.data())); } } +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #endif // ENABLE_WORLD_COORDINATE } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index e279e53e2..b8001d7a9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -107,7 +107,11 @@ private: double calc_ratio(const UpdateData& data) const; #if ENABLE_WORLD_COORDINATE +#if ENABLE_GL_SHADERS_ATTRIBUTES + Transform3d local_transform(const Selection& selection) const; +#else void transform_to_local(const Selection& selection) const; +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #endif // ENABLE_WORLD_COORDINATE }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 7c61673b4..f72ce3206 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -1,1370 +1,1374 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. -#include "GLGizmoSlaSupports.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/Camera.hpp" -#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" -#include "slic3r/GUI/MainFrame.hpp" -#include "slic3r/Utils/UndoRedo.hpp" - -#include - -#include -#include -#include - -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/GUI_ObjectSettings.hpp" -#include "slic3r/GUI/GUI_ObjectList.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/NotificationManager.hpp" -#include "slic3r/GUI/MsgDialog.hpp" -#include "libslic3r/PresetBundle.hpp" -#include "libslic3r/SLAPrint.hpp" - - -namespace Slic3r { -namespace GUI { - -GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -{} - -bool GLGizmoSlaSupports::on_init() -{ - m_shortcut_key = WXK_CONTROL_L; - - m_desc["head_diameter"] = _L("Head diameter") + ": "; - m_desc["lock_supports"] = _L("Lock supports under new islands"); - m_desc["remove_selected"] = _L("Remove selected points"); - m_desc["remove_all"] = _L("Remove all points"); - m_desc["apply_changes"] = _L("Apply changes"); - m_desc["discard_changes"] = _L("Discard changes"); - m_desc["minimal_distance"] = _L("Minimal points distance") + ": "; - m_desc["points_density"] = _L("Support points density") + ": "; - m_desc["auto_generate"] = _L("Auto-generate points"); - m_desc["manual_editing"] = _L("Manual editing"); - m_desc["clipping_of_view"] = _L("Clipping of view")+ ": "; - m_desc["reset_direction"] = _L("Reset direction"); - - m_cone.init_from(its_make_cone(1., 1., 2 * PI / 24)); - m_cylinder.init_from(its_make_cylinder(1., 1., 2 * PI / 24.)); - m_sphere.init_from(its_make_sphere(1., (2 * M_PI) / 24.)); - - return true; -} - -void GLGizmoSlaSupports::data_changed() -{ - if (! m_c->selection_info()) - return; - - ModelObject* mo = m_c->selection_info()->model_object(); - - if (m_state == On && mo && mo->id() != m_old_mo_id) { - disable_editing_mode(); - reload_cache(); - m_old_mo_id = mo->id(); - m_c->instances_hider()->show_supports(true); - } - - // If we triggered autogeneration before, check backend and fetch results if they are there - if (mo) { - if (mo->sla_points_status == sla::PointsStatus::Generating) - get_data_from_backend(); - } -} - - - -void GLGizmoSlaSupports::on_render() -{ - if (!m_cone.is_initialized()) - m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); - if (!m_sphere.is_initialized()) - m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0)); - if (!m_cylinder.is_initialized()) - m_cylinder.init_from(its_make_cylinder(1.0, 1.0, double(PI) / 12.0)); - - ModelObject* mo = m_c->selection_info()->model_object(); - const Selection& selection = m_parent.get_selection(); - - // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off - if (m_state == On - && (mo != selection.get_model()->objects[selection.get_object_idx()] - || m_c->selection_info()->get_active_instance() != selection.get_instance_idx())) { - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); - return; - } - - glsafe(::glEnable(GL_BLEND)); - glsafe(::glEnable(GL_DEPTH_TEST)); - - if (selection.is_from_single_instance()) - render_points(selection, false); - - m_selection_rectangle.render(m_parent); - m_c->object_clipper()->render_cut(); - m_c->supports_clipper()->render_cut(); - - glsafe(::glDisable(GL_BLEND)); -} - - -void GLGizmoSlaSupports::on_render_for_picking() -{ - const Selection& selection = m_parent.get_selection(); - //glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, true); -} - -void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) -{ - const size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); - - const bool has_points = (cache_size != 0); - const bool has_holes = (! m_c->hollowed_mesh()->get_hollowed_mesh() - && ! m_c->selection_info()->model_object()->sla_drain_holes.empty()); - - if (! has_points && ! has_holes) - return; - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light"); - if (shader == nullptr) - return; - - shader->start_using(); - ScopeGuard guard([shader]() { shader->stop_using(); }); -#else - GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); - if (shader != nullptr) - shader->start_using(); - ScopeGuard guard([shader]() { - if (shader != nullptr) - shader->stop_using(); - }); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); - Geometry::Transformation transformation(vol->get_instance_transformation().get_matrix() * vol->get_volume_transformation().get_matrix()); - const Transform3d& instance_scaling_matrix_inverse = transformation.get_matrix(true, true, false, true).inverse(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * transformation.get_matrix(); - const Camera& camera = wxGetApp().plater()->get_camera(); - const Transform3d& view_matrix = camera.get_view_matrix(); - const Transform3d& projection_matrix = camera.get_projection_matrix(); - - shader->set_uniform("projection_matrix", projection_matrix); -#else - const Transform3d& instance_matrix = transformation.get_matrix(); - const float z_shift = m_c->selection_info()->get_sla_shift(); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(0.0, 0.0, z_shift)); - glsafe(::glMultMatrixd(instance_matrix.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - ColorRGBA render_color; - for (size_t i = 0; i < cache_size; ++i) { - const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i]; - const bool point_selected = m_editing_mode ? m_editing_cache[i].selected : false; - - if (is_mesh_point_clipped(support_point.pos.cast())) - continue; - - // First decide about the color of the point. - if (picking) - render_color = picking_color_component(i); - else { - if (size_t(m_hover_id) == i && m_editing_mode) // ignore hover state unless editing mode is active - render_color = { 0.f, 1.f, 1.f, 1.f }; - else { // neigher hover nor picking - bool supports_new_island = m_lock_unique_islands && support_point.is_new_island; - if (m_editing_mode) { - if (point_selected) - render_color = { 1.f, 0.3f, 0.3f, 1.f}; - else - if (supports_new_island) - render_color = { 0.3f, 0.3f, 1.f, 1.f }; - else - render_color = { 0.7f, 0.7f, 0.7f, 1.f }; - } - else - render_color = { 0.5f, 0.5f, 0.5f, 1.f }; - } - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_cone.set_color(render_color); - m_sphere.set_color(render_color); - if (!picking) -#else - m_cone.set_color(-1, render_color); - m_sphere.set_color(-1, render_color); - if (shader && !picking) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - shader->set_uniform("emission_factor", 0.5f); - - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d support_matrix = Geometry::assemble_transform(support_point.pos.cast()) * instance_scaling_matrix_inverse; -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(support_point.pos.x(), support_point.pos.y(), support_point.pos.z())); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (vol->is_left_handed()) - glFrontFace(GL_CW); - - // Matrices set, we can render the point mark now. - // If in editing mode, we'll also render a cone pointing to the sphere. - if (m_editing_mode) { - // in case the normal is not yet cached, find and cache it - if (m_editing_cache[i].normal == Vec3f::Zero()) - m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); - - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); - const Eigen::AngleAxisd aa(q); - const double cone_radius = 0.25; // mm - const double cone_height = 0.75; -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = view_matrix * instance_matrix * support_matrix * Transform3d(aa.toRotationMatrix()) * - Geometry::assemble_transform((cone_height + support_point.head_front_radius * RenderPointScale) * Vec3d::UnitZ(), - Vec3d(PI, 0.0, 0.0), Vec3d(cone_radius, cone_radius, cone_height)); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glPushMatrix()); - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); - glsafe(::glTranslatef(0.f, 0.f, cone_height + support_point.head_front_radius * RenderPointScale)); - glsafe(::glRotated(180., 1., 0., 0.)); - glsafe(::glScaled(cone_radius, cone_radius, cone_height)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_cone.render(); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - - const double radius = (double)support_point.head_front_radius * RenderPointScale; -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = view_matrix * instance_matrix * support_matrix * - Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), radius * Vec3d::Ones()); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glPushMatrix()); - glsafe(::glScaled(radius, radius, radius)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_sphere.render(); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - if (vol->is_left_handed()) - glFrontFace(GL_CCW); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - - // Now render the drain holes: - if (has_holes && ! picking) { - render_color = { 0.7f, 0.7f, 0.7f, 0.7f }; -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_cylinder.set_color(render_color); -#else - m_cylinder.set_color(-1, render_color); - if (shader != nullptr) -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - shader->set_uniform("emission_factor", 0.5f); - for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { - if (is_mesh_point_clipped(drain_hole.pos.cast())) - continue; - -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; -#else - // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z())); - glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - if (vol->is_left_handed()) - glFrontFace(GL_CW); - - // Matrices set, we can render the point mark now. - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); - const Eigen::AngleAxisd aa(q); -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Transform3d view_model_matrix = view_matrix * instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) * - Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); - - shader->set_uniform("view_model_matrix", view_model_matrix); - shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); -#else - glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); - glsafe(::glTranslated(0., 0., -drain_hole.height)); - glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_cylinder.render(); - - if (vol->is_left_handed()) - glFrontFace(GL_CCW); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - } - } - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -} - - - -bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const -{ - if (m_c->object_clipper()->get_position() == 0.) - return false; - - auto sel_info = m_c->selection_info(); - int active_inst = m_c->selection_info()->get_active_instance(); - const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; - const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix(); - - Vec3d transformed_point = trafo * point; - transformed_point(2) += sel_info->get_sla_shift(); - return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); -} - - - -// 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 GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) -{ - if (! m_c->raycaster()->raycaster()) - return false; - - const Camera& camera = wxGetApp().plater()->get_camera(); - const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); - Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - - double clp_dist = m_c->object_clipper()->get_position(); - const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); - - // The raycaster query - Vec3f hit; - Vec3f normal; - if (m_c->raycaster()->raycaster()->unproject_on_mesh( - mouse_pos, - trafo.get_matrix(), - camera, - hit, - normal, - clp_dist != 0. ? clp : nullptr)) - { - // Check whether the hit is in a hole - bool in_hole = false; - // In case the hollowed and drilled mesh is available, we can allow - // placing points in holes, because they should never end up - // on surface that's been drilled away. - if (! m_c->hollowed_mesh()->get_hollowed_mesh()) { - sla::DrainHoles drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; - for (const sla::DrainHole& hole : drain_holes) { - if (hole.is_inside(hit)) { - in_hole = true; - break; - } - } - } - if (! in_hole) { - // Return both the point and the facet normal. - pos_and_normal = std::make_pair(hit, normal); - return true; - } - } - - return false; -} - -// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. -// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is -// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo -// concludes that the event was not intended for it, it should return false. -bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) -{ - ModelObject* mo = m_c->selection_info()->model_object(); - int active_inst = m_c->selection_info()->get_active_instance(); - - if (m_editing_mode) { - - // left down with shift - show the selection rectangle: - if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { - if (m_hover_id == -1) { - if (shift_down || alt_down) { - m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); - } - } - else { - if (m_editing_cache[m_hover_id].selected) - unselect_point(m_hover_id); - else { - if (!alt_down) - select_point(m_hover_id); - } - } - - return true; - } - - // left down without selection rectangle - place point on the mesh: - if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { - // If any point is in hover state, this should initiate its move - return control back to GLCanvas: - if (m_hover_id != -1) - return false; - - // If there is some selection, don't add new point and deselect everything instead. - if (m_selection_empty) { - std::pair pos_and_normal; - if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add support point")); - m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); - m_parent.set_as_dirty(); - m_wait_for_up_event = true; - } - else - return false; - } - else - select_point(NoPoints); - - return true; - } - - // left up with selection rectangle - select points inside the rectangle: - if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { - // Is this a selection or deselection rectangle? - GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); - - // First collect positions of all the points in world coordinates. - Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation(); - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); - std::vector points; - for (unsigned int i=0; i()); - - // Now ask the rectangle which of the points are inside. - std::vector points_inside; - std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); - for (size_t idx : points_idxs) - points_inside.push_back(points[idx].cast()); - - // Only select/deselect points that are actually visible. We want to check not only - // the point itself, but also the center of base of its cone, so the points don't hide - // under every miniature irregularity on the model. Remember the actual number and - // append the cone bases. - size_t orig_pts_num = points_inside.size(); - for (size_t idx : points_idxs) - points_inside.emplace_back((trafo.get_matrix().cast() * (m_editing_cache[idx].support_point.pos + m_editing_cache[idx].normal)).cast()); - - for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( - trafo, wxGetApp().plater()->get_camera(), points_inside, - m_c->object_clipper()->get_clipping_plane())) - { - if (idx >= orig_pts_num) // this is a cone-base, get index of point it belongs to - idx -= orig_pts_num; - if (rectangle_status == GLSelectionRectangle::EState::Deselect) - unselect_point(points_idxs[idx]); - else - select_point(points_idxs[idx]); - } - return true; - } - - // left up with no selection rectangle - if (action == SLAGizmoEventType::LeftUp) { - if (m_wait_for_up_event) { - m_wait_for_up_event = false; - return true; - } - } - - // dragging the selection rectangle: - if (action == SLAGizmoEventType::Dragging) { - if (m_wait_for_up_event) - return true; // point has been placed and the button not released yet - // this prevents GLCanvas from starting scene rotation - - if (m_selection_rectangle.is_dragging()) { - m_selection_rectangle.dragging(mouse_position); - return true; - } - - return false; - } - - if (action == SLAGizmoEventType::Delete) { - // delete key pressed - delete_selected_points(); - return true; - } - - if (action == SLAGizmoEventType::ApplyChanges) { - editing_mode_apply_changes(); - return true; - } - - if (action == SLAGizmoEventType::DiscardChanges) { - ask_about_changes_call_after([this](){ editing_mode_apply_changes(); }, - [this](){ editing_mode_discard_changes(); }); - return true; - } - - if (action == SLAGizmoEventType::RightDown) { - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - delete_selected_points(); - return true; - } - return false; - } - - if (action == SLAGizmoEventType::SelectAll) { - select_point(AllPoints); - return true; - } - } - - if (!m_editing_mode) { - if (action == SLAGizmoEventType::AutomaticGeneration) { - auto_generate(); - return true; - } - - if (action == SLAGizmoEventType::ManualEditing) { - switch_to_editing_mode(); - return true; - } - } - - if (action == SLAGizmoEventType::MouseWheelUp && control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::min(1., pos + 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - - if (action == SLAGizmoEventType::MouseWheelDown && control_down) { - double pos = m_c->object_clipper()->get_position(); - pos = std::max(0., pos - 0.01); - m_c->object_clipper()->set_position(pos, true); - return true; - } - - if (action == SLAGizmoEventType::ResetClippingPlane) { - m_c->object_clipper()->set_position(-1., false); - return true; - } - - return false; -} - -void GLGizmoSlaSupports::delete_selected_points(bool force) -{ - if (! m_editing_mode) { - std::cout << "DEBUGGING: delete_selected_points called out of editing mode!" << std::endl; - std::abort(); - } - - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point")); - - for (unsigned int idx=0; idx GLGizmoSlaSupports::get_config_options(const std::vector& keys) const -{ - std::vector out; - const ModelObject* mo = m_c->selection_info()->model_object(); - - if (! mo) - return out; - - const DynamicPrintConfig& object_cfg = mo->config.get(); - const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - std::unique_ptr default_cfg = nullptr; - - for (const std::string& key : keys) { - if (object_cfg.has(key)) - out.push_back(object_cfg.option(key)); - else - if (print_cfg.has(key)) - out.push_back(print_cfg.option(key)); - else { // we must get it from defaults - if (default_cfg == nullptr) - default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); - out.push_back(default_cfg->option(key)); - } - } - - return out; -} - - - -/* -void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB* aabb, const Vec3f& normal, double offset, std::vector& idxs) const -{ - if (aabb->is_leaf()) { // this is a facet - // corner.dot(normal) - offset - idxs.push_back(aabb->m_primitive); - } - else { // not a leaf - using CornerType = Eigen::AlignedBox::CornerType; - bool sign = std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(0)))); - for (unsigned int i=1; i<8; ++i) - if (std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(i)))) != sign) { - find_intersecting_facets(aabb->m_left, normal, offset, idxs); - find_intersecting_facets(aabb->m_right, normal, offset, idxs); - } - } -} - - - -void GLGizmoSlaSupports::make_line_segments() const -{ - TriangleMeshSlicer tms(&m_c->m_model_object->volumes.front()->mesh); - Vec3f normal(0.f, 1.f, 1.f); - double d = 0.; - - std::vector lines; - find_intersections(&m_AABB, normal, d, lines); - ExPolygons expolys; - tms.make_expolygons_simple(lines, &expolys); - - SVG svg("slice_loops.svg", get_extents(expolys)); - svg.draw(expolys); - //for (const IntersectionLine &l : lines[i]) - // svg.draw(l, "red", 0); - //svg.draw_outline(expolygons, "black", "blue", 0); - svg.Close(); -} -*/ - - -void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit) -{ - static float last_y = 0.0f; - static float last_h = 0.0f; - - ModelObject* mo = m_c->selection_info()->model_object(); - - if (! mo) - return; - - bool first_run = true; // This is a hack to redraw the button when all points are removed, - // so it is not delayed until the background process finishes. -RENDER_AGAIN: - //m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - //const ImVec2 window_size(m_imgui->scaled(18.f, 16.f)); - //ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); - //ImGui::SetNextWindowSize(ImVec2(window_size)); - - m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); - - // adjust window position to avoid overlap the view toolbar - float win_h = ImGui::GetWindowHeight(); - y = std::min(y, bottom_limit - win_h); - ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); - if ((last_h != win_h) || (last_y != y)) - { - // ask canvas for another frame to render the window in the correct position - m_imgui->set_requires_extra_frame(); - if (last_h != win_h) - last_h = win_h; - if (last_y != y) - last_y = y; - } - - // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: - - const float settings_sliders_left = std::max(m_imgui->calc_text_size(m_desc.at("minimal_distance")).x, m_imgui->calc_text_size(m_desc.at("points_density")).x) + m_imgui->scaled(1.f); - const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); - const float diameter_slider_left = m_imgui->calc_text_size(m_desc.at("head_diameter")).x + m_imgui->scaled(1.f); - const float minimal_slider_width = m_imgui->scaled(4.f); - const float buttons_width_approx = m_imgui->calc_text_size(m_desc.at("apply_changes")).x + m_imgui->calc_text_size(m_desc.at("discard_changes")).x + m_imgui->scaled(1.5f); - const float lock_supports_width_approx = m_imgui->calc_text_size(m_desc.at("lock_supports")).x + m_imgui->scaled(2.f); - - float window_width = minimal_slider_width + std::max(std::max(settings_sliders_left, clipping_slider_left), diameter_slider_left); - window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx); - - bool force_refresh = false; - bool remove_selected = false; - bool remove_all = false; - - if (m_editing_mode) { - - float diameter_upper_cap = static_cast(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value; - if (m_new_point_head_diameter > diameter_upper_cap) - m_new_point_head_diameter = diameter_upper_cap; - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("head_diameter")); - ImGui::SameLine(diameter_slider_left); - ImGui::PushItemWidth(window_width - diameter_slider_left); - - // Following is a nasty way to: - // - save the initial value of the slider before one starts messing with it - // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene - // - take correct undo/redo snapshot after the user is done with moving the slider - float initial_value = m_new_point_head_diameter; - m_imgui->slider_float("##head_diameter", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f"); - if (m_imgui->get_last_slider_status().clicked) { - if (m_old_point_head_diameter == 0.f) - m_old_point_head_diameter = initial_value; - } - if (m_imgui->get_last_slider_status().edited) { - for (auto& cache_entry : m_editing_cache) - if (cache_entry.selected) - cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; - } - if (m_imgui->get_last_slider_status().deactivated_after_edit) { - // momentarily restore the old value to take snapshot - for (auto& cache_entry : m_editing_cache) - if (cache_entry.selected) - cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f; - float backup = m_new_point_head_diameter; - m_new_point_head_diameter = m_old_point_head_diameter; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change point head diameter")); - m_new_point_head_diameter = backup; - for (auto& cache_entry : m_editing_cache) - if (cache_entry.selected) - cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; - m_old_point_head_diameter = 0.f; - } - - bool changed = m_lock_unique_islands; - m_imgui->checkbox(m_desc.at("lock_supports"), m_lock_unique_islands); - force_refresh |= changed != m_lock_unique_islands; - - m_imgui->disabled_begin(m_selection_empty); - remove_selected = m_imgui->button(m_desc.at("remove_selected")); - m_imgui->disabled_end(); - - m_imgui->disabled_begin(m_editing_cache.empty()); - remove_all = m_imgui->button(m_desc.at("remove_all")); - m_imgui->disabled_end(); - - m_imgui->text(" "); // vertical gap - - if (m_imgui->button(m_desc.at("apply_changes"))) { - editing_mode_apply_changes(); - force_refresh = true; - } - ImGui::SameLine(); - bool discard_changes = m_imgui->button(m_desc.at("discard_changes")); - if (discard_changes) { - editing_mode_discard_changes(); - force_refresh = true; - } - } - else { // not in editing mode: - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("minimal_distance")); - ImGui::SameLine(settings_sliders_left); - ImGui::PushItemWidth(window_width - settings_sliders_left); - - std::vector opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); - float density = static_cast(opts[0])->value; - float minimal_point_distance = static_cast(opts[1])->value; - - m_imgui->slider_float("##minimal_point_distance", &minimal_point_distance, 0.f, 20.f, "%.f mm"); - bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider - bool slider_edited = m_imgui->get_last_slider_status().edited; // someone is dragging the slider - bool slider_released = m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider - - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("points_density")); - ImGui::SameLine(settings_sliders_left); - - m_imgui->slider_float("##points_density", &density, 0.f, 200.f, "%.f %%"); - slider_clicked |= m_imgui->get_last_slider_status().clicked; - slider_edited |= m_imgui->get_last_slider_status().edited; - slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; - - if (slider_clicked) { // stash the values of the settings so we know what to revert to after undo - m_minimal_point_distance_stash = minimal_point_distance; - m_density_stash = density; - } - if (slider_edited) { - mo->config.set("support_points_minimal_distance", minimal_point_distance); - mo->config.set("support_points_density_relative", (int)density); - } - if (slider_released) { - mo->config.set("support_points_minimal_distance", m_minimal_point_distance_stash); - mo->config.set("support_points_density_relative", (int)m_density_stash); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support parameter change")); - mo->config.set("support_points_minimal_distance", minimal_point_distance); - mo->config.set("support_points_density_relative", (int)density); - wxGetApp().obj_list()->update_and_show_object_settings_item(); - } - - bool generate = m_imgui->button(m_desc.at("auto_generate")); - - if (generate) - auto_generate(); - - ImGui::Separator(); - if (m_imgui->button(m_desc.at("manual_editing"))) - switch_to_editing_mode(); - - m_imgui->disabled_begin(m_normal_cache.empty()); - remove_all = m_imgui->button(m_desc.at("remove_all")); - m_imgui->disabled_end(); - - // m_imgui->text(""); - // m_imgui->text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) : - // (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) : - // (m_c->m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) : - // (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS")))); - } - - - // Following is rendered in both editing and non-editing mode: - ImGui::Separator(); - if (m_c->object_clipper()->get_position() == 0.f) { - ImGui::AlignTextToFramePadding(); - m_imgui->text(m_desc.at("clipping_of_view")); - } - else { - if (m_imgui->button(m_desc.at("reset_direction"))) { - wxGetApp().CallAfter([this](){ - m_c->object_clipper()->set_position(-1., false); - }); - } - } - - ImGui::SameLine(clipping_slider_left); - ImGui::PushItemWidth(window_width - clipping_slider_left); - float clp_dist = m_c->object_clipper()->get_position(); - if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) - m_c->object_clipper()->set_position(clp_dist, true); - - - if (m_imgui->button("?")) { - wxGetApp().CallAfter([]() { - SlaGizmoHelpDialog help_dlg; - help_dlg.ShowModal(); - }); - } - - m_imgui->end(); - - if (remove_selected || remove_all) { - force_refresh = false; - m_parent.set_as_dirty(); - bool was_in_editing = m_editing_mode; - if (! was_in_editing) - switch_to_editing_mode(); - if (remove_all) { - select_point(AllPoints); - delete_selected_points(true); // true - delete regardless of locked status - } - if (remove_selected) - delete_selected_points(false); // leave locked points - if (! was_in_editing) - editing_mode_apply_changes(); - - if (first_run) { - first_run = false; - goto RENDER_AGAIN; - } - } - - if (force_refresh) - m_parent.set_as_dirty(); -} - -bool GLGizmoSlaSupports::on_is_activable() const -{ - const Selection& selection = m_parent.get_selection(); - - if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA - || !selection.is_from_single_instance()) - return false; - - // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. - const Selection::IndicesList& list = selection.get_volume_idxs(); - for (const auto& idx : list) - if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) - return false; - - return true; -} - -bool GLGizmoSlaSupports::on_is_selectable() const -{ - return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); -} - -std::string GLGizmoSlaSupports::on_get_name() const -{ - return _u8L("SLA Support Points"); -} - -CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const -{ - return CommonGizmosDataID( - int(CommonGizmosDataID::SelectionInfo) - | int(CommonGizmosDataID::InstancesHider) - | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); -} - - - -void GLGizmoSlaSupports::ask_about_changes_call_after(std::function on_yes, std::function on_no) -{ - wxGetApp().CallAfter([on_yes, on_no]() { - // Following is called through CallAfter, because otherwise there was a problem - // on OSX with the wxMessageDialog being shown several times when clicked into. - MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually " - "edited support points?") + "\n",_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL ); - int ret = dlg.ShowModal(); - if (ret == wxID_YES) - on_yes(); - else if (ret == wxID_NO) - on_no(); - }); -} - - -void GLGizmoSlaSupports::on_set_state() -{ - if (m_state == m_old_state) - return; - - if (m_state == On && m_old_state != On) { // the gizmo was just turned on - // Set default head diameter from config. - const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; - } - if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - bool will_ask = m_editing_mode && unsaved_changes() && on_is_activable(); - if (will_ask) { - ask_about_changes_call_after([this](){ editing_mode_apply_changes(); }, - [this](){ editing_mode_discard_changes(); }); - // refuse to be turned off so the gizmo is active when the CallAfter is executed - m_state = m_old_state; - } - else { - // we are actually shutting down - disable_editing_mode(); // so it is not active next time the gizmo opens - m_old_mo_id = -1; - } - } - m_old_state = m_state; -} - - - -void GLGizmoSlaSupports::on_start_dragging() -{ - if (m_hover_id != -1) { - select_point(NoPoints); - select_point(m_hover_id); - m_point_before_drag = m_editing_cache[m_hover_id]; - } - else - m_point_before_drag = CacheEntry(); -} - - -void GLGizmoSlaSupports::on_stop_dragging() -{ - if (m_hover_id != -1) { - CacheEntry backup = m_editing_cache[m_hover_id]; - - if (m_point_before_drag.support_point.pos != Vec3f::Zero() // some point was touched - && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected - { - m_editing_cache[m_hover_id] = m_point_before_drag; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move support point")); - m_editing_cache[m_hover_id] = backup; - } - } - m_point_before_drag = CacheEntry(); -} - -void GLGizmoSlaSupports::on_dragging(const UpdateData &data) -{ - assert(m_hover_id != -1); - if (!m_editing_mode) return; - if (m_editing_cache[m_hover_id].support_point.is_new_island && m_lock_unique_islands) - return; - - std::pair pos_and_normal; - if (!unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) - return; - - m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first; - m_editing_cache[m_hover_id].support_point.is_new_island = false; - m_editing_cache[m_hover_id].normal = pos_and_normal.second; -} - -void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) -{ - ar(m_new_point_head_diameter, - m_normal_cache, - m_editing_cache, - m_selection_empty - ); -} - - - -void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const -{ - ar(m_new_point_head_diameter, - m_normal_cache, - m_editing_cache, - m_selection_empty - ); -} - - - -void GLGizmoSlaSupports::select_point(int i) -{ - if (! m_editing_mode) { - std::cout << "DEBUGGING: select_point called when out of editing mode!" << std::endl; - std::abort(); - } - - if (i == AllPoints || i == NoPoints) { - for (auto& point_and_selection : m_editing_cache) - point_and_selection.selected = ( i == AllPoints ); - m_selection_empty = (i == NoPoints); - - if (i == AllPoints) - m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f; - } - else { - m_editing_cache[i].selected = true; - m_selection_empty = false; - m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f; - } -} - - -void GLGizmoSlaSupports::unselect_point(int i) -{ - if (! m_editing_mode) { - std::cout << "DEBUGGING: unselect_point called when out of editing mode!" << std::endl; - std::abort(); - } - - m_editing_cache[i].selected = false; - m_selection_empty = true; - for (const CacheEntry& ce : m_editing_cache) { - if (ce.selected) { - m_selection_empty = false; - break; - } - } -} - - - - -void GLGizmoSlaSupports::editing_mode_discard_changes() -{ - if (! m_editing_mode) { - std::cout << "DEBUGGING: editing_mode_discard_changes called when out of editing mode!" << std::endl; - std::abort(); - } - select_point(NoPoints); - disable_editing_mode(); -} - - - -void GLGizmoSlaSupports::editing_mode_apply_changes() -{ - // If there are no changes, don't touch the front-end. The data in the cache could have been - // taken from the backend and copying them to ModelObject would needlessly invalidate them. - disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken - - if (unsaved_changes()) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support points edit")); - - m_normal_cache.clear(); - for (const CacheEntry& ce : m_editing_cache) - m_normal_cache.push_back(ce.support_point); - - ModelObject* mo = m_c->selection_info()->model_object(); - mo->sla_points_status = sla::PointsStatus::UserModified; - mo->sla_support_points.clear(); - mo->sla_support_points = m_normal_cache; - - reslice_SLA_supports(); - } -} - - - -void GLGizmoSlaSupports::reload_cache() -{ - const ModelObject* mo = m_c->selection_info()->model_object(); - m_normal_cache.clear(); - if (mo->sla_points_status == sla::PointsStatus::AutoGenerated || mo->sla_points_status == sla::PointsStatus::Generating) - get_data_from_backend(); - else - for (const sla::SupportPoint& point : mo->sla_support_points) - m_normal_cache.emplace_back(point); -} - - -bool GLGizmoSlaSupports::has_backend_supports() const -{ - const ModelObject* mo = m_c->selection_info()->model_object(); - if (! mo) - return false; - - // find SlaPrintObject with this ID - for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == mo->id()) - return po->is_step_done(slaposSupportPoints); - } - return false; -} - -void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const -{ - wxGetApp().CallAfter([this, postpone_error_messages]() { - wxGetApp().plater()->reslice_SLA_supports( - *m_c->selection_info()->model_object(), postpone_error_messages); - }); -} - -bool GLGizmoSlaSupports::on_mouse(const wxMouseEvent &mouse_event){ - if (mouse_event.Moving()) return false; - if (use_grabbers(mouse_event)) return true; - - // wxCoord == int --> wx/types.h - Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); - Vec2d mouse_pos = mouse_coord.cast(); - - static bool pending_right_up = false; - if (mouse_event.LeftDown()) { - bool grabber_contains_mouse = (get_hover_id() != -1); - bool control_down = mouse_event.CmdDown(); - if ((!control_down || grabber_contains_mouse) && - gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) - return true; - } else if (mouse_event.Dragging()) { - bool control_down = mouse_event.CmdDown(); - if (m_parent.get_move_volume_id() != -1) { - // don't allow dragging objects with the Sla gizmo on - return true; - } else if (!control_down && - gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) { - // the gizmo got the event and took some action, no need to do - // anything more here - m_parent.set_as_dirty(); - return true; - } else if (control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())){ - // CTRL has been pressed while already dragging -> stop current action - if (mouse_event.LeftIsDown()) - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); - else if (mouse_event.RightIsDown()) - pending_right_up = false; - } - } else if (mouse_event.LeftUp() && !m_parent.is_mouse_dragging()) { - // in case SLA/FDM gizmo is selected, we just pass the LeftUp event - // and stop processing - neither object moving or selecting is - // suppressed in that case - gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); - return true; - }else if (mouse_event.RightDown()){ - if (m_parent.get_selection().get_object_idx() != -1 && - gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) { - // we need to set the following right up as processed to avoid showing - // the context menu if the user release the mouse over the object - pending_right_up = true; - // event was taken care of by the SlaSupports gizmo - return true; - } - } else if (pending_right_up && mouse_event.RightUp()) { - pending_right_up = false; - return true; - } - return false; -} - -void GLGizmoSlaSupports::get_data_from_backend() -{ - if (! has_backend_supports()) - return; - ModelObject* mo = m_c->selection_info()->model_object(); - - // find the respective SLAPrintObject, we need a pointer to it - for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { - if (po->model_object()->id() == mo->id()) { - m_normal_cache.clear(); - const std::vector& points = po->get_support_points(); - auto mat = (po->trafo() * po->model_object()->volumes.front()->get_transformation().get_matrix()).inverse().cast(); - for (unsigned int i=0; isla_points_status = sla::PointsStatus::AutoGenerated; - break; - } - } - - // We don't copy the data into ModelObject, as this would stop the background processing. -} - - - -void GLGizmoSlaSupports::auto_generate() -{ - //wxMessageDialog dlg(GUI::wxGetApp().plater(), - MessageDialog dlg(GUI::wxGetApp().plater(), - _L("Autogeneration will erase all manually edited points.") + "\n\n" + - _L("Are you sure you want to do it?") + "\n", - _L("Warning"), wxICON_WARNING | wxYES | wxNO); - - ModelObject* mo = m_c->selection_info()->model_object(); - - if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points")); - wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); - mo->sla_points_status = sla::PointsStatus::Generating; - } -} - - - -void GLGizmoSlaSupports::switch_to_editing_mode() -{ - wxGetApp().plater()->enter_gizmos_stack(); - m_editing_mode = true; - m_editing_cache.clear(); - for (const sla::SupportPoint& sp : m_normal_cache) - m_editing_cache.emplace_back(sp); - select_point(NoPoints); - - m_c->instances_hider()->show_supports(false); - m_parent.set_as_dirty(); -} - - -void GLGizmoSlaSupports::disable_editing_mode() -{ - if (m_editing_mode) { - m_editing_mode = false; - wxGetApp().plater()->leave_gizmos_stack(); - m_c->instances_hider()->show_supports(true); - m_parent.set_as_dirty(); - } - wxGetApp().plater()->get_notification_manager()->close_notification_of_type(NotificationType::QuitSLAManualMode); -} - - - -bool GLGizmoSlaSupports::unsaved_changes() const -{ - if (m_editing_cache.size() != m_normal_cache.size()) - return true; - - for (size_t i=0; iSetFont(font); - - auto vsizer = new wxBoxSizer(wxVERTICAL); - auto gridsizer = new wxFlexGridSizer(2, 5, 15); - auto hsizer = new wxBoxSizer(wxHORIZONTAL); - - hsizer->AddSpacer(20); - hsizer->Add(vsizer); - hsizer->AddSpacer(20); - - vsizer->AddSpacer(20); - vsizer->Add(note_text, 1, wxALIGN_CENTRE_HORIZONTAL); - vsizer->AddSpacer(20); - vsizer->Add(gridsizer); - vsizer->AddSpacer(20); - - std::vector> shortcuts; - shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add point"))); - shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove point"))); - shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move point"))); - shortcuts.push_back(std::make_pair(ctrl+_L("Left click"), _L("Add point to selection"))); - shortcuts.push_back(std::make_pair(alt+_L("Left click"), _L("Remove point from selection"))); - shortcuts.push_back(std::make_pair(wxString("Shift+")+_L("Drag"), _L("Select by rectangle"))); - shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _L("Deselect by rectangle"))); - shortcuts.push_back(std::make_pair(ctrl+"A", _L("Select all points"))); - shortcuts.push_back(std::make_pair("Delete", _L("Remove selected points"))); - shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"), _L("Move clipping plane"))); - shortcuts.push_back(std::make_pair("R", _L("Reset clipping plane"))); - shortcuts.push_back(std::make_pair("Enter", _L("Apply changes"))); - shortcuts.push_back(std::make_pair("Esc", _L("Discard changes"))); - shortcuts.push_back(std::make_pair("M", _L("Switch to editing mode"))); - shortcuts.push_back(std::make_pair("A", _L("Auto-generate points"))); - - for (const auto& pair : shortcuts) { - auto shortcut = new wxStaticText(this, wxID_ANY, pair.first); - auto desc = new wxStaticText(this, wxID_ANY, pair.second); - shortcut->SetFont(bold_font); - desc->SetFont(font); - gridsizer->Add(shortcut, -1, wxALIGN_CENTRE_VERTICAL); - gridsizer->Add(desc, -1, wxALIGN_CENTRE_VERTICAL); - } - - SetSizer(hsizer); - hsizer->SetSizeHints(this); -} - - - -} // namespace GUI -} // namespace Slic3r +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoSlaSupports.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" +#include "slic3r/GUI/MainFrame.hpp" +#include "slic3r/Utils/UndoRedo.hpp" + +#include + +#include +#include +#include + +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_ObjectSettings.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/NotificationManager.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/SLAPrint.hpp" + + +namespace Slic3r { +namespace GUI { + +GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +{} + +bool GLGizmoSlaSupports::on_init() +{ + m_shortcut_key = WXK_CONTROL_L; + + m_desc["head_diameter"] = _L("Head diameter") + ": "; + m_desc["lock_supports"] = _L("Lock supports under new islands"); + m_desc["remove_selected"] = _L("Remove selected points"); + m_desc["remove_all"] = _L("Remove all points"); + m_desc["apply_changes"] = _L("Apply changes"); + m_desc["discard_changes"] = _L("Discard changes"); + m_desc["minimal_distance"] = _L("Minimal points distance") + ": "; + m_desc["points_density"] = _L("Support points density") + ": "; + m_desc["auto_generate"] = _L("Auto-generate points"); + m_desc["manual_editing"] = _L("Manual editing"); + m_desc["clipping_of_view"] = _L("Clipping of view")+ ": "; + m_desc["reset_direction"] = _L("Reset direction"); + + m_cone.init_from(its_make_cone(1., 1., 2 * PI / 24)); + m_cylinder.init_from(its_make_cylinder(1., 1., 2 * PI / 24.)); + m_sphere.init_from(its_make_sphere(1., (2 * M_PI) / 24.)); + + return true; +} + +void GLGizmoSlaSupports::data_changed() +{ + if (! m_c->selection_info()) + return; + + ModelObject* mo = m_c->selection_info()->model_object(); + + if (m_state == On && mo && mo->id() != m_old_mo_id) { + disable_editing_mode(); + reload_cache(); + m_old_mo_id = mo->id(); + m_c->instances_hider()->show_supports(true); + } + + // If we triggered autogeneration before, check backend and fetch results if they are there + if (mo) { + if (mo->sla_points_status == sla::PointsStatus::Generating) + get_data_from_backend(); + } +} + + + +void GLGizmoSlaSupports::on_render() +{ + if (!m_cone.is_initialized()) + m_cone.init_from(its_make_cone(1.0, 1.0, double(PI) / 12.0)); + if (!m_sphere.is_initialized()) + m_sphere.init_from(its_make_sphere(1.0, double(PI) / 12.0)); + if (!m_cylinder.is_initialized()) + m_cylinder.init_from(its_make_cylinder(1.0, 1.0, double(PI) / 12.0)); + + ModelObject* mo = m_c->selection_info()->model_object(); + const Selection& selection = m_parent.get_selection(); + + // If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off + if (m_state == On + && (mo != selection.get_model()->objects[selection.get_object_idx()] + || m_c->selection_info()->get_active_instance() != selection.get_instance_idx())) { + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); + return; + } + + glsafe(::glEnable(GL_BLEND)); + glsafe(::glEnable(GL_DEPTH_TEST)); + + if (selection.is_from_single_instance()) + render_points(selection, false); + + m_selection_rectangle.render(m_parent); + m_c->object_clipper()->render_cut(); + m_c->supports_clipper()->render_cut(); + + glsafe(::glDisable(GL_BLEND)); +} + + +void GLGizmoSlaSupports::on_render_for_picking() +{ + const Selection& selection = m_parent.get_selection(); + //glsafe(::glEnable(GL_DEPTH_TEST)); + render_points(selection, true); +} + +void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) +{ + const size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); + + const bool has_points = (cache_size != 0); + const bool has_holes = (! m_c->hollowed_mesh()->get_hollowed_mesh() + && ! m_c->selection_info()->model_object()->sla_drain_holes.empty()); + + if (! has_points && ! has_holes) + return; + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader(picking ? "flat" : "gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); + ScopeGuard guard([shader]() { shader->stop_using(); }); +#else + GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); + if (shader != nullptr) + shader->start_using(); + ScopeGuard guard([shader]() { + if (shader != nullptr) + shader->stop_using(); + }); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + Geometry::Transformation transformation(vol->get_instance_transformation().get_matrix() * vol->get_volume_transformation().get_matrix()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); +#else + const Transform3d& instance_scaling_matrix_inverse = transformation.get_matrix(true, true, false, true).inverse(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * transformation.get_matrix(); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d& projection_matrix = camera.get_projection_matrix(); + + shader->set_uniform("projection_matrix", projection_matrix); +#else + const Transform3d& instance_matrix = transformation.get_matrix(); + const float z_shift = m_c->selection_info()->get_sla_shift(); + glsafe(::glPushMatrix()); + glsafe(::glTranslated(0.0, 0.0, z_shift)); + glsafe(::glMultMatrixd(instance_matrix.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + ColorRGBA render_color; + for (size_t i = 0; i < cache_size; ++i) { + const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i]; + const bool point_selected = m_editing_mode ? m_editing_cache[i].selected : false; + + if (is_mesh_point_clipped(support_point.pos.cast())) + continue; + + // First decide about the color of the point. + if (picking) + render_color = picking_color_component(i); + else { + if (size_t(m_hover_id) == i && m_editing_mode) // ignore hover state unless editing mode is active + render_color = { 0.f, 1.f, 1.f, 1.f }; + else { // neigher hover nor picking + bool supports_new_island = m_lock_unique_islands && support_point.is_new_island; + if (m_editing_mode) { + if (point_selected) + render_color = { 1.f, 0.3f, 0.3f, 1.f}; + else + if (supports_new_island) + render_color = { 0.3f, 0.3f, 1.f, 1.f }; + else + render_color = { 0.7f, 0.7f, 0.7f, 1.f }; + } + else + render_color = { 0.5f, 0.5f, 0.5f, 1.f }; + } + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_cone.set_color(render_color); + m_sphere.set_color(render_color); + if (!picking) +#else + m_cone.set_color(-1, render_color); + m_sphere.set_color(-1, render_color); + if (shader && !picking) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + shader->set_uniform("emission_factor", 0.5f); + + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d support_matrix = Geometry::assemble_transform(support_point.pos.cast()) * instance_scaling_matrix_inverse; +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslatef(support_point.pos.x(), support_point.pos.y(), support_point.pos.z())); + glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (vol->is_left_handed()) + glFrontFace(GL_CW); + + // Matrices set, we can render the point mark now. + // If in editing mode, we'll also render a cone pointing to the sphere. + if (m_editing_mode) { + // in case the normal is not yet cached, find and cache it + if (m_editing_cache[i].normal == Vec3f::Zero()) + m_c->raycaster()->raycaster()->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal); + + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast()); + const Eigen::AngleAxisd aa(q); + const double cone_radius = 0.25; // mm + const double cone_height = 0.75; +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = view_matrix * instance_matrix * support_matrix * Transform3d(aa.toRotationMatrix()) * + Geometry::assemble_transform((cone_height + support_point.head_front_radius * RenderPointScale) * Vec3d::UnitZ(), + Vec3d(PI, 0.0, 0.0), Vec3d(cone_radius, cone_radius, cone_height)); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glPushMatrix()); + glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); + glsafe(::glTranslatef(0.f, 0.f, cone_height + support_point.head_front_radius * RenderPointScale)); + glsafe(::glRotated(180., 1., 0., 0.)); + glsafe(::glScaled(cone_radius, cone_radius, cone_height)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_cone.render(); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + + const double radius = (double)support_point.head_front_radius * RenderPointScale; +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = view_matrix * instance_matrix * support_matrix * + Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), radius * Vec3d::Ones()); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glPushMatrix()); + glsafe(::glScaled(radius, radius, radius)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_sphere.render(); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + if (vol->is_left_handed()) + glFrontFace(GL_CCW); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + + // Now render the drain holes: + if (has_holes && ! picking) { + render_color = { 0.7f, 0.7f, 0.7f, 0.7f }; +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_cylinder.set_color(render_color); +#else + m_cylinder.set_color(-1, render_color); + if (shader != nullptr) +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + shader->set_uniform("emission_factor", 0.5f); + for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { + if (is_mesh_point_clipped(drain_hole.pos.cast())) + continue; + +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d hole_matrix = Geometry::assemble_transform(drain_hole.pos.cast()) * instance_scaling_matrix_inverse; +#else + // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. + glsafe(::glPushMatrix()); + glsafe(::glTranslatef(drain_hole.pos.x(), drain_hole.pos.y(), drain_hole.pos.z())); + glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + if (vol->is_left_handed()) + glFrontFace(GL_CW); + + // Matrices set, we can render the point mark now. + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d::UnitZ(), instance_scaling_matrix_inverse * (-drain_hole.normal).cast()); + const Eigen::AngleAxisd aa(q); +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Transform3d view_model_matrix = view_matrix * instance_matrix * hole_matrix * Transform3d(aa.toRotationMatrix()) * + Geometry::assemble_transform(-drain_hole.height * Vec3d::UnitZ(), Vec3d::Zero(), Vec3d(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("normal_matrix", (Matrix3d)view_model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose()); +#else + glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis().x(), aa.axis().y(), aa.axis().z())); + glsafe(::glTranslated(0., 0., -drain_hole.height)); + glsafe(::glScaled(drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength)); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_cylinder.render(); + + if (vol->is_left_handed()) + glFrontFace(GL_CCW); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + } + } + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +} + + + +bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const +{ + if (m_c->object_clipper()->get_position() == 0.) + return false; + + auto sel_info = m_c->selection_info(); + int active_inst = m_c->selection_info()->get_active_instance(); + const ModelInstance* mi = sel_info->model_object()->instances[active_inst]; + const Transform3d& trafo = mi->get_transformation().get_matrix() * sel_info->model_object()->volumes.front()->get_matrix(); + + Vec3d transformed_point = trafo * point; + transformed_point(2) += sel_info->get_sla_shift(); + return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point); +} + + + +// 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 GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair& pos_and_normal) +{ + if (! m_c->raycaster()->raycaster()) + return false; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Selection& selection = m_parent.get_selection(); + const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); + + double clp_dist = m_c->object_clipper()->get_position(); + const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane(); + + // The raycaster query + Vec3f hit; + Vec3f normal; + if (m_c->raycaster()->raycaster()->unproject_on_mesh( + mouse_pos, + trafo.get_matrix(), + camera, + hit, + normal, + clp_dist != 0. ? clp : nullptr)) + { + // Check whether the hit is in a hole + bool in_hole = false; + // In case the hollowed and drilled mesh is available, we can allow + // placing points in holes, because they should never end up + // on surface that's been drilled away. + if (! m_c->hollowed_mesh()->get_hollowed_mesh()) { + sla::DrainHoles drain_holes = m_c->selection_info()->model_object()->sla_drain_holes; + for (const sla::DrainHole& hole : drain_holes) { + if (hole.is_inside(hit)) { + in_hole = true; + break; + } + } + } + if (! in_hole) { + // Return both the point and the facet normal. + pos_and_normal = std::make_pair(hit, normal); + return true; + } + } + + return false; +} + +// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. +// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is +// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo +// concludes that the event was not intended for it, it should return false. +bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + ModelObject* mo = m_c->selection_info()->model_object(); + int active_inst = m_c->selection_info()->get_active_instance(); + + if (m_editing_mode) { + + // left down with shift - show the selection rectangle: + if (action == SLAGizmoEventType::LeftDown && (shift_down || alt_down || control_down)) { + if (m_hover_id == -1) { + if (shift_down || alt_down) { + m_selection_rectangle.start_dragging(mouse_position, shift_down ? GLSelectionRectangle::EState::Select : GLSelectionRectangle::EState::Deselect); + } + } + else { + if (m_editing_cache[m_hover_id].selected) + unselect_point(m_hover_id); + else { + if (!alt_down) + select_point(m_hover_id); + } + } + + return true; + } + + // left down without selection rectangle - place point on the mesh: + if (action == SLAGizmoEventType::LeftDown && !m_selection_rectangle.is_dragging() && !shift_down) { + // If any point is in hover state, this should initiate its move - return control back to GLCanvas: + if (m_hover_id != -1) + return false; + + // If there is some selection, don't add new point and deselect everything instead. + if (m_selection_empty) { + std::pair pos_and_normal; + if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add support point")); + m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); + m_parent.set_as_dirty(); + m_wait_for_up_event = true; + } + else + return false; + } + else + select_point(NoPoints); + + return true; + } + + // left up with selection rectangle - select points inside the rectangle: + if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::ShiftUp || action == SLAGizmoEventType::AltUp) && m_selection_rectangle.is_dragging()) { + // Is this a selection or deselection rectangle? + GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); + + // First collect positions of all the points in world coordinates. + Geometry::Transformation trafo = mo->instances[active_inst]->get_transformation(); + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); + std::vector points; + for (unsigned int i=0; i()); + + // Now ask the rectangle which of the points are inside. + std::vector points_inside; + std::vector points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); + for (size_t idx : points_idxs) + points_inside.push_back(points[idx].cast()); + + // Only select/deselect points that are actually visible. We want to check not only + // the point itself, but also the center of base of its cone, so the points don't hide + // under every miniature irregularity on the model. Remember the actual number and + // append the cone bases. + size_t orig_pts_num = points_inside.size(); + for (size_t idx : points_idxs) + points_inside.emplace_back((trafo.get_matrix().cast() * (m_editing_cache[idx].support_point.pos + m_editing_cache[idx].normal)).cast()); + + for (size_t idx : m_c->raycaster()->raycaster()->get_unobscured_idxs( + trafo, wxGetApp().plater()->get_camera(), points_inside, + m_c->object_clipper()->get_clipping_plane())) + { + if (idx >= orig_pts_num) // this is a cone-base, get index of point it belongs to + idx -= orig_pts_num; + if (rectangle_status == GLSelectionRectangle::EState::Deselect) + unselect_point(points_idxs[idx]); + else + select_point(points_idxs[idx]); + } + return true; + } + + // left up with no selection rectangle + if (action == SLAGizmoEventType::LeftUp) { + if (m_wait_for_up_event) { + m_wait_for_up_event = false; + return true; + } + } + + // dragging the selection rectangle: + if (action == SLAGizmoEventType::Dragging) { + if (m_wait_for_up_event) + return true; // point has been placed and the button not released yet + // this prevents GLCanvas from starting scene rotation + + if (m_selection_rectangle.is_dragging()) { + m_selection_rectangle.dragging(mouse_position); + return true; + } + + return false; + } + + if (action == SLAGizmoEventType::Delete) { + // delete key pressed + delete_selected_points(); + return true; + } + + if (action == SLAGizmoEventType::ApplyChanges) { + editing_mode_apply_changes(); + return true; + } + + if (action == SLAGizmoEventType::DiscardChanges) { + ask_about_changes_call_after([this](){ editing_mode_apply_changes(); }, + [this](){ editing_mode_discard_changes(); }); + return true; + } + + if (action == SLAGizmoEventType::RightDown) { + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + delete_selected_points(); + return true; + } + return false; + } + + if (action == SLAGizmoEventType::SelectAll) { + select_point(AllPoints); + return true; + } + } + + if (!m_editing_mode) { + if (action == SLAGizmoEventType::AutomaticGeneration) { + auto_generate(); + return true; + } + + if (action == SLAGizmoEventType::ManualEditing) { + switch_to_editing_mode(); + return true; + } + } + + if (action == SLAGizmoEventType::MouseWheelUp && control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = std::min(1., pos + 0.01); + m_c->object_clipper()->set_position(pos, true); + return true; + } + + if (action == SLAGizmoEventType::MouseWheelDown && control_down) { + double pos = m_c->object_clipper()->get_position(); + pos = std::max(0., pos - 0.01); + m_c->object_clipper()->set_position(pos, true); + return true; + } + + if (action == SLAGizmoEventType::ResetClippingPlane) { + m_c->object_clipper()->set_position(-1., false); + return true; + } + + return false; +} + +void GLGizmoSlaSupports::delete_selected_points(bool force) +{ + if (! m_editing_mode) { + std::cout << "DEBUGGING: delete_selected_points called out of editing mode!" << std::endl; + std::abort(); + } + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point")); + + for (unsigned int idx=0; idx GLGizmoSlaSupports::get_config_options(const std::vector& keys) const +{ + std::vector out; + const ModelObject* mo = m_c->selection_info()->model_object(); + + if (! mo) + return out; + + const DynamicPrintConfig& object_cfg = mo->config.get(); + const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + std::unique_ptr default_cfg = nullptr; + + for (const std::string& key : keys) { + if (object_cfg.has(key)) + out.push_back(object_cfg.option(key)); + else + if (print_cfg.has(key)) + out.push_back(print_cfg.option(key)); + else { // we must get it from defaults + if (default_cfg == nullptr) + default_cfg.reset(DynamicPrintConfig::new_from_defaults_keys(keys)); + out.push_back(default_cfg->option(key)); + } + } + + return out; +} + + + +/* +void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB* aabb, const Vec3f& normal, double offset, std::vector& idxs) const +{ + if (aabb->is_leaf()) { // this is a facet + // corner.dot(normal) - offset + idxs.push_back(aabb->m_primitive); + } + else { // not a leaf + using CornerType = Eigen::AlignedBox::CornerType; + bool sign = std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(0)))); + for (unsigned int i=1; i<8; ++i) + if (std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(i)))) != sign) { + find_intersecting_facets(aabb->m_left, normal, offset, idxs); + find_intersecting_facets(aabb->m_right, normal, offset, idxs); + } + } +} + + + +void GLGizmoSlaSupports::make_line_segments() const +{ + TriangleMeshSlicer tms(&m_c->m_model_object->volumes.front()->mesh); + Vec3f normal(0.f, 1.f, 1.f); + double d = 0.; + + std::vector lines; + find_intersections(&m_AABB, normal, d, lines); + ExPolygons expolys; + tms.make_expolygons_simple(lines, &expolys); + + SVG svg("slice_loops.svg", get_extents(expolys)); + svg.draw(expolys); + //for (const IntersectionLine &l : lines[i]) + // svg.draw(l, "red", 0); + //svg.draw_outline(expolygons, "black", "blue", 0); + svg.Close(); +} +*/ + + +void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit) +{ + static float last_y = 0.0f; + static float last_h = 0.0f; + + ModelObject* mo = m_c->selection_info()->model_object(); + + if (! mo) + return; + + bool first_run = true; // This is a hack to redraw the button when all points are removed, + // so it is not delayed until the background process finishes. +RENDER_AGAIN: + //m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + //const ImVec2 window_size(m_imgui->scaled(18.f, 16.f)); + //ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); + //ImGui::SetNextWindowSize(ImVec2(window_size)); + + m_imgui->begin(get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + + // adjust window position to avoid overlap the view toolbar + float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + if ((last_h != win_h) || (last_y != y)) + { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) + last_h = win_h; + if (last_y != y) + last_y = y; + } + + // First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that: + + const float settings_sliders_left = std::max(m_imgui->calc_text_size(m_desc.at("minimal_distance")).x, m_imgui->calc_text_size(m_desc.at("points_density")).x) + m_imgui->scaled(1.f); + const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f); + const float diameter_slider_left = m_imgui->calc_text_size(m_desc.at("head_diameter")).x + m_imgui->scaled(1.f); + const float minimal_slider_width = m_imgui->scaled(4.f); + const float buttons_width_approx = m_imgui->calc_text_size(m_desc.at("apply_changes")).x + m_imgui->calc_text_size(m_desc.at("discard_changes")).x + m_imgui->scaled(1.5f); + const float lock_supports_width_approx = m_imgui->calc_text_size(m_desc.at("lock_supports")).x + m_imgui->scaled(2.f); + + float window_width = minimal_slider_width + std::max(std::max(settings_sliders_left, clipping_slider_left), diameter_slider_left); + window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx); + + bool force_refresh = false; + bool remove_selected = false; + bool remove_all = false; + + if (m_editing_mode) { + + float diameter_upper_cap = static_cast(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value; + if (m_new_point_head_diameter > diameter_upper_cap) + m_new_point_head_diameter = diameter_upper_cap; + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("head_diameter")); + ImGui::SameLine(diameter_slider_left); + ImGui::PushItemWidth(window_width - diameter_slider_left); + + // Following is a nasty way to: + // - save the initial value of the slider before one starts messing with it + // - keep updating the head radius during sliding so it is continuosly refreshed in 3D scene + // - take correct undo/redo snapshot after the user is done with moving the slider + float initial_value = m_new_point_head_diameter; + m_imgui->slider_float("##head_diameter", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f"); + if (m_imgui->get_last_slider_status().clicked) { + if (m_old_point_head_diameter == 0.f) + m_old_point_head_diameter = initial_value; + } + if (m_imgui->get_last_slider_status().edited) { + for (auto& cache_entry : m_editing_cache) + if (cache_entry.selected) + cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; + } + if (m_imgui->get_last_slider_status().deactivated_after_edit) { + // momentarily restore the old value to take snapshot + for (auto& cache_entry : m_editing_cache) + if (cache_entry.selected) + cache_entry.support_point.head_front_radius = m_old_point_head_diameter / 2.f; + float backup = m_new_point_head_diameter; + m_new_point_head_diameter = m_old_point_head_diameter; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Change point head diameter")); + m_new_point_head_diameter = backup; + for (auto& cache_entry : m_editing_cache) + if (cache_entry.selected) + cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; + m_old_point_head_diameter = 0.f; + } + + bool changed = m_lock_unique_islands; + m_imgui->checkbox(m_desc.at("lock_supports"), m_lock_unique_islands); + force_refresh |= changed != m_lock_unique_islands; + + m_imgui->disabled_begin(m_selection_empty); + remove_selected = m_imgui->button(m_desc.at("remove_selected")); + m_imgui->disabled_end(); + + m_imgui->disabled_begin(m_editing_cache.empty()); + remove_all = m_imgui->button(m_desc.at("remove_all")); + m_imgui->disabled_end(); + + m_imgui->text(" "); // vertical gap + + if (m_imgui->button(m_desc.at("apply_changes"))) { + editing_mode_apply_changes(); + force_refresh = true; + } + ImGui::SameLine(); + bool discard_changes = m_imgui->button(m_desc.at("discard_changes")); + if (discard_changes) { + editing_mode_discard_changes(); + force_refresh = true; + } + } + else { // not in editing mode: + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("minimal_distance")); + ImGui::SameLine(settings_sliders_left); + ImGui::PushItemWidth(window_width - settings_sliders_left); + + std::vector opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); + float density = static_cast(opts[0])->value; + float minimal_point_distance = static_cast(opts[1])->value; + + m_imgui->slider_float("##minimal_point_distance", &minimal_point_distance, 0.f, 20.f, "%.f mm"); + bool slider_clicked = m_imgui->get_last_slider_status().clicked; // someone clicked the slider + bool slider_edited = m_imgui->get_last_slider_status().edited; // someone is dragging the slider + bool slider_released = m_imgui->get_last_slider_status().deactivated_after_edit; // someone has just released the slider + + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("points_density")); + ImGui::SameLine(settings_sliders_left); + + m_imgui->slider_float("##points_density", &density, 0.f, 200.f, "%.f %%"); + slider_clicked |= m_imgui->get_last_slider_status().clicked; + slider_edited |= m_imgui->get_last_slider_status().edited; + slider_released |= m_imgui->get_last_slider_status().deactivated_after_edit; + + if (slider_clicked) { // stash the values of the settings so we know what to revert to after undo + m_minimal_point_distance_stash = minimal_point_distance; + m_density_stash = density; + } + if (slider_edited) { + mo->config.set("support_points_minimal_distance", minimal_point_distance); + mo->config.set("support_points_density_relative", (int)density); + } + if (slider_released) { + mo->config.set("support_points_minimal_distance", m_minimal_point_distance_stash); + mo->config.set("support_points_density_relative", (int)m_density_stash); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support parameter change")); + mo->config.set("support_points_minimal_distance", minimal_point_distance); + mo->config.set("support_points_density_relative", (int)density); + wxGetApp().obj_list()->update_and_show_object_settings_item(); + } + + bool generate = m_imgui->button(m_desc.at("auto_generate")); + + if (generate) + auto_generate(); + + ImGui::Separator(); + if (m_imgui->button(m_desc.at("manual_editing"))) + switch_to_editing_mode(); + + m_imgui->disabled_begin(m_normal_cache.empty()); + remove_all = m_imgui->button(m_desc.at("remove_all")); + m_imgui->disabled_end(); + + // m_imgui->text(""); + // m_imgui->text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) : + // (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) : + // (m_c->m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) : + // (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS")))); + } + + + // Following is rendered in both editing and non-editing mode: + ImGui::Separator(); + if (m_c->object_clipper()->get_position() == 0.f) { + ImGui::AlignTextToFramePadding(); + m_imgui->text(m_desc.at("clipping_of_view")); + } + else { + if (m_imgui->button(m_desc.at("reset_direction"))) { + wxGetApp().CallAfter([this](){ + m_c->object_clipper()->set_position(-1., false); + }); + } + } + + ImGui::SameLine(clipping_slider_left); + ImGui::PushItemWidth(window_width - clipping_slider_left); + float clp_dist = m_c->object_clipper()->get_position(); + if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f")) + m_c->object_clipper()->set_position(clp_dist, true); + + + if (m_imgui->button("?")) { + wxGetApp().CallAfter([]() { + SlaGizmoHelpDialog help_dlg; + help_dlg.ShowModal(); + }); + } + + m_imgui->end(); + + if (remove_selected || remove_all) { + force_refresh = false; + m_parent.set_as_dirty(); + bool was_in_editing = m_editing_mode; + if (! was_in_editing) + switch_to_editing_mode(); + if (remove_all) { + select_point(AllPoints); + delete_selected_points(true); // true - delete regardless of locked status + } + if (remove_selected) + delete_selected_points(false); // leave locked points + if (! was_in_editing) + editing_mode_apply_changes(); + + if (first_run) { + first_run = false; + goto RENDER_AGAIN; + } + } + + if (force_refresh) + m_parent.set_as_dirty(); +} + +bool GLGizmoSlaSupports::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA + || !selection.is_from_single_instance()) + return false; + + // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. + const Selection::IndicesList& list = selection.get_volume_idxs(); + for (const auto& idx : list) + if (selection.get_volume(idx)->is_outside && selection.get_volume(idx)->composite_id.volume_id >= 0) + return false; + + return true; +} + +bool GLGizmoSlaSupports::on_is_selectable() const +{ + return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA); +} + +std::string GLGizmoSlaSupports::on_get_name() const +{ + return _u8L("SLA Support Points"); +} + +CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const +{ + return CommonGizmosDataID( + int(CommonGizmosDataID::SelectionInfo) + | int(CommonGizmosDataID::InstancesHider) + | int(CommonGizmosDataID::Raycaster) + | int(CommonGizmosDataID::HollowedMesh) + | int(CommonGizmosDataID::ObjectClipper) + | int(CommonGizmosDataID::SupportsClipper)); +} + + + +void GLGizmoSlaSupports::ask_about_changes_call_after(std::function on_yes, std::function on_no) +{ + wxGetApp().CallAfter([on_yes, on_no]() { + // Following is called through CallAfter, because otherwise there was a problem + // on OSX with the wxMessageDialog being shown several times when clicked into. + MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually " + "edited support points?") + "\n",_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL ); + int ret = dlg.ShowModal(); + if (ret == wxID_YES) + on_yes(); + else if (ret == wxID_NO) + on_no(); + }); +} + + +void GLGizmoSlaSupports::on_set_state() +{ + if (m_state == m_old_state) + return; + + if (m_state == On && m_old_state != On) { // the gizmo was just turned on + // Set default head diameter from config. + const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; + } + if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off + bool will_ask = m_editing_mode && unsaved_changes() && on_is_activable(); + if (will_ask) { + ask_about_changes_call_after([this](){ editing_mode_apply_changes(); }, + [this](){ editing_mode_discard_changes(); }); + // refuse to be turned off so the gizmo is active when the CallAfter is executed + m_state = m_old_state; + } + else { + // we are actually shutting down + disable_editing_mode(); // so it is not active next time the gizmo opens + m_old_mo_id = -1; + } + } + m_old_state = m_state; +} + + + +void GLGizmoSlaSupports::on_start_dragging() +{ + if (m_hover_id != -1) { + select_point(NoPoints); + select_point(m_hover_id); + m_point_before_drag = m_editing_cache[m_hover_id]; + } + else + m_point_before_drag = CacheEntry(); +} + + +void GLGizmoSlaSupports::on_stop_dragging() +{ + if (m_hover_id != -1) { + CacheEntry backup = m_editing_cache[m_hover_id]; + + if (m_point_before_drag.support_point.pos != Vec3f::Zero() // some point was touched + && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected + { + m_editing_cache[m_hover_id] = m_point_before_drag; + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move support point")); + m_editing_cache[m_hover_id] = backup; + } + } + m_point_before_drag = CacheEntry(); +} + +void GLGizmoSlaSupports::on_dragging(const UpdateData &data) +{ + assert(m_hover_id != -1); + if (!m_editing_mode) return; + if (m_editing_cache[m_hover_id].support_point.is_new_island && m_lock_unique_islands) + return; + + std::pair pos_and_normal; + if (!unproject_on_mesh(data.mouse_pos.cast(), pos_and_normal)) + return; + + m_editing_cache[m_hover_id].support_point.pos = pos_and_normal.first; + m_editing_cache[m_hover_id].support_point.is_new_island = false; + m_editing_cache[m_hover_id].normal = pos_and_normal.second; +} + +void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar) +{ + ar(m_new_point_head_diameter, + m_normal_cache, + m_editing_cache, + m_selection_empty + ); +} + + + +void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const +{ + ar(m_new_point_head_diameter, + m_normal_cache, + m_editing_cache, + m_selection_empty + ); +} + + + +void GLGizmoSlaSupports::select_point(int i) +{ + if (! m_editing_mode) { + std::cout << "DEBUGGING: select_point called when out of editing mode!" << std::endl; + std::abort(); + } + + if (i == AllPoints || i == NoPoints) { + for (auto& point_and_selection : m_editing_cache) + point_and_selection.selected = ( i == AllPoints ); + m_selection_empty = (i == NoPoints); + + if (i == AllPoints) + m_new_point_head_diameter = m_editing_cache[0].support_point.head_front_radius * 2.f; + } + else { + m_editing_cache[i].selected = true; + m_selection_empty = false; + m_new_point_head_diameter = m_editing_cache[i].support_point.head_front_radius * 2.f; + } +} + + +void GLGizmoSlaSupports::unselect_point(int i) +{ + if (! m_editing_mode) { + std::cout << "DEBUGGING: unselect_point called when out of editing mode!" << std::endl; + std::abort(); + } + + m_editing_cache[i].selected = false; + m_selection_empty = true; + for (const CacheEntry& ce : m_editing_cache) { + if (ce.selected) { + m_selection_empty = false; + break; + } + } +} + + + + +void GLGizmoSlaSupports::editing_mode_discard_changes() +{ + if (! m_editing_mode) { + std::cout << "DEBUGGING: editing_mode_discard_changes called when out of editing mode!" << std::endl; + std::abort(); + } + select_point(NoPoints); + disable_editing_mode(); +} + + + +void GLGizmoSlaSupports::editing_mode_apply_changes() +{ + // If there are no changes, don't touch the front-end. The data in the cache could have been + // taken from the backend and copying them to ModelObject would needlessly invalidate them. + disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken + + if (unsaved_changes()) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support points edit")); + + m_normal_cache.clear(); + for (const CacheEntry& ce : m_editing_cache) + m_normal_cache.push_back(ce.support_point); + + ModelObject* mo = m_c->selection_info()->model_object(); + mo->sla_points_status = sla::PointsStatus::UserModified; + mo->sla_support_points.clear(); + mo->sla_support_points = m_normal_cache; + + reslice_SLA_supports(); + } +} + + + +void GLGizmoSlaSupports::reload_cache() +{ + const ModelObject* mo = m_c->selection_info()->model_object(); + m_normal_cache.clear(); + if (mo->sla_points_status == sla::PointsStatus::AutoGenerated || mo->sla_points_status == sla::PointsStatus::Generating) + get_data_from_backend(); + else + for (const sla::SupportPoint& point : mo->sla_support_points) + m_normal_cache.emplace_back(point); +} + + +bool GLGizmoSlaSupports::has_backend_supports() const +{ + const ModelObject* mo = m_c->selection_info()->model_object(); + if (! mo) + return false; + + // find SlaPrintObject with this ID + for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { + if (po->model_object()->id() == mo->id()) + return po->is_step_done(slaposSupportPoints); + } + return false; +} + +void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const +{ + wxGetApp().CallAfter([this, postpone_error_messages]() { + wxGetApp().plater()->reslice_SLA_supports( + *m_c->selection_info()->model_object(), postpone_error_messages); + }); +} + +bool GLGizmoSlaSupports::on_mouse(const wxMouseEvent &mouse_event){ + if (mouse_event.Moving()) return false; + if (use_grabbers(mouse_event)) return true; + + // wxCoord == int --> wx/types.h + Vec2i mouse_coord(mouse_event.GetX(), mouse_event.GetY()); + Vec2d mouse_pos = mouse_coord.cast(); + + static bool pending_right_up = false; + if (mouse_event.LeftDown()) { + bool grabber_contains_mouse = (get_hover_id() != -1); + bool control_down = mouse_event.CmdDown(); + if ((!control_down || grabber_contains_mouse) && + gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) + return true; + } else if (mouse_event.Dragging()) { + bool control_down = mouse_event.CmdDown(); + if (m_parent.get_move_volume_id() != -1) { + // don't allow dragging objects with the Sla gizmo on + return true; + } else if (!control_down && + gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false)) { + // the gizmo got the event and took some action, no need to do + // anything more here + m_parent.set_as_dirty(); + return true; + } else if (control_down && (mouse_event.LeftIsDown() || mouse_event.RightIsDown())){ + // CTRL has been pressed while already dragging -> stop current action + if (mouse_event.LeftIsDown()) + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), true); + else if (mouse_event.RightIsDown()) + pending_right_up = false; + } + } else if (mouse_event.LeftUp() && !m_parent.is_mouse_dragging()) { + // in case SLA/FDM gizmo is selected, we just pass the LeftUp event + // and stop processing - neither object moving or selecting is + // suppressed in that case + gizmo_event(SLAGizmoEventType::LeftUp, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), mouse_event.CmdDown()); + return true; + }else if (mouse_event.RightDown()){ + if (m_parent.get_selection().get_object_idx() != -1 && + gizmo_event(SLAGizmoEventType::RightDown, mouse_pos, false, false, false)) { + // we need to set the following right up as processed to avoid showing + // the context menu if the user release the mouse over the object + pending_right_up = true; + // event was taken care of by the SlaSupports gizmo + return true; + } + } else if (pending_right_up && mouse_event.RightUp()) { + pending_right_up = false; + return true; + } + return false; +} + +void GLGizmoSlaSupports::get_data_from_backend() +{ + if (! has_backend_supports()) + return; + ModelObject* mo = m_c->selection_info()->model_object(); + + // find the respective SLAPrintObject, we need a pointer to it + for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { + if (po->model_object()->id() == mo->id()) { + m_normal_cache.clear(); + const std::vector& points = po->get_support_points(); + auto mat = (po->trafo() * po->model_object()->volumes.front()->get_transformation().get_matrix()).inverse().cast(); + for (unsigned int i=0; isla_points_status = sla::PointsStatus::AutoGenerated; + break; + } + } + + // We don't copy the data into ModelObject, as this would stop the background processing. +} + + + +void GLGizmoSlaSupports::auto_generate() +{ + //wxMessageDialog dlg(GUI::wxGetApp().plater(), + MessageDialog dlg(GUI::wxGetApp().plater(), + _L("Autogeneration will erase all manually edited points.") + "\n\n" + + _L("Are you sure you want to do it?") + "\n", + _L("Warning"), wxICON_WARNING | wxYES | wxNO); + + ModelObject* mo = m_c->selection_info()->model_object(); + + if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points")); + wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); + mo->sla_points_status = sla::PointsStatus::Generating; + } +} + + + +void GLGizmoSlaSupports::switch_to_editing_mode() +{ + wxGetApp().plater()->enter_gizmos_stack(); + m_editing_mode = true; + m_editing_cache.clear(); + for (const sla::SupportPoint& sp : m_normal_cache) + m_editing_cache.emplace_back(sp); + select_point(NoPoints); + + m_c->instances_hider()->show_supports(false); + m_parent.set_as_dirty(); +} + + +void GLGizmoSlaSupports::disable_editing_mode() +{ + if (m_editing_mode) { + m_editing_mode = false; + wxGetApp().plater()->leave_gizmos_stack(); + m_c->instances_hider()->show_supports(true); + m_parent.set_as_dirty(); + } + wxGetApp().plater()->get_notification_manager()->close_notification_of_type(NotificationType::QuitSLAManualMode); +} + + + +bool GLGizmoSlaSupports::unsaved_changes() const +{ + if (m_editing_cache.size() != m_normal_cache.size()) + return true; + + for (size_t i=0; iSetFont(font); + + auto vsizer = new wxBoxSizer(wxVERTICAL); + auto gridsizer = new wxFlexGridSizer(2, 5, 15); + auto hsizer = new wxBoxSizer(wxHORIZONTAL); + + hsizer->AddSpacer(20); + hsizer->Add(vsizer); + hsizer->AddSpacer(20); + + vsizer->AddSpacer(20); + vsizer->Add(note_text, 1, wxALIGN_CENTRE_HORIZONTAL); + vsizer->AddSpacer(20); + vsizer->Add(gridsizer); + vsizer->AddSpacer(20); + + std::vector> shortcuts; + shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add point"))); + shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove point"))); + shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move point"))); + shortcuts.push_back(std::make_pair(ctrl+_L("Left click"), _L("Add point to selection"))); + shortcuts.push_back(std::make_pair(alt+_L("Left click"), _L("Remove point from selection"))); + shortcuts.push_back(std::make_pair(wxString("Shift+")+_L("Drag"), _L("Select by rectangle"))); + shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _L("Deselect by rectangle"))); + shortcuts.push_back(std::make_pair(ctrl+"A", _L("Select all points"))); + shortcuts.push_back(std::make_pair("Delete", _L("Remove selected points"))); + shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"), _L("Move clipping plane"))); + shortcuts.push_back(std::make_pair("R", _L("Reset clipping plane"))); + shortcuts.push_back(std::make_pair("Enter", _L("Apply changes"))); + shortcuts.push_back(std::make_pair("Esc", _L("Discard changes"))); + shortcuts.push_back(std::make_pair("M", _L("Switch to editing mode"))); + shortcuts.push_back(std::make_pair("A", _L("Auto-generate points"))); + + for (const auto& pair : shortcuts) { + auto shortcut = new wxStaticText(this, wxID_ANY, pair.first); + auto desc = new wxStaticText(this, wxID_ANY, pair.second); + shortcut->SetFont(bold_font); + desc->SetFont(font); + gridsizer->Add(shortcut, -1, wxALIGN_CENTRE_VERTICAL); + gridsizer->Add(desc, -1, wxALIGN_CENTRE_VERTICAL); + } + + SetSizer(hsizer); + hsizer->SetSizeHints(this); +} + + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index bbdcdeb34..777a4d7ba 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -1,370 +1,378 @@ -#include "MeshUtils.hpp" - -#include "libslic3r/Tesselate.hpp" -#include "libslic3r/TriangleMesh.hpp" -#include "libslic3r/TriangleMeshSlicer.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/Model.hpp" - -#if ENABLE_LEGACY_OPENGL_REMOVAL -#include "slic3r/GUI/GUI_App.hpp" -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#include "slic3r/GUI/Camera.hpp" -#if ENABLE_GL_SHADERS_ATTRIBUTES -#include "slic3r/GUI/Plater.hpp" -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#include - -#include - - -namespace Slic3r { -namespace GUI { - -void MeshClipper::set_plane(const ClippingPlane& plane) -{ - if (m_plane != plane) { - m_plane = plane; - m_triangles_valid = false; - } -} - - -void MeshClipper::set_limiting_plane(const ClippingPlane& plane) -{ - if (m_limiting_plane != plane) { - m_limiting_plane = plane; - m_triangles_valid = false; - } -} - - - -void MeshClipper::set_mesh(const TriangleMesh& mesh) -{ - if (m_mesh != &mesh) { - m_mesh = &mesh; - m_triangles_valid = false; - m_triangles2d.resize(0); - } -} - -void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) -{ - if (m_negative_mesh != &mesh) { - m_negative_mesh = &mesh; - m_triangles_valid = false; - m_triangles2d.resize(0); - } -} - - - -void MeshClipper::set_transformation(const Geometry::Transformation& trafo) -{ - if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) { - m_trafo = trafo; - m_triangles_valid = false; - m_triangles2d.resize(0); - } -} - -#if ENABLE_LEGACY_OPENGL_REMOVAL -void MeshClipper::render_cut(const ColorRGBA& color) -#else -void MeshClipper::render_cut() -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -{ - if (! m_triangles_valid) - recalculate_triangles(); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (m_model.vertices_count() == 0 || m_model.indices_count() == 0) - return; - - GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); - if (curr_shader != nullptr) - curr_shader->stop_using(); - - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - 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()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - m_model.set_color(color); - m_model.render(); - shader->stop_using(); - } - - if (curr_shader != nullptr) - curr_shader->start_using(); -#else - if (m_vertex_array.has_VBOs()) - m_vertex_array.render(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -} - - - -void MeshClipper::recalculate_triangles() -{ - const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast(); - // Calculate clipping plane normal in mesh coordinates. - const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast(); - const Vec3d up = up_noscale.cast().cwiseProduct(m_trafo.get_scaling_factor()); - // Calculate distance from mesh origin to the clipping plane (in mesh coordinates). - const float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm()); - - // Now do the cutting - MeshSlicingParams slicing_params; - slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ())); - - ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params)); - - if (m_negative_mesh && !m_negative_mesh->empty()) { - const ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params)); - expolys = diff_ex(expolys, neg_expolys); - } - - // Triangulate and rotate the cut into world coords: - Eigen::Quaterniond q; - q.setFromTwoVectors(Vec3d::UnitZ(), up); - Transform3d tr = Transform3d::Identity(); - tr.rotate(q); - tr = m_trafo.get_matrix() * tr; - - if (m_limiting_plane != ClippingPlane::ClipsNothing()) - { - // Now remove whatever ended up below the limiting plane (e.g. sinking objects). - // First transform the limiting plane from world to mesh coords. - // Note that inverse of tr transforms the plane from world to horizontal. - const Vec3d normal_old = m_limiting_plane.get_normal().normalized(); - const Vec3d normal_new = (tr.matrix().block<3,3>(0,0).transpose() * normal_old).normalized(); - - // normal_new should now be the plane normal in mesh coords. To find the offset, - // transform a point and set offset so it belongs to the transformed plane. - Vec3d pt = Vec3d::Zero(); - const double plane_offset = m_limiting_plane.get_data()[3]; - if (std::abs(normal_old.z()) > 0.5) // normal is normalized, at least one of the coords if larger than sqrt(3)/3 = 0.57 - pt.z() = - plane_offset / normal_old.z(); - else if (std::abs(normal_old.y()) > 0.5) - pt.y() = - plane_offset / normal_old.y(); - else - pt.x() = - plane_offset / normal_old.x(); - pt = tr.inverse() * pt; - const double offset = -(normal_new.dot(pt)); - - if (std::abs(normal_old.dot(m_plane.get_normal().normalized())) > 0.99) { - // The cuts are parallel, show all or nothing. - if (normal_old.dot(m_plane.get_normal().normalized()) < 0.0 && offset < height_mesh) - expolys.clear(); - } else { - // The cut is a horizontal plane defined by z=height_mesh. - // ax+by+e=0 is the line of intersection with the limiting plane. - // Normalized so a^2 + b^2 = 1. - const double len = std::hypot(normal_new.x(), normal_new.y()); - if (len == 0.) - return; - const double a = normal_new.x() / len; - const double b = normal_new.y() / len; - const double e = (normal_new.z() * height_mesh + offset) / len; - - // We need a half-plane to limit the cut. Get angle of the intersecting line. - double angle = (b != 0.0) ? std::atan(-a / b) : ((a < 0.0) ? -0.5 * M_PI : 0.5 * M_PI); - if (b > 0) // select correct half-plane - angle += M_PI; - - // We'll take a big rectangle above x-axis and rotate and translate - // it so it lies on our line. This will be the figure to subtract - // from the cut. The coordinates must not overflow after the transform, - // make the rectangle a bit smaller. - const coord_t size = (std::numeric_limits::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4; - Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})}; - ep.front().rotate(angle); - ep.front().translate(scale_(-e * a), scale_(-e * b)); - expolys = diff_ex(expolys, ep); - } - } - - m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.); - - tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting - -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_model.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; - init_data.reserve_vertices(m_triangles2d.size()); - init_data.reserve_indices(m_triangles2d.size()); - - // vertices + indices - for (auto it = m_triangles2d.cbegin(); it != m_triangles2d.cend(); it = it + 3) { - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); - const size_t idx = it - m_triangles2d.cbegin(); - init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); - } - - if (!init_data.is_empty()) - m_model.init_from(std::move(init_data)); -#else - m_vertex_array.release_geometry(); - for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) { - m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); - m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up); - m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up); - const size_t idx = it - m_triangles2d.cbegin(); - m_vertex_array.push_triangle(idx, idx+1, idx+2); - } - m_vertex_array.finalize_geometry(true); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - m_triangles_valid = true; -} - - -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) const -{ - 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(0), viewport[3] - mouse_pos(1), 0.), - modelview, projection, viewport, pt1); - igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 1.), - modelview, projection, viewport, pt2); - - Transform3d inv = trafo.inverse(); - pt1 = inv * pt1; - pt2 = inv * pt2; - - point = pt1; - direction = pt2-pt1; -} - - -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) 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 - - unsigned i = 0; - - // Remove points that are obscured or cut by the clipping plane. - // Also, remove anything below the bed (sinking objects). - for (i=0; i= SINKING_Z_THRESHOLD && - (! clipping_plane || ! clipping_plane->is_point_clipped(transformed_hit))) - break; - } - - 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; - } - - // Now stuff the points in the provided vector and calculate normals if asked about them: - position = hits[i].position().cast(); - normal = hits[i].normal().cast(); - - if (facet_idx) - *facet_idx = hits[i].face(); - - return true; -} - - -std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector& points, - const ClippingPlane* clipping_plane) const -{ - std::vector out; - - const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true); - Vec3d direction_to_camera = -camera.get_dir_forward(); - Vec3d direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera).normalized().eval(); - direction_to_camera_mesh = direction_to_camera_mesh.cwiseProduct(trafo.get_scaling_factor()); - const Transform3d inverse_trafo = trafo.get_matrix().inverse(); - - for (size_t i=0; iis_point_clipped(pt.cast())) - continue; - - bool is_obscured = false; - // Cast a ray in the direction of the camera and look for intersection with the mesh: - std::vector hits; - // Offset the start of the ray by EPSILON to account for numerical inaccuracies. - hits = m_emesh.query_ray_hits((inverse_trafo * pt.cast() + direction_to_camera_mesh * EPSILON), - direction_to_camera_mesh); - - if (! hits.empty()) { - // If the closest hit facet normal points in the same direction as the ray, - // we are looking through the mesh and should therefore discard the point: - if (hits.front().normal().dot(direction_to_camera_mesh.cast()) > 0) - is_obscured = true; - - // Eradicate all hits that the caller wants to ignore - for (unsigned j=0; jis_point_clipped(trafo.get_matrix() * hits[j].position())) { - hits.erase(hits.begin()+j); - --j; - } - } - - // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. - // Also, the threshold is in mesh coordinates, not in actual dimensions. - if (! hits.empty()) - is_obscured = true; - } - if (! is_obscured) - out.push_back(i); - } - return out; -} - - -Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const -{ - int idx = 0; - Vec3d closest_point; - m_emesh.squared_distance(point.cast(), idx, closest_point); - if (normal) - *normal = m_normals[idx]; - - return closest_point.cast(); -} - -int MeshRaycaster::get_closest_facet(const Vec3f &point) const -{ - int facet_idx = 0; - Vec3d closest_point; - m_emesh.squared_distance(point.cast(), facet_idx, closest_point); - return facet_idx; -} - -} // namespace GUI -} // namespace Slic3r +#include "MeshUtils.hpp" + +#include "libslic3r/Tesselate.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Model.hpp" + +#if ENABLE_LEGACY_OPENGL_REMOVAL +#include "slic3r/GUI/GUI_App.hpp" +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#include "slic3r/GUI/Camera.hpp" +#if ENABLE_GL_SHADERS_ATTRIBUTES +#include "slic3r/GUI/Plater.hpp" +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + +#include + +#include + + +namespace Slic3r { +namespace GUI { + +void MeshClipper::set_plane(const ClippingPlane& plane) +{ + if (m_plane != plane) { + m_plane = plane; + m_triangles_valid = false; + } +} + + +void MeshClipper::set_limiting_plane(const ClippingPlane& plane) +{ + if (m_limiting_plane != plane) { + m_limiting_plane = plane; + m_triangles_valid = false; + } +} + + + +void MeshClipper::set_mesh(const TriangleMesh& mesh) +{ + if (m_mesh != &mesh) { + m_mesh = &mesh; + m_triangles_valid = false; + m_triangles2d.resize(0); + } +} + +void MeshClipper::set_negative_mesh(const TriangleMesh& mesh) +{ + if (m_negative_mesh != &mesh) { + m_negative_mesh = &mesh; + m_triangles_valid = false; + m_triangles2d.resize(0); + } +} + + + +void MeshClipper::set_transformation(const Geometry::Transformation& trafo) +{ + if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) { + m_trafo = trafo; + m_triangles_valid = false; + m_triangles2d.resize(0); + } +} + +#if ENABLE_LEGACY_OPENGL_REMOVAL +void MeshClipper::render_cut(const ColorRGBA& color) +#else +void MeshClipper::render_cut() +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +{ + if (! m_triangles_valid) + recalculate_triangles(); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (m_model.vertices_count() == 0 || m_model.indices_count() == 0) + return; + + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + if (curr_shader != nullptr) + curr_shader->stop_using(); + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#if ENABLE_GL_SHADERS_ATTRIBUTES + 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()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + m_model.set_color(color); + m_model.render(); + shader->stop_using(); + } + + if (curr_shader != nullptr) + curr_shader->start_using(); +#else + if (m_vertex_array.has_VBOs()) + m_vertex_array.render(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +} + + + +void MeshClipper::recalculate_triangles() +{ +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3f instance_matrix_no_translation_no_scaling = m_trafo.get_rotation_matrix().cast(); +#else + const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + // Calculate clipping plane normal in mesh coordinates. + const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast(); + const Vec3d up = up_noscale.cast().cwiseProduct(m_trafo.get_scaling_factor()); + // Calculate distance from mesh origin to the clipping plane (in mesh coordinates). + const float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm()); + + // Now do the cutting + MeshSlicingParams slicing_params; + slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ())); + + ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params)); + + if (m_negative_mesh && !m_negative_mesh->empty()) { + const ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params)); + expolys = diff_ex(expolys, neg_expolys); + } + + // Triangulate and rotate the cut into world coords: + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d::UnitZ(), up); + Transform3d tr = Transform3d::Identity(); + tr.rotate(q); + tr = m_trafo.get_matrix() * tr; + + if (m_limiting_plane != ClippingPlane::ClipsNothing()) + { + // Now remove whatever ended up below the limiting plane (e.g. sinking objects). + // First transform the limiting plane from world to mesh coords. + // Note that inverse of tr transforms the plane from world to horizontal. + const Vec3d normal_old = m_limiting_plane.get_normal().normalized(); + const Vec3d normal_new = (tr.matrix().block<3,3>(0,0).transpose() * normal_old).normalized(); + + // normal_new should now be the plane normal in mesh coords. To find the offset, + // transform a point and set offset so it belongs to the transformed plane. + Vec3d pt = Vec3d::Zero(); + const double plane_offset = m_limiting_plane.get_data()[3]; + if (std::abs(normal_old.z()) > 0.5) // normal is normalized, at least one of the coords if larger than sqrt(3)/3 = 0.57 + pt.z() = - plane_offset / normal_old.z(); + else if (std::abs(normal_old.y()) > 0.5) + pt.y() = - plane_offset / normal_old.y(); + else + pt.x() = - plane_offset / normal_old.x(); + pt = tr.inverse() * pt; + const double offset = -(normal_new.dot(pt)); + + if (std::abs(normal_old.dot(m_plane.get_normal().normalized())) > 0.99) { + // The cuts are parallel, show all or nothing. + if (normal_old.dot(m_plane.get_normal().normalized()) < 0.0 && offset < height_mesh) + expolys.clear(); + } else { + // The cut is a horizontal plane defined by z=height_mesh. + // ax+by+e=0 is the line of intersection with the limiting plane. + // Normalized so a^2 + b^2 = 1. + const double len = std::hypot(normal_new.x(), normal_new.y()); + if (len == 0.) + return; + const double a = normal_new.x() / len; + const double b = normal_new.y() / len; + const double e = (normal_new.z() * height_mesh + offset) / len; + + // We need a half-plane to limit the cut. Get angle of the intersecting line. + double angle = (b != 0.0) ? std::atan(-a / b) : ((a < 0.0) ? -0.5 * M_PI : 0.5 * M_PI); + if (b > 0) // select correct half-plane + angle += M_PI; + + // We'll take a big rectangle above x-axis and rotate and translate + // it so it lies on our line. This will be the figure to subtract + // from the cut. The coordinates must not overflow after the transform, + // make the rectangle a bit smaller. + const coord_t size = (std::numeric_limits::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4; + Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})}; + ep.front().rotate(angle); + ep.front().translate(scale_(-e * a), scale_(-e * b)); + expolys = diff_ex(expolys, ep); + } + } + + m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.); + + tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting + +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_model.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_vertices(m_triangles2d.size()); + init_data.reserve_indices(m_triangles2d.size()); + + // vertices + indices + for (auto it = m_triangles2d.cbegin(); it != m_triangles2d.cend(); it = it + 3) { + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + init_data.add_vertex((Vec3f)(tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast(), (Vec3f)up.cast()); + const size_t idx = it - m_triangles2d.cbegin(); + init_data.add_triangle((unsigned int)idx, (unsigned int)idx + 1, (unsigned int)idx + 2); + } + + if (!init_data.is_empty()) + m_model.init_from(std::move(init_data)); +#else + m_vertex_array.release_geometry(); + for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) { + m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); + m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up); + m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up); + const size_t idx = it - m_triangles2d.cbegin(); + m_vertex_array.push_triangle(idx, idx+1, idx+2); + } + m_vertex_array.finalize_geometry(true); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + m_triangles_valid = true; +} + + +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) const +{ + 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(0), viewport[3] - mouse_pos(1), 0.), + modelview, projection, viewport, pt1); + igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 1.), + modelview, projection, viewport, pt2); + + Transform3d inv = trafo.inverse(); + pt1 = inv * pt1; + pt2 = inv * pt2; + + point = pt1; + direction = pt2-pt1; +} + + +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) 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 + + unsigned i = 0; + + // Remove points that are obscured or cut by the clipping plane. + // Also, remove anything below the bed (sinking objects). + for (i=0; i= SINKING_Z_THRESHOLD && + (! clipping_plane || ! clipping_plane->is_point_clipped(transformed_hit))) + break; + } + + 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; + } + + // Now stuff the points in the provided vector and calculate normals if asked about them: + position = hits[i].position().cast(); + normal = hits[i].normal().cast(); + + if (facet_idx) + *facet_idx = hits[i].face(); + + return true; +} + + +std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector& points, + const ClippingPlane* clipping_plane) const +{ + std::vector out; + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Transform3d instance_matrix_no_translation_no_scaling = trafo.get_rotation_matrix(); +#else + const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d direction_to_camera = -camera.get_dir_forward(); + Vec3d direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera).normalized().eval(); + direction_to_camera_mesh = direction_to_camera_mesh.cwiseProduct(trafo.get_scaling_factor()); + const Transform3d inverse_trafo = trafo.get_matrix().inverse(); + + for (size_t i=0; iis_point_clipped(pt.cast())) + continue; + + bool is_obscured = false; + // Cast a ray in the direction of the camera and look for intersection with the mesh: + std::vector hits; + // Offset the start of the ray by EPSILON to account for numerical inaccuracies. + hits = m_emesh.query_ray_hits((inverse_trafo * pt.cast() + direction_to_camera_mesh * EPSILON), + direction_to_camera_mesh); + + if (! hits.empty()) { + // If the closest hit facet normal points in the same direction as the ray, + // we are looking through the mesh and should therefore discard the point: + if (hits.front().normal().dot(direction_to_camera_mesh.cast()) > 0) + is_obscured = true; + + // Eradicate all hits that the caller wants to ignore + for (unsigned j=0; jis_point_clipped(trafo.get_matrix() * hits[j].position())) { + hits.erase(hits.begin()+j); + --j; + } + } + + // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. + // Also, the threshold is in mesh coordinates, not in actual dimensions. + if (! hits.empty()) + is_obscured = true; + } + if (! is_obscured) + out.push_back(i); + } + return out; +} + + +Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const +{ + int idx = 0; + Vec3d closest_point; + m_emesh.squared_distance(point.cast(), idx, closest_point); + if (normal) + *normal = m_normals[idx]; + + return closest_point.cast(); +} + +int MeshRaycaster::get_closest_facet(const Vec3f &point) const +{ + int facet_idx = 0; + Vec3d closest_point; + m_emesh.squared_distance(point.cast(), facet_idx, closest_point); + return facet_idx; +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cd9f221b4..0c1985733 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3492,7 +3492,11 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); new_volume->set_transformation(old_volume->get_transformation()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); +#else new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); if (old_volume->source.is_converted_from_inches) new_volume->convert_from_imperial_units(); @@ -3847,10 +3851,16 @@ void Plater::priv::reload_from_disk() new_volume->config.apply(old_volume->config); new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + new_volume->set_transformation(Geometry::assemble_transform(old_volume->source.transform.get_offset()) * + old_volume->get_transformation().get_matrix_no_offset() * old_volume->source.transform.get_matrix_no_offset()); + new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); +#else new_volume->set_transformation(Geometry::assemble_transform(old_volume->source.transform.get_offset()) * old_volume->get_transformation().get_matrix(true) * old_volume->source.transform.get_matrix(true)); new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES new_volume->source.object_idx = old_volume->source.object_idx; new_volume->source.volume_idx = old_volume->source.volume_idx; assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 3bb2db1ac..45d80a145 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -723,7 +723,11 @@ const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const const GLVolume& volume = *(*m_volumes)[i]; if (volume.is_modifier) continue; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d trafo = volume.get_instance_transformation().get_matrix_no_scaling_factor() * volume.get_volume_transformation().get_matrix(); +#else Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES trafo.translation().z() += volume.get_sla_shift_z(); (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); } @@ -1486,8 +1490,14 @@ void Selection::render(float scale_factor) } else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) { const GLVolume& v = *get_volume(*get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + box = v.transformed_convex_hull_bounding_box( + v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); + trafo = v.get_instance_transformation().get_matrix_no_scaling_factor() * v.get_volume_transformation().get_matrix_no_scaling_factor(); +#else box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { const Selection::IndicesList& ids = get_volume_idxs(); @@ -1495,8 +1505,13 @@ void Selection::render(float scale_factor) const GLVolume& v = *get_volume(id); box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + box = box.transformed(get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix()); + trafo = get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor(); +#else box = box.transformed(get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); trafo = get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } render_bounding_box(box, trafo, ColorRGB::WHITE()); @@ -1516,11 +1531,7 @@ void Selection::render_center(bool gizmo_is_dragging) return; #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -1565,11 +1576,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) return; #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat_attr" : "gouraud_light_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader(boost::starts_with(sidebar_field, "layer") ? "flat" : "gouraud_light"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -1622,7 +1629,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES Transform3d orient_matrix = Transform3d::Identity(); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); +#else orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SHOW_AXES axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); #else @@ -1667,13 +1678,21 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES if (wxGetApp().obj_manipul()->is_local_coordinates()) { const GLVolume* v = (*m_volumes)[*m_list.begin()]; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + orient_matrix = v->get_instance_transformation().get_rotation_matrix() * v->get_volume_transformation().get_rotation_matrix(); +#else orient_matrix = v->get_instance_transformation().get_matrix(true, false, true, true) * v->get_volume_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SHOW_AXES axes_center = (*m_volumes)[*m_list.begin()]->world_matrix().translation(); #endif // ENABLE_WORLD_COORDINATE_SHOW_AXES } else { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); +#else orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SHOW_AXES axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); } @@ -1700,7 +1719,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) else { #if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES if (requires_local_axes()) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); +#else orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else glsafe(::glTranslated(center.x(), center.y(), center.z())); if (requires_local_axes()) { @@ -2357,11 +2380,7 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con glsafe(::glLineWidth(2.0f * m_scale_factor)); -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("flat_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader("flat"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; @@ -3061,8 +3080,13 @@ void Selection::paste_volumes_from_clipboard() { ModelInstance* dst_instance = dst_object->instances[dst_inst_idx]; BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix_no_offset(); + Transform3d dst_matrix = dst_instance->get_transformation().get_matrix_no_offset(); +#else Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true); Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix); // used to keep relative position of multivolume selections when pasting from another object From 2f6f73e10f07a5aa16b15efbd3196e45dc756ad1 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 2 May 2022 12:21:48 +0200 Subject: [PATCH 115/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::translate(const Vec3d& displacement, ECoordinatesType type) to use matrix multiplication Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 2 +- src/libslic3r/Geometry.hpp | 4 +- src/libslic3r/Model.hpp | 2 +- src/slic3r/GUI/3DScene.hpp | 6 ++- src/slic3r/GUI/Selection.cpp | 81 ++++++++++++++++++++++++++++++++++-- 5 files changed, 86 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index fba1b2378..44e68a9d8 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -712,7 +712,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation // No need to run the non-linear least squares fitting for uniform scaling. // Just set the inverse. #if ENABLE_TRANSFORMATIONS_BY_MATRICES - out = instance_transformation.get_matrix_no_offset().inverse(); + out.set_matrix(instance_transformation.get_matrix_no_offset().inverse()); #else out.set_from_transform(instance_transformation.get_matrix(true).inverse()); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index ffe96cd3a..d0548f765 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -398,8 +398,6 @@ public: #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_TRANSFORMATIONS_BY_MATRICES - Transformation& operator = (const Transform3d& transform) { m_matrix = transform; return *this; } - Vec3d get_offset() const { return m_matrix.translation(); } double get_offset(Axis axis) const { return get_offset()[axis]; } @@ -481,6 +479,8 @@ public: const Transform3d& get_matrix() const { return m_matrix; } Transform3d get_matrix_no_offset() const; Transform3d get_matrix_no_scaling_factor() const; + + void set_matrix(const Transform3d& transform) { m_matrix = transform; } #else const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index f2844abcb..016689a46 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -692,7 +692,7 @@ public: const Geometry::Transformation& get_transformation() const { return m_transformation; } void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } #if ENABLE_TRANSFORMATIONS_BY_MATRICES - void set_transformation(const Transform3d& trafo) { m_transformation = trafo; } + void set_transformation(const Transform3d& trafo) { m_transformation.set_matrix(trafo); } #else void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); } #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 263d8af08..9fb5a6965 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -432,8 +432,9 @@ public: const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; } void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); } - #if ENABLE_TRANSFORMATIONS_BY_MATRICES + void set_instance_transformation(const Transform3d& transform) { m_instance_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); } + Vec3d get_instance_offset() const { return m_instance_transformation.get_offset(); } #else const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); } @@ -471,8 +472,9 @@ public: const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; } void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); } - #if ENABLE_TRANSFORMATIONS_BY_MATRICES + void set_volume_transformation(const Transform3d& transform) { m_volume_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); } + Vec3d get_volume_offset() const { return m_volume_transformation.get_offset(); } #else const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 45d80a145..1f0e77e16 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -763,6 +763,81 @@ void Selection::setup_cache() set_caches(); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void Selection::translate(const Vec3d& displacement, ECoordinatesType type) +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + const VolumeCache& volume_data = m_cache.volumes_data[i]; + if (m_mode == Instance && !v.is_wipe_tower) { + switch (type) + { + case ECoordinatesType::World: + { + if (is_from_fully_selected_instance(i)) + v.set_instance_transformation(Geometry::assemble_transform(displacement) * volume_data.get_instance_full_matrix()); + else + assert(false); + + break; + } + case ECoordinatesType::Local: + { + if (is_from_fully_selected_instance(i)) { + const Vec3d world_displacemet = volume_data.get_instance_rotation_matrix() * displacement; + v.set_instance_transformation(Geometry::assemble_transform(world_displacemet) * volume_data.get_instance_full_matrix()); + } + else + assert(false); + + break; + } + default: { assert(false); break; } + } + } + else { + switch (type) + { + case ECoordinatesType::World: + { + const Transform3d inst_matrix_no_offset = volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix(); + const Vec3d inst_displacement = inst_matrix_no_offset.inverse() * displacement; + v.set_volume_transformation(Geometry::assemble_transform(inst_displacement) * volume_data.get_volume_full_matrix()); + break; + } + case ECoordinatesType::Instance: + { + const Vec3d inst_displacement = volume_data.get_instance_scale_matrix().inverse() * displacement; + v.set_volume_transformation(Geometry::assemble_transform(inst_displacement) * volume_data.get_volume_full_matrix()); + break; + } + case ECoordinatesType::Local: + { + const Vec3d inst_displacement = volume_data.get_instance_scale_matrix().inverse() * + volume_data.get_volume_rotation_matrix() * displacement; + v.set_volume_transformation(Geometry::assemble_transform(inst_displacement) * volume_data.get_volume_full_matrix()); + break; + } + default: { assert(false); break; } + } + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + synchronize_unselected_instances(SyncRotationType::NONE); + else if (m_mode == Volume) + synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + ensure_not_below_bed(); + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} +#else #if ENABLE_WORLD_COORDINATE void Selection::translate(const Vec3d& displacement, ECoordinatesType type) #else @@ -835,6 +910,7 @@ void Selection::translate(const Vec3d& displacement, bool local) set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Rotate an object around one of the axes. Only one rotation component is expected to be changing. void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) @@ -1491,9 +1567,8 @@ void Selection::render(float scale_factor) else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) { const GLVolume& v = *get_volume(*get_volume_idxs().begin()); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - box = v.transformed_convex_hull_bounding_box( - v.get_instance_transformation().get_scaling_factor_matrix() * v.get_volume_transformation().get_scaling_factor_matrix()); - trafo = v.get_instance_transformation().get_matrix_no_scaling_factor() * v.get_volume_transformation().get_matrix_no_scaling_factor(); + box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_scaling_factor_matrix()); + trafo = v.get_instance_transformation().get_matrix() * v.get_volume_transformation().get_matrix_no_scaling_factor(); #else box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false); From 689b274fd3b47fb2365848b54960a182961a8ce4 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Fri, 3 Jun 2022 14:57:07 +0200 Subject: [PATCH 116/169] updated bundle version to 0.1.5 --- resources/profiles/Creality.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index d57a885ba..ce39cd6d6 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -5,7 +5,7 @@ name = Creality # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.1.4 +config_version = 0.1.5 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% From 0b617e2cf6b7fb5aa3f4c8e0098f0dfa509bb8b6 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Fri, 3 Jun 2022 15:00:48 +0200 Subject: [PATCH 117/169] Fixed min_slic3r_version --- resources/profiles/INAT.idx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/profiles/INAT.idx b/resources/profiles/INAT.idx index 6087bae62..0446399a1 100644 --- a/resources/profiles/INAT.idx +++ b/resources/profiles/INAT.idx @@ -1,5 +1,6 @@ -min_slic3r_version = 2.4.1 +min_slic3r_version = 2.5.0-alpha0 0.0.4 Improve Proton X profiles, Add Proton XE-750 printer +min_slic3r_version = 2.4.1 0.0.3 Set default filament profile. 0.0.2 Improved start gcode, changed filename format 0.0.1 Initial version From 55be16d158ce72c860700e26ae9bd19b03e0af95 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 2 May 2022 12:46:13 +0200 Subject: [PATCH 118/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) to use matrix multiplication and removed unused method void Selection::translate(unsigned int object_idx, const Vec3d& displacement) Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 10 ++++++++++ src/slic3r/GUI/Selection.hpp | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 1f0e77e16..7bc3ce91a 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1292,6 +1292,7 @@ void Selection::mirror(Axis axis) set_bounding_boxes_dirty(); } +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES void Selection::translate(unsigned int object_idx, const Vec3d& displacement) { if (!m_valid) @@ -1340,6 +1341,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) this->set_bounding_boxes_dirty(); } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) { @@ -1349,7 +1351,11 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; if (v.object_idx() == (int)object_idx && v.instance_idx() == (int)instance_idx) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + v.set_instance_transformation(Geometry::assemble_transform(displacement) * v.get_instance_transformation().get_matrix()); +#else v.set_instance_offset(v.get_instance_offset() + displacement); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } std::set done; // prevent processing volumes twice @@ -1382,7 +1388,11 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co if (v.object_idx() != object_idx || v.instance_idx() != (int)instance_idx) continue; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + v.set_instance_transformation(Geometry::assemble_transform(displacement) * v.get_instance_transformation().get_matrix()); +#else v.set_instance_offset(v.get_instance_offset() + displacement); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES done.insert(j); } } diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 95a73f8f7..012fb0e4d 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -363,7 +363,9 @@ public: void scale_to_fit_print_volume(const BuildVolume& volume); void mirror(Axis axis); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES void translate(unsigned int object_idx, const Vec3d& displacement); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED From 9f503b95e8494d31e4c05fb0c8b48167650bdd8b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 4 May 2022 10:28:59 +0200 Subject: [PATCH 119/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) to use matrix multiplication Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 12 ++ src/libslic3r/Geometry.hpp | 21 ++- src/slic3r/GUI/GLCanvas3D.cpp | 33 +++-- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 12 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 63 ++++++--- src/slic3r/GUI/Selection.cpp | 163 ++++++++++++++++++------ src/slic3r/GUI/Selection.hpp | 29 +++-- 7 files changed, 256 insertions(+), 77 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 44e68a9d8..031e489f7 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -314,6 +314,18 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation, } #if ENABLE_TRANSFORMATIONS_BY_MATRICES +void assemble_transform(Transform3d& transform, const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror) +{ + transform = translation * rotation * scale * mirror; +} + +Transform3d assemble_transform(const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror) +{ + Transform3d transform; + assemble_transform(transform, translation, rotation, scale, mirror); + return transform; +} + void rotation_transform(Transform3d& transform, const Vec3d& rotation) { transform = Transform3d::Identity(); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index d0548f765..c55867023 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -323,7 +323,8 @@ bool arrange( // 4) rotate Y // 5) rotate Z // 6) translate -void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); +void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), + const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); // Returns the transform obtained by assembling the given transformations in the following order: // 1) mirror @@ -332,9 +333,21 @@ void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d // 4) rotate Y // 5) rotate Z // 6) translate -Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); +Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), + const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); #if ENABLE_TRANSFORMATIONS_BY_MATRICES +// Sets the given transform by multiplying the given transformations in the following order: +// T = translation * rotation * scale * mirror +void assemble_transform(Transform3d& transform, const Transform3d& translation = Transform3d::Identity(), + const Transform3d& rotation = Transform3d::Identity(), const Transform3d& scale = Transform3d::Identity(), + const Transform3d& mirror = Transform3d::Identity()); + +// Returns the transform obtained by multiplying the given transformations in the following order: +// T = translation * rotation * scale * mirror +Transform3d assemble_transform(const Transform3d& translation = Transform3d::Identity(), const Transform3d& rotation = Transform3d::Identity(), + const Transform3d& scale = Transform3d::Identity(), const Transform3d& mirror = Transform3d::Identity()); + // Sets the given transform by assembling the given rotations in the following order: // 1) rotate X // 2) rotate Y @@ -391,10 +404,10 @@ class Transformation public: #if ENABLE_TRANSFORMATIONS_BY_MATRICES Transformation() = default; - explicit Transformation(const Transform3d & transform) : m_matrix(transform) {} + explicit Transformation(const Transform3d& transform) : m_matrix(transform) {} #else Transformation(); - explicit Transformation(const Transform3d & transform); + explicit Transformation(const Transform3d& transform); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_TRANSFORMATIONS_BY_MATRICES diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index adab374f5..f3ba04c50 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3825,9 +3825,17 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); +#else model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES else if (selection_mode == Selection::Volume) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); +#else model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES object_moved = true; model_object->invalidate_bounding_box(); @@ -3907,8 +3915,8 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) int object_idx = v->object_idx(); if (object_idx == 1000) { // the wipe tower #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - Vec3d offset = v->get_volume_offset(); - post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2)))); + const Vec3d offset = v->get_volume_offset(); + post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset.x(), offset.y(), v->get_volume_rotation().z()))); } #if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL int object_idx = v->object_idx(); @@ -3916,8 +3924,8 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) continue; - int instance_idx = v->instance_idx(); - int volume_idx = v->volume_idx(); + const int instance_idx = v->instance_idx(); + const int volume_idx = v->volume_idx(); done.insert(std::pair(object_idx, instance_idx)); @@ -3925,12 +3933,20 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); +#else model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (selection_mode == Selection::Volume) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); +#else model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation()); model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } model_object->invalidate_bounding_box(); } @@ -7347,13 +7363,11 @@ bool GLCanvas3D::_is_any_volume_outside() const void GLCanvas3D::_update_selection_from_hover() { bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); - bool selection_changed = false; - if (m_hover_volume_idxs.empty()) { - if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) { - selection_changed = ! m_selection.is_empty(); + if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) m_selection.remove_all(); - } + + return; } GLSelectionRectangle::EState state = m_rectangle_selection.get_state(); @@ -7366,6 +7380,7 @@ void GLCanvas3D::_update_selection_from_hover() } } + bool selection_changed = false; #if ENABLE_NEW_RECTANGLE_SELECTION if (!m_rectangle_selection.is_empty()) { #endif // ENABLE_NEW_RECTANGLE_SELECTION diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 52dd207e9..42fe796a4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -54,9 +54,7 @@ bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) { } void GLGizmoMove3D::data_changed() { - const Selection &selection = m_parent.get_selection(); - bool is_wipe_tower = selection.is_wipe_tower(); - m_grabbers[2].enabled = !is_wipe_tower; + m_grabbers[2].enabled = !m_parent.get_selection().is_wipe_tower(); } bool GLGizmoMove3D::on_init() @@ -100,11 +98,19 @@ void GLGizmoMove3D::on_start_dragging() m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center; +#else m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center; +#else m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } m_starting_box_center = m_center; m_starting_box_bottom_center = m_center; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 6579a1431..51d089873 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -287,7 +287,15 @@ void GLGizmoRotate::on_render_for_picking() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + ECoordinatesType coordinates_type; + if (selection.is_wipe_tower()) + coordinates_type = ECoordinatesType::Local; + else + coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); +#else const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (coordinates_type == ECoordinatesType::World) { m_bounding_box = selection.get_bounding_box(); m_center = m_bounding_box.center(); @@ -326,7 +334,11 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) if (coordinates_type == ECoordinatesType::World) m_orient_matrix = Transform3d::Identity(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + else if (coordinates_type == ECoordinatesType::Local && (selection.is_wipe_tower() || selection.is_single_volume_or_modifier())) { +#else else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix(); @@ -749,12 +761,20 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const { case X: { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + ret = Geometry::rotation_transform(0.5 * PI * Vec3d::UnitY()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()); +#else ret = Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES break; } case Y: { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + ret = Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitY()); +#else ret = Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitY()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES break; } default: @@ -868,13 +888,21 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) // Apply new temporary rotations #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; - switch (wxGetApp().obj_manipul()->get_coordinates_type()) - { - default: - case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; } - case ECoordinatesType::Instance: { transformation_type = TransformationType::Instance_Relative_Joint; break; } - case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + if (m_parent.get_selection().is_wipe_tower()) + transformation_type = TransformationType::Instance_Relative_Joint; + else { +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + switch (wxGetApp().obj_manipul()->get_coordinates_type()) + { + default: + case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; } + case ECoordinatesType::Instance: { transformation_type = TransformationType::Instance_Relative_Joint; break; } + case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; } + } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else TransformationType transformation_type(TransformationType::World_Relative_Joint); #endif // ENABLE_WORLD_COORDINATE @@ -885,22 +913,27 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) } void GLGizmoRotate3D::data_changed() { - const Selection &selection = m_parent.get_selection(); - bool is_wipe_tower = selection.is_wipe_tower(); - if (is_wipe_tower) { - DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; - float wipe_tower_rotation_angle = - dynamic_cast( - config.option("wipe_tower_rotation_angle")) - ->value; + if (m_parent.get_selection().is_wipe_tower()) { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES + const DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + const float wipe_tower_rotation_angle = + dynamic_cast( + config.option("wipe_tower_rotation_angle"))->value; set_rotation(Vec3d(0., 0., (M_PI / 180.) * wipe_tower_rotation_angle)); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_gizmos[0].disable_grabber(); m_gizmos[1].disable_grabber(); - } else { + } + else { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES set_rotation(Vec3d::Zero()); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_gizmos[0].enable_grabber(); m_gizmos[1].enable_grabber(); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + set_rotation(Vec3d::Zero()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } bool GLGizmoRotate3D::on_init() diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 7bc3ce91a..c96e8efc7 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -35,29 +35,25 @@ static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5 namespace Slic3r { namespace GUI { -Selection::VolumeCache::TransformCache::TransformCache() - : position(Vec3d::Zero()) - , rotation(Vec3d::Zero()) - , scaling_factor(Vec3d::Ones()) - , mirror(Vec3d::Ones()) - , rotation_matrix(Transform3d::Identity()) - , scale_matrix(Transform3d::Identity()) - , mirror_matrix(Transform3d::Identity()) - , full_matrix(Transform3d::Identity()) -{ -} - -Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) - : position(transform.get_offset()) - , rotation(transform.get_rotation()) - , scaling_factor(transform.get_scaling_factor()) - , mirror(transform.get_mirror()) - , full_matrix(transform.get_matrix()) -{ - rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); - scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); - mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); -} + Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) + : position(transform.get_offset()) + , rotation(transform.get_rotation()) + , scaling_factor(transform.get_scaling_factor()) + , mirror(transform.get_mirror()) + , full_matrix(transform.get_matrix()) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + , transform(transform) + , rotation_matrix(transform.get_rotation_matrix()) + , scale_matrix(transform.get_scaling_factor_matrix()) + , mirror_matrix(transform.get_mirror_matrix()) +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES + rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); + scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); + mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES + } Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) : m_volume(volume_transform) @@ -772,27 +768,19 @@ void Selection::translate(const Vec3d& displacement, ECoordinatesType type) for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; - if (m_mode == Instance && !v.is_wipe_tower) { + if (m_mode == Instance && !is_wipe_tower()) { + assert(is_from_fully_selected_instance(i)); switch (type) { case ECoordinatesType::World: { - if (is_from_fully_selected_instance(i)) - v.set_instance_transformation(Geometry::assemble_transform(displacement) * volume_data.get_instance_full_matrix()); - else - assert(false); - + v.set_instance_transformation(Geometry::assemble_transform(displacement) * volume_data.get_instance_full_matrix()); break; } case ECoordinatesType::Local: { - if (is_from_fully_selected_instance(i)) { - const Vec3d world_displacemet = volume_data.get_instance_rotation_matrix() * displacement; - v.set_instance_transformation(Geometry::assemble_transform(world_displacemet) * volume_data.get_instance_full_matrix()); - } - else - assert(false); - + const Vec3d world_displacemet = volume_data.get_instance_rotation_matrix() * displacement; + v.set_instance_transformation(Geometry::assemble_transform(world_displacemet) * volume_data.get_instance_full_matrix()); break; } default: { assert(false); break; } @@ -913,6 +901,88 @@ void Selection::translate(const Vec3d& displacement, bool local) #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Rotate an object around one of the axes. Only one rotation component is expected to be changing. +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) +{ + if (!m_valid) + return; + + assert(transformation_type.relative() || (transformation_type.absolute() && transformation_type.local())); + + const Transform3d rotation_matrix = Geometry::rotation_transform(rotation); + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + const VolumeCache& volume_data = m_cache.volumes_data[i]; + if (m_mode == Instance && !is_wipe_tower()) { + assert(is_from_fully_selected_instance(i)); + const Geometry::Transformation& old_trafo = volume_data.get_instance_transform(); + Transform3d new_rotation_matrix = Transform3d::Identity(); + if (transformation_type.absolute()) + new_rotation_matrix = rotation_matrix; + else { + if (transformation_type.world()) + new_rotation_matrix = rotation_matrix * old_trafo.get_rotation_matrix(); + else if (transformation_type.local()) + new_rotation_matrix = old_trafo.get_rotation_matrix() * rotation_matrix; + else + assert(false); + } + + const Vec3d new_offset = transformation_type.independent() ? old_trafo.get_offset() : + m_cache.dragging_center + new_rotation_matrix * old_trafo.get_rotation_matrix().inverse() * + (old_trafo.get_offset() - m_cache.dragging_center); + v.set_instance_transformation(Geometry::assemble_transform(Geometry::assemble_transform(new_offset), new_rotation_matrix, + old_trafo.get_scaling_factor_matrix(), old_trafo.get_mirror_matrix())); + } + else { + const Geometry::Transformation& old_trafo = volume_data.get_volume_transform(); + Transform3d new_rotation_matrix = Transform3d::Identity(); + + if (transformation_type.absolute()) + new_rotation_matrix = rotation_matrix; + else { + if (transformation_type.world()) { + const Transform3d inst_rotation_matrix = volume_data.get_instance_transform().get_rotation_matrix(); + new_rotation_matrix = inst_rotation_matrix.inverse() * rotation_matrix * inst_rotation_matrix * old_trafo.get_rotation_matrix(); + } + else if (transformation_type.instance()) + new_rotation_matrix = rotation_matrix * old_trafo.get_rotation_matrix(); + else if (transformation_type.local()) + new_rotation_matrix = old_trafo.get_rotation_matrix() * rotation_matrix; + else + assert(false); + } + + const Vec3d new_offset = !is_wipe_tower() ? old_trafo.get_offset() : + m_cache.dragging_center + new_rotation_matrix * old_trafo.get_rotation_matrix().inverse() * + (old_trafo.get_offset() - m_cache.dragging_center); + v.set_volume_transformation(Geometry::assemble_transform(Geometry::assemble_transform(new_offset), new_rotation_matrix, + old_trafo.get_scaling_factor_matrix(), old_trafo.get_mirror_matrix())); + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) { + int rot_axis_max = 0; + rotation.cwiseAbs().maxCoeff(&rot_axis_max); + SyncRotationType synch; + if (transformation_type.world() && rot_axis_max == 2) + synch = SyncRotationType::NONE; + else if (transformation_type.local()) + synch = SyncRotationType::FULL; + else + synch = SyncRotationType::GENERAL; + synchronize_unselected_instances(synch); + } + else if (m_mode == Volume) + synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} +#else void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) { if (!m_valid) @@ -1073,6 +1143,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void Selection::flattening_rotate(const Vec3d& normal) { @@ -2975,7 +3046,13 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ if (v->object_idx() != object_idx || v->instance_idx() == instance_idx) continue; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Vec3d inst_rotation_i = m_cache.volumes_data[i].get_instance_transform().get_rotation(); + const Vec3d inst_rotation_j = m_cache.volumes_data[j].get_instance_transform().get_rotation(); + assert(is_rotation_xy_synchronized(inst_rotation_i, inst_rotation_j)); +#else assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES switch (sync_rotation_type) { case SyncRotationType::NONE: { // z only rotation -> synch instance z @@ -2987,17 +3064,29 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ } case SyncRotationType::GENERAL: { // generic rotation -> update instance z with the delta of the rotation. +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const double z_diff = Geometry::rotation_diff_z(inst_rotation_i, inst_rotation_j); +#else const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); break; } #if ENABLE_WORLD_COORDINATE case SyncRotationType::FULL: { // generic rotation -> update instance z with the delta of the rotation. +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, inst_rotation_j)); +#else const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, m_cache.volumes_data[j].get_instance_rotation())); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d& axis = angle_axis.axis(); const double z_diff = (std::abs(axis.x()) > EPSILON || std::abs(axis.y()) > EPSILON) ? - angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(rotation, m_cache.volumes_data[j].get_instance_rotation()); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(rotation, inst_rotation_j); +#else + angle_axis.angle()* axis.z() : Geometry::rotation_diff_z(rotation, m_cache.volumes_data[j].get_instance_rotation()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); break; diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 012fb0e4d..47a40877c 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -121,16 +121,19 @@ private: private: struct TransformCache { - Vec3d position; - Vec3d rotation; - Vec3d scaling_factor; - Vec3d mirror; - Transform3d rotation_matrix; - Transform3d scale_matrix; - Transform3d mirror_matrix; - Transform3d full_matrix; + Vec3d position{ Vec3d::Zero() }; + Vec3d rotation{ Vec3d::Zero() }; + Vec3d scaling_factor{ Vec3d::Ones() }; + Vec3d mirror{ Vec3d::Ones() }; + Transform3d rotation_matrix{ Transform3d::Identity() }; + Transform3d scale_matrix{ Transform3d::Identity() }; + Transform3d mirror_matrix{ Transform3d::Identity() }; + Transform3d full_matrix{ Transform3d::Identity() }; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Geometry::Transformation transform; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - TransformCache(); + TransformCache() = default; explicit TransformCache(const Geometry::Transformation& transform); }; @@ -142,13 +145,18 @@ private: VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform); const Vec3d& get_volume_position() const { return m_volume.position; } +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d& get_volume_rotation() const { return m_volume.rotation; } const Vec3d& get_volume_scaling_factor() const { return m_volume.scaling_factor; } const Vec3d& get_volume_mirror() const { return m_volume.mirror; } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES const Transform3d& get_volume_rotation_matrix() const { return m_volume.rotation_matrix; } const Transform3d& get_volume_scale_matrix() const { return m_volume.scale_matrix; } const Transform3d& get_volume_mirror_matrix() const { return m_volume.mirror_matrix; } const Transform3d& get_volume_full_matrix() const { return m_volume.full_matrix; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& get_volume_transform() const { return m_volume.transform; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d& get_instance_position() const { return m_instance.position; } const Vec3d& get_instance_rotation() const { return m_instance.rotation; } @@ -158,6 +166,9 @@ private: const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; } const Transform3d& get_instance_mirror_matrix() const { return m_instance.mirror_matrix; } const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& get_instance_transform() const { return m_instance.transform; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES }; public: From 0e3490620e2b835b7382d23b6d0022205e9293f4 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 4 May 2022 13:31:19 +0200 Subject: [PATCH 120/169] Added method const GLVolume* Selection::get_first_volume() const to simplify client code Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectList.cpp | 6 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 40 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 832 ++++++------- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 12 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 23 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 21 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 52 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 1134 +++++++++--------- src/slic3r/GUI/Plater.cpp | 6 +- src/slic3r/GUI/Selection.cpp | 49 +- src/slic3r/GUI/Selection.hpp | 1 + 13 files changed, 1094 insertions(+), 1092 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 216c2f472..ecf015b6d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1530,7 +1530,7 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo const BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx); // First (any) GLVolume of the selected instance. They all share the same instance matrix. - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); const Geometry::Transformation inst_transform = v->get_instance_transformation(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES const Transform3d inv_inst_transform = inst_transform.get_matrix_no_offset().inverse(); @@ -1654,7 +1654,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode ModelVolume *new_volume = model_object.add_volume(std::move(mesh), type); // First (any) GLVolume of the selected instance. They all share the same instance matrix. - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); // Transform the new modifier to be aligned with the print bed. const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); @@ -3282,7 +3282,7 @@ void ObjectList::update_selections() #else else if (selection.is_single_volume() || selection.is_any_modifier()) { #endif // ENABLE_WORLD_COORDINATE - const auto gl_vol = selection.get_volume(*selection.get_volume_idxs().begin()); + const auto gl_vol = selection.get_first_volume(); if (m_objects_model->GetVolumeIdByItem(m_objects_model->GetParent(item)) == gl_vol->volume_idx()) return; } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index c73f5eb4a..ddb878a7d 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -287,7 +287,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : #else if (selection.is_single_volume() || selection.is_single_modifier()) { #endif // ENABLE_WORLD_COORDINATE - GLVolume* volume = const_cast(selection.get_volume(*selection.get_volume_idxs().begin())); + GLVolume* volume = const_cast(selection.get_first_volume()); volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis)); } else if (selection.is_single_full_instance()) { @@ -351,7 +351,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : #if ENABLE_WORLD_COORDINATE if (selection.is_single_volume_or_modifier()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); const double min_z = get_volume_min_z(*volume); if (!is_world_coordinates()) { #if ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -371,7 +371,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : } #else if (selection.is_single_volume() || selection.is_single_modifier()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (get_volume_min_z(*volume) * Vec3d::UnitZ()); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); @@ -384,7 +384,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : #if ENABLE_WORLD_COORDINATE const double min_z = selection.get_scaled_instance_bounding_box().min.z(); if (!is_world_coordinates()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); #else @@ -432,7 +432,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : #else if (selection.is_single_volume() || selection.is_single_modifier()) #endif // ENABLE_WORLD_COORDINATE - const_cast(selection.get_volume(*selection.get_volume_idxs().begin()))->set_volume_rotation(Vec3d::Zero()); + const_cast(selection.get_first_volume())->set_volume_rotation(Vec3d::Zero()); else if (selection.is_single_full_instance()) { for (unsigned int idx : selection.get_volume_idxs()) { const_cast(selection.get_volume(idx))->set_instance_rotation(Vec3d::Zero()); @@ -611,7 +611,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) ObjectList* obj_list = wxGetApp().obj_list(); if (selection.is_single_full_instance()) { // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); #if !ENABLE_WORLD_COORDINATE m_new_position = volume->get_instance_offset(); #endif // !ENABLE_WORLD_COORDINATE @@ -674,7 +674,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE // the selection contains a single volume - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); #if ENABLE_WORLD_COORDINATE if (is_world_coordinates()) { const Geometry::Transformation trafo(volume->world_matrix()); @@ -872,18 +872,18 @@ void ObjectManipulation::update_reset_buttons_visibility() if ((m_coordinates_type == ECoordinatesType::World && selection.is_single_full_instance()) || (m_coordinates_type == ECoordinatesType::Instance && selection.is_single_volume_or_modifier())) { const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() : - get_volume_min_z(*selection.get_volume(*selection.get_volume_idxs().begin())); + get_volume_min_z(*selection.get_first_volume()); show_drop_to_bed = std::abs(min_z) > EPSILON; } if (m_coordinates_type == ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); #else if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); Vec3d rotation; Vec3d scale; double min_z = 0.0; @@ -947,7 +947,7 @@ void ObjectManipulation::update_mirror_buttons_visibility() #else if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); Vec3d mirror; if (selection.is_single_full_instance()) @@ -1141,7 +1141,7 @@ void ObjectManipulation::change_size_value(int axis, double value) #else if (selection.is_single_volume() || selection.is_single_modifier()) { #endif // ENABLE_WORLD_COORDINATE - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + 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()); const Vec3d local_change = local_size.cwiseQuotient(local_ref_size); @@ -1156,7 +1156,7 @@ void ObjectManipulation::change_size_value(int axis, double value) ref_size = m_world_coordinates ? #endif // ENABLE_WORLD_COORDINATE selection.get_unscaled_instance_bounding_box().size() : - wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); + wxGetApp().model().objects[selection.get_first_volume()->object_idx()]->raw_mesh_bounding_box().size(); #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED this->do_size(axis, size.cwiseQuotient(ref_size)); @@ -1196,10 +1196,10 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const if (!uniform_scale && is_world_coordinates()) { if (selection.is_single_full_instance()) - scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); + scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()).inverse(); scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); } } @@ -1236,10 +1236,10 @@ void ObjectManipulation::do_size(int axis, const Vec3d& scale) const if (!uniform_scale && is_world_coordinates()) { if (selection.is_single_full_instance()) - scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); + scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()).inverse(); scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); } } @@ -1309,7 +1309,7 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) #endif // ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); // Is the angle close to a multiple of 90 degrees? if (!Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { // Cannot apply scaling in the world coordinate system. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9a87d5a45..dfad3817c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -1,416 +1,416 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. -#include "GLGizmoCut.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" - -#include - -#include -#include -#include -#include - -#include - -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/Plater.hpp" -#include "slic3r/GUI/GUI_ObjectManipulation.hpp" -#include "libslic3r/AppConfig.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/TriangleMeshSlicer.hpp" - -namespace Slic3r { -namespace GUI { - -const double GLGizmoCut::Offset = 10.0; -const double GLGizmoCut::Margin = 20.0; -static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); - -GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) - : GLGizmoBase(parent, icon_filename, sprite_id) -{} - -std::string GLGizmoCut::get_tooltip() const -{ - double cut_z = m_cut_z; - if (wxGetApp().app_config->get("use_inches") == "1") - cut_z *= ObjectManipulation::mm_to_in; - - return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(cut_z, 2) : ""; -} - -bool GLGizmoCut::on_mouse(const wxMouseEvent &mouse_event) -{ - return use_grabbers(mouse_event); -} - -bool GLGizmoCut::on_init() -{ - m_grabbers.emplace_back(); - m_shortcut_key = WXK_CONTROL_C; - return true; -} - -std::string GLGizmoCut::on_get_name() const -{ - return _u8L("Cut"); -} - -void GLGizmoCut::on_set_state() -{ - // Reset m_cut_z on gizmo activation - if (m_state == On) - m_cut_z = bounding_box().center().z(); -} - -bool GLGizmoCut::on_is_activable() const -{ - const Selection& selection = m_parent.get_selection(); - return selection.is_single_full_instance() && !selection.is_wipe_tower(); -} - -void GLGizmoCut::on_start_dragging() -{ - if (m_hover_id == -1) - return; - - const BoundingBoxf3 box = bounding_box(); - m_max_z = box.max.z(); - m_start_z = m_cut_z; - m_drag_pos = m_grabbers[m_hover_id].center; - m_drag_center = box.center(); - m_drag_center.z() = m_cut_z; -} - -void GLGizmoCut::on_dragging(const UpdateData &data) -{ - assert(m_hover_id != -1); - set_cut_z(m_start_z + calc_projection(data.mouse_ray)); -} - -void GLGizmoCut::on_render() -{ - const BoundingBoxf3 box = bounding_box(); - Vec3d plane_center = box.center(); - plane_center.z() = m_cut_z; - m_max_z = box.max.z(); - set_cut_z(m_cut_z); - - update_contours(); - - const float min_x = box.min.x() - Margin; - const float max_x = box.max.x() + Margin; - const float min_y = box.min.y() - Margin; - const float max_y = box.max.y() + Margin; - glsafe(::glEnable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_CULL_FACE)); - glsafe(::glEnable(GL_BLEND)); - glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - -#if ENABLE_LEGACY_OPENGL_REMOVAL - GLShaderProgram* shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); - const Vec3d diff = plane_center - m_old_center; - // Z changed when move with cut plane - // X and Y changed when move with cutted object - bool is_changed = std::abs(diff.x()) > EPSILON || - std::abs(diff.y()) > EPSILON || - std::abs(diff.z()) > EPSILON; - m_old_center = plane_center; - - if (!m_plane.is_initialized() || is_changed) { - m_plane.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; - init_data.reserve_vertices(4); - init_data.reserve_indices(6); - - // vertices - init_data.add_vertex(Vec3f(min_x, min_y, plane_center.z())); - init_data.add_vertex(Vec3f(max_x, min_y, plane_center.z())); - init_data.add_vertex(Vec3f(max_x, max_y, plane_center.z())); - init_data.add_vertex(Vec3f(min_x, max_y, plane_center.z())); - - // indices - init_data.add_triangle(0, 1, 2); - init_data.add_triangle(2, 3, 0); - - m_plane.init_from(std::move(init_data)); - } - -#if ENABLE_GL_SHADERS_ATTRIBUTES - 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()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - - m_plane.render(); -#else - // Draw the cutting plane - ::glBegin(GL_QUADS); - ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); - ::glVertex3f(min_x, min_y, plane_center.z()); - ::glVertex3f(max_x, min_y, plane_center.z()); - ::glVertex3f(max_x, max_y, plane_center.z()); - ::glVertex3f(min_x, max_y, plane_center.z()); - glsafe(::glEnd()); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - - glsafe(::glEnable(GL_CULL_FACE)); - glsafe(::glDisable(GL_BLEND)); - - // Draw the grabber and the connecting line - m_grabbers[0].center = plane_center; - m_grabbers[0].center.z() = plane_center.z() + Offset; - - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); - - glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); -#if ENABLE_LEGACY_OPENGL_REMOVAL - if (!m_grabber_connection.is_initialized() || is_changed) { - m_grabber_connection.reset(); - - GLModel::Geometry init_data; - init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; - init_data.color = ColorRGBA::YELLOW(); - init_data.reserve_vertices(2); - init_data.reserve_indices(2); - - // vertices - init_data.add_vertex((Vec3f)plane_center.cast()); - init_data.add_vertex((Vec3f)m_grabbers[0].center.cast()); - - // indices - init_data.add_line(0, 1); - - m_grabber_connection.init_from(std::move(init_data)); - } - - m_grabber_connection.render(); - - shader->stop_using(); - } - - shader = wxGetApp().get_shader("gouraud_light"); -#else - glsafe(::glColor3f(1.0, 1.0, 0.0)); - ::glBegin(GL_LINES); - ::glVertex3dv(plane_center.data()); - ::glVertex3dv(m_grabbers[0].center.data()); - glsafe(::glEnd()); - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - if (shader != nullptr) { - shader->start_using(); - shader->set_uniform("emission_factor", 0.1f); - - m_grabbers[0].color = GRABBER_COLOR; - m_grabbers[0].render(m_hover_id == 0, float((box.size().x() + box.size().y() + box.size().z()) / 3.0)); - - shader->stop_using(); - } - -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader = wxGetApp().get_shader("flat"); - if (shader != nullptr) { - shader->start_using(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_GL_SHADERS_ATTRIBUTES - const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()* Geometry::assemble_transform(m_cut_contours.shift)); - shader->set_uniform("projection_matrix", camera.get_projection_matrix()); -#else - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glLineWidth(2.0f)); - m_cut_contours.contours.render(); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_LEGACY_OPENGL_REMOVAL - shader->stop_using(); - } -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - -void GLGizmoCut::on_render_for_picking() -{ - glsafe(::glDisable(GL_DEPTH_TEST)); - render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); -} - -void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) -{ - static float last_y = 0.0f; - static float last_h = 0.0f; - - m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - - const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; - - // adjust window position to avoid overlap the view toolbar - const float win_h = ImGui::GetWindowHeight(); - y = std::min(y, bottom_limit - win_h); - ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); - if (last_h != win_h || last_y != y) { - // ask canvas for another frame to render the window in the correct position - m_imgui->set_requires_extra_frame(); - if (last_h != win_h) - last_h = win_h; - if (last_y != y) - last_y = y; - } - - ImGui::AlignTextToFramePadding(); - m_imgui->text("Z"); - ImGui::SameLine(); - ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f); - - double cut_z = m_cut_z; - if (imperial_units) - cut_z *= ObjectManipulation::mm_to_in; - ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); - - ImGui::SameLine(); - m_imgui->text(imperial_units ? _L("in") : _L("mm")); - - m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0); - - ImGui::Separator(); - - m_imgui->checkbox(_L("Keep upper part"), m_keep_upper); - m_imgui->checkbox(_L("Keep lower part"), m_keep_lower); - m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); - - ImGui::Separator(); - - m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z <= m_cut_z); - const bool cut_clicked = m_imgui->button(_L("Perform cut")); - m_imgui->disabled_end(); - - m_imgui->end(); - - if (cut_clicked && (m_keep_upper || m_keep_lower)) - perform_cut(m_parent.get_selection()); -} - -void GLGizmoCut::set_cut_z(double cut_z) -{ - // Clamp the plane to the object's bounding box - m_cut_z = std::clamp(cut_z, 0.0, m_max_z); -} - -void GLGizmoCut::perform_cut(const Selection& selection) -{ - const int instance_idx = selection.get_instance_idx(); - const int object_idx = selection.get_object_idx(); - - wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); - - // m_cut_z is the distance from the bed. Subtract possible SLA elevation. - const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); - const double object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z(); - - if (0.0 < object_cut_z && object_cut_z < m_max_z) - wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, - only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | - only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | - only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower)); - else { - // the object is SLA-elevated and the plane is under it. - } -} - -double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const -{ - double projection = 0.0; - - const Vec3d starting_vec = m_drag_pos - m_drag_center; - const double len_starting_vec = starting_vec.norm(); - if (len_starting_vec != 0.0) { - const Vec3d mouse_dir = mouse_ray.unit_vector(); - // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position - // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form - // in our case plane normal and ray direction are the same (orthogonal view) - // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal - const Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; - // vector from the starting position to the found intersection - const Vec3d inters_vec = inters - m_drag_pos; - - // finds projection of the vector along the staring direction - projection = inters_vec.dot(starting_vec.normalized()); - } - return projection; -} - -BoundingBoxf3 GLGizmoCut::bounding_box() const -{ - BoundingBoxf3 ret; - const Selection& selection = m_parent.get_selection(); - const Selection::IndicesList& idxs = selection.get_volume_idxs(); - return selection.get_bounding_box(); - - for (unsigned int i : idxs) { - const GLVolume* volume = selection.get_volume(i); - if (!volume->is_modifier) - ret.merge(volume->transformed_convex_hull_bounding_box()); - } - return ret; -} - -void GLGizmoCut::update_contours() -{ - const Selection& selection = m_parent.get_selection(); - const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); - const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box(); - - const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()]; - const int instance_idx = selection.get_instance_idx(); - std::vector volumes_idxs = std::vector(model_object->volumes.size()); - for (size_t i = 0; i < model_object->volumes.size(); ++i) { - volumes_idxs[i] = model_object->volumes[i]->id(); - } - - if (0.0 < m_cut_z && m_cut_z < m_max_z) { - if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() || - m_cut_contours.instance_idx != instance_idx || m_cut_contours.volumes_idxs != volumes_idxs) { - m_cut_contours.cut_z = m_cut_z; - - if (m_cut_contours.object_id != model_object->id() || m_cut_contours.volumes_idxs != volumes_idxs) - m_cut_contours.mesh = model_object->raw_mesh(); - - m_cut_contours.position = box.center(); - m_cut_contours.shift = Vec3d::Zero(); - m_cut_contours.object_id = model_object->id(); - m_cut_contours.instance_idx = instance_idx; - m_cut_contours.volumes_idxs = volumes_idxs; - m_cut_contours.contours.reset(); - - MeshSlicingParams slicing_params; - slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix(); - slicing_params.trafo.pretranslate(Vec3d(0., 0., first_glvolume->get_sla_shift_z())); - - const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params); - if (!polys.empty()) { - m_cut_contours.contours.init_from(polys, static_cast(m_cut_z)); -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_cut_contours.contours.set_color(ColorRGBA::WHITE()); -#else - m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f }); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - } - } - else if (box.center() != m_cut_contours.position) { - m_cut_contours.shift = box.center() - m_cut_contours.position; - } - } - else - m_cut_contours.contours.reset(); -} - -} // namespace GUI -} // namespace Slic3r +// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "GLGizmoCut.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" + +#include + +#include +#include +#include +#include + +#include + +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GUI_ObjectManipulation.hpp" +#include "libslic3r/AppConfig.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" + +namespace Slic3r { +namespace GUI { + +const double GLGizmoCut::Offset = 10.0; +const double GLGizmoCut::Margin = 20.0; +static const ColorRGBA GRABBER_COLOR = ColorRGBA::ORANGE(); + +GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) + : GLGizmoBase(parent, icon_filename, sprite_id) +{} + +std::string GLGizmoCut::get_tooltip() const +{ + double cut_z = m_cut_z; + if (wxGetApp().app_config->get("use_inches") == "1") + cut_z *= ObjectManipulation::mm_to_in; + + return (m_hover_id == 0 || m_grabbers[0].dragging) ? "Z: " + format(cut_z, 2) : ""; +} + +bool GLGizmoCut::on_mouse(const wxMouseEvent &mouse_event) +{ + return use_grabbers(mouse_event); +} + +bool GLGizmoCut::on_init() +{ + m_grabbers.emplace_back(); + m_shortcut_key = WXK_CONTROL_C; + return true; +} + +std::string GLGizmoCut::on_get_name() const +{ + return _u8L("Cut"); +} + +void GLGizmoCut::on_set_state() +{ + // Reset m_cut_z on gizmo activation + if (m_state == On) + m_cut_z = bounding_box().center().z(); +} + +bool GLGizmoCut::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + return selection.is_single_full_instance() && !selection.is_wipe_tower(); +} + +void GLGizmoCut::on_start_dragging() +{ + if (m_hover_id == -1) + return; + + const BoundingBoxf3 box = bounding_box(); + m_max_z = box.max.z(); + m_start_z = m_cut_z; + m_drag_pos = m_grabbers[m_hover_id].center; + m_drag_center = box.center(); + m_drag_center.z() = m_cut_z; +} + +void GLGizmoCut::on_dragging(const UpdateData &data) +{ + assert(m_hover_id != -1); + set_cut_z(m_start_z + calc_projection(data.mouse_ray)); +} + +void GLGizmoCut::on_render() +{ + const BoundingBoxf3 box = bounding_box(); + Vec3d plane_center = box.center(); + plane_center.z() = m_cut_z; + m_max_z = box.max.z(); + set_cut_z(m_cut_z); + + update_contours(); + + const float min_x = box.min.x() - Margin; + const float max_x = box.max.x() + Margin; + const float min_y = box.min.y() - Margin; + const float max_y = box.max.y() + Margin; + glsafe(::glEnable(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_CULL_FACE)); + glsafe(::glEnable(GL_BLEND)); + glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); + +#if ENABLE_LEGACY_OPENGL_REMOVAL + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); + const Vec3d diff = plane_center - m_old_center; + // Z changed when move with cut plane + // X and Y changed when move with cutted object + bool is_changed = std::abs(diff.x()) > EPSILON || + std::abs(diff.y()) > EPSILON || + std::abs(diff.z()) > EPSILON; + m_old_center = plane_center; + + if (!m_plane.is_initialized() || is_changed) { + m_plane.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = { 0.8f, 0.8f, 0.8f, 0.5f }; + init_data.reserve_vertices(4); + init_data.reserve_indices(6); + + // vertices + init_data.add_vertex(Vec3f(min_x, min_y, plane_center.z())); + init_data.add_vertex(Vec3f(max_x, min_y, plane_center.z())); + init_data.add_vertex(Vec3f(max_x, max_y, plane_center.z())); + init_data.add_vertex(Vec3f(min_x, max_y, plane_center.z())); + + // indices + init_data.add_triangle(0, 1, 2); + init_data.add_triangle(2, 3, 0); + + m_plane.init_from(std::move(init_data)); + } + +#if ENABLE_GL_SHADERS_ATTRIBUTES + 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()); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + + m_plane.render(); +#else + // Draw the cutting plane + ::glBegin(GL_QUADS); + ::glColor4f(0.8f, 0.8f, 0.8f, 0.5f); + ::glVertex3f(min_x, min_y, plane_center.z()); + ::glVertex3f(max_x, min_y, plane_center.z()); + ::glVertex3f(max_x, max_y, plane_center.z()); + ::glVertex3f(min_x, max_y, plane_center.z()); + glsafe(::glEnd()); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + + glsafe(::glEnable(GL_CULL_FACE)); + glsafe(::glDisable(GL_BLEND)); + + // Draw the grabber and the connecting line + m_grabbers[0].center = plane_center; + m_grabbers[0].center.z() = plane_center.z() + Offset; + + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + + glsafe(::glLineWidth(m_hover_id != -1 ? 2.0f : 1.5f)); +#if ENABLE_LEGACY_OPENGL_REMOVAL + if (!m_grabber_connection.is_initialized() || is_changed) { + m_grabber_connection.reset(); + + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::YELLOW(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex((Vec3f)plane_center.cast()); + init_data.add_vertex((Vec3f)m_grabbers[0].center.cast()); + + // indices + init_data.add_line(0, 1); + + m_grabber_connection.init_from(std::move(init_data)); + } + + m_grabber_connection.render(); + + shader->stop_using(); + } + + shader = wxGetApp().get_shader("gouraud_light"); +#else + glsafe(::glColor3f(1.0, 1.0, 0.0)); + ::glBegin(GL_LINES); + ::glVertex3dv(plane_center.data()); + ::glVertex3dv(m_grabbers[0].center.data()); + glsafe(::glEnd()); + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); + + m_grabbers[0].color = GRABBER_COLOR; + m_grabbers[0].render(m_hover_id == 0, float((box.size().x() + box.size().y() + box.size().z()) / 3.0)); + + shader->stop_using(); + } + +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader = wxGetApp().get_shader("flat"); + if (shader != nullptr) { + shader->start_using(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_GL_SHADERS_ATTRIBUTES + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()* Geometry::assemble_transform(m_cut_contours.shift)); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#else + glsafe(::glPushMatrix()); + glsafe(::glTranslated(m_cut_contours.shift.x(), m_cut_contours.shift.y(), m_cut_contours.shift.z())); +#endif // ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glLineWidth(2.0f)); + m_cut_contours.contours.render(); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_LEGACY_OPENGL_REMOVAL + shader->stop_using(); + } +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + +void GLGizmoCut::on_render_for_picking() +{ + glsafe(::glDisable(GL_DEPTH_TEST)); + render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); +} + +void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) +{ + static float last_y = 0.0f; + static float last_h = 0.0f; + + m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + + // adjust window position to avoid overlap the view toolbar + const float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + if (last_h != win_h || last_y != y) { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) + last_h = win_h; + if (last_y != y) + last_y = y; + } + + ImGui::AlignTextToFramePadding(); + m_imgui->text("Z"); + ImGui::SameLine(); + ImGui::PushItemWidth(m_imgui->get_style_scaling() * 150.0f); + + double cut_z = m_cut_z; + if (imperial_units) + cut_z *= ObjectManipulation::mm_to_in; + ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal); + + ImGui::SameLine(); + m_imgui->text(imperial_units ? _L("in") : _L("mm")); + + m_cut_z = cut_z * (imperial_units ? ObjectManipulation::in_to_mm : 1.0); + + ImGui::Separator(); + + m_imgui->checkbox(_L("Keep upper part"), m_keep_upper); + m_imgui->checkbox(_L("Keep lower part"), m_keep_lower); + m_imgui->checkbox(_L("Rotate lower part upwards"), m_rotate_lower); + + ImGui::Separator(); + + m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z <= m_cut_z); + const bool cut_clicked = m_imgui->button(_L("Perform cut")); + m_imgui->disabled_end(); + + m_imgui->end(); + + if (cut_clicked && (m_keep_upper || m_keep_lower)) + perform_cut(m_parent.get_selection()); +} + +void GLGizmoCut::set_cut_z(double cut_z) +{ + // Clamp the plane to the object's bounding box + m_cut_z = std::clamp(cut_z, 0.0, m_max_z); +} + +void GLGizmoCut::perform_cut(const Selection& selection) +{ + const int instance_idx = selection.get_instance_idx(); + const int object_idx = selection.get_object_idx(); + + wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); + + // m_cut_z is the distance from the bed. Subtract possible SLA elevation. + const GLVolume* first_glvolume = selection.get_first_volume(); + const double object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z(); + + if (0.0 < object_cut_z && object_cut_z < m_max_z) + wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, + only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | + only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | + only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower)); + else { + // the object is SLA-elevated and the plane is under it. + } +} + +double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const +{ + double projection = 0.0; + + const Vec3d starting_vec = m_drag_pos - m_drag_center; + const double len_starting_vec = starting_vec.norm(); + if (len_starting_vec != 0.0) { + const Vec3d mouse_dir = mouse_ray.unit_vector(); + // finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position + // use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form + // in our case plane normal and ray direction are the same (orthogonal view) + // when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal + const Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir; + // vector from the starting position to the found intersection + const Vec3d inters_vec = inters - m_drag_pos; + + // finds projection of the vector along the staring direction + projection = inters_vec.dot(starting_vec.normalized()); + } + return projection; +} + +BoundingBoxf3 GLGizmoCut::bounding_box() const +{ + BoundingBoxf3 ret; + const Selection& selection = m_parent.get_selection(); + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + return selection.get_bounding_box(); + + for (unsigned int i : idxs) { + const GLVolume* volume = selection.get_volume(i); + if (!volume->is_modifier) + ret.merge(volume->transformed_convex_hull_bounding_box()); + } + return ret; +} + +void GLGizmoCut::update_contours() +{ + const Selection& selection = m_parent.get_selection(); + const GLVolume* first_glvolume = selection.get_first_volume(); + const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box(); + + const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()]; + const int instance_idx = selection.get_instance_idx(); + std::vector volumes_idxs = std::vector(model_object->volumes.size()); + for (size_t i = 0; i < model_object->volumes.size(); ++i) { + volumes_idxs[i] = model_object->volumes[i]->id(); + } + + if (0.0 < m_cut_z && m_cut_z < m_max_z) { + if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() || + m_cut_contours.instance_idx != instance_idx || m_cut_contours.volumes_idxs != volumes_idxs) { + m_cut_contours.cut_z = m_cut_z; + + if (m_cut_contours.object_id != model_object->id() || m_cut_contours.volumes_idxs != volumes_idxs) + m_cut_contours.mesh = model_object->raw_mesh(); + + m_cut_contours.position = box.center(); + m_cut_contours.shift = Vec3d::Zero(); + m_cut_contours.object_id = model_object->id(); + m_cut_contours.instance_idx = instance_idx; + m_cut_contours.volumes_idxs = volumes_idxs; + m_cut_contours.contours.reset(); + + MeshSlicingParams slicing_params; + slicing_params.trafo = first_glvolume->get_instance_transformation().get_matrix(); + slicing_params.trafo.pretranslate(Vec3d(0., 0., first_glvolume->get_sla_shift_z())); + + const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params); + if (!polys.empty()) { + m_cut_contours.contours.init_from(polys, static_cast(m_cut_z)); +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_cut_contours.contours.set_color(ColorRGBA::WHITE()); +#else + m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f }); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + } + } + else if (box.center() != m_cut_contours.position) { + m_cut_contours.shift = box.center() - m_cut_contours.position; + } + } + else + m_cut_contours.contours.reset(); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index b882570fc..9bac7f293 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -118,17 +118,17 @@ void GLGizmoFlatten::on_render() glsafe(::glEnable(GL_BLEND)); if (selection.is_single_full_instance()) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + const Transform3d& m = selection.get_first_volume()->get_instance_transformation().get_matrix(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); const Transform3d view_model_matrix = camera.get_view_matrix() * - Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; + Geometry::assemble_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * m; shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #else glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); + glsafe(::glTranslatef(0.f, 0.f, selection.get_first_volume()->get_sla_shift_z())); glsafe(::glMultMatrixd(m.data())); #endif // ENABLE_GL_SHADERS_ATTRIBUTES if (this->is_plane_update_necessary()) @@ -172,17 +172,17 @@ void GLGizmoFlatten::on_render_for_picking() glsafe(::glDisable(GL_BLEND)); if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); + const Transform3d& m = selection.get_first_volume()->get_instance_transformation().get_matrix(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); const Transform3d view_model_matrix = camera.get_view_matrix() * - Geometry::assemble_transform(selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z() * Vec3d::UnitZ()) * m; + Geometry::assemble_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * m; shader->set_uniform("view_model_matrix", view_model_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #else glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); + glsafe(::glTranslatef(0.f, 0.f, selection.get_first_volume()->get_sla_shift_z())); glsafe(::glMultMatrixd(m.data())); #endif // ENABLE_GL_SHADERS_ATTRIBUTES if (this->is_plane_update_necessary()) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index a60da3591..15030a0fa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -94,7 +94,7 @@ void GLGizmoHollow::on_render_for_picking() { const Selection& selection = m_parent.get_selection(); //#if ENABLE_RENDER_PICKING_PASS -// m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); +// m_z_shift = selection.get_first_volume()->get_sla_shift_z(); //#endif glsafe(::glEnable(GL_DEPTH_TEST)); @@ -117,7 +117,7 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); #endif // ENABLE_LEGACY_OPENGL_REMOVAL - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* vol = selection.get_first_volume(); Geometry::Transformation trafo = vol->get_instance_transformation() * vol->get_volume_transformation(); #if ENABLE_GL_SHADERS_ATTRIBUTES @@ -242,7 +242,7 @@ bool GLGizmoHollow::unproject_on_mesh(const Vec2d& mouse_pos, std::pairget_camera(); const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 42fe796a4..afb0b9140 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -97,7 +97,7 @@ void GLGizmoMove3D::on_start_dragging() if (coordinates_type == ECoordinatesType::World) m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center; #else @@ -105,7 +105,7 @@ void GLGizmoMove3D::on_start_dragging() #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center; #else @@ -315,7 +315,7 @@ void GLGizmoMove3D::on_render() #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix()* base_matrix); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -359,7 +359,7 @@ void GLGizmoMove3D::on_render() #endif // ENABLE_GL_SHADERS_ATTRIBUTES #else render_grabber_extension((Axis)m_hover_id, box, false); -#endif // ENABLE_WORLD_COORDINATE +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_GIZMO_GRABBER_REFACTOR } @@ -508,7 +508,7 @@ Transform3d GLGizmoMove3D::local_transform(const Selection& selection) const { Transform3d ret = Geometry::assemble_transform(m_center); if (!wxGetApp().obj_manipul()->is_world_coordinates()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); #else @@ -530,7 +530,7 @@ void GLGizmoMove3D::transform_to_local(const Selection& selection) const glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); if (!wxGetApp().obj_manipul()->is_world_coordinates()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); @@ -548,7 +548,7 @@ void GLGizmoMove3D::calc_selection_box_and_center() m_center = m_bounding_box.center(); } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES 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()); @@ -565,11 +565,12 @@ void GLGizmoMove3D::calc_selection_box_and_center() m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } #if ENABLE_TRANSFORMATIONS_BY_MATRICES - m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix()); - m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor() * m_bounding_box.center(); + 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(); #else - m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); - m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); + m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); + m_center = selection.get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 51d089873..a89173460 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -301,7 +301,7 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_center = m_bounding_box.center(); } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES 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()); @@ -318,11 +318,12 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } #if ENABLE_TRANSFORMATIONS_BY_MATRICES - m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix()); - m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor() * m_bounding_box.center(); + 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(); #else - m_bounding_box = m_bounding_box.transformed(selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); - m_center = selection.get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); + m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); + m_center = selection.get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } @@ -339,7 +340,7 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) #else else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix(); #else @@ -347,7 +348,7 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_orient_matrix = v.get_instance_transformation().get_rotation_matrix(); #else @@ -789,7 +790,7 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const return Geometry::assemble_transform(m_center) * m_orient_matrix * ret; #else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) - ret = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true) * ret; + ret = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true) * ret; return Geometry::assemble_transform(m_center) * ret; #endif // ENABLE_WORLD_COORDINATE @@ -803,7 +804,7 @@ void GLGizmoRotate::transform_to_local(const Selection& selection) const glsafe(::glMultMatrixd(m_orient_matrix.data())); #else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) { - const Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + const Transform3d orient_matrix = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } #endif // ENABLE_WORLD_COORDINATE @@ -864,7 +865,7 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons m = m * m_orient_matrix.inverse(); #else if (selection.is_single_volume() || selection.is_single_modifier() || selection.requires_local_axes()) - m = m * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true).inverse(); + m = m * selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true).inverse(); #endif // ENABLE_WORLD_COORDINATE m.translate(-m_center); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 05df5f193..675ed0b3f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -45,13 +45,13 @@ std::string GLGizmoScale3D::get_tooltip() const Vec3d scale = 100.0 * Vec3d::Ones(); if (selection.is_single_full_instance()) - scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor(); + scale = 100.0 * selection.get_first_volume()->get_instance_scaling_factor(); #if ENABLE_WORLD_COORDINATE else if (selection.is_single_volume_or_modifier()) #else else if (selection.is_single_modifier() || selection.is_single_volume()) #endif // ENABLE_WORLD_COORDINATE - scale = 100.0 * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor(); + scale = 100.0 * selection.get_first_volume()->get_volume_scaling_factor(); if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging) return "X: " + format(scale.x(), 4) + "%"; @@ -99,16 +99,14 @@ void GLGizmoScale3D::data_changed() if (enable_scale_xyz) { // all volumes in the selection belongs to the same instance, any of // them contains the needed data, so we take the first - const GLVolume *volume = selection.get_volume(*selection.get_volume_idxs().begin()); - if (selection.is_single_full_instance()) { + const GLVolume* volume = selection.get_first_volume(); + if (selection.is_single_full_instance()) set_scale(volume->get_instance_scaling_factor()); - } else if (selection.is_single_volume() || - selection.is_single_modifier()) { + else if (selection.is_single_volume() || selection.is_single_modifier()) set_scale(volume->get_volume_scaling_factor()); - } - } else { - set_scale(Vec3d::Ones()); } + else + set_scale(Vec3d::Ones()); } bool GLGizmoScale3D::on_init() @@ -220,14 +218,14 @@ void GLGizmoScale3D::on_render() #if ENABLE_WORLD_COORDINATE #if ENABLE_TRANSFORMATIONS_BY_MATRICES - m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_scaling_factor_matrix()); + m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix()); #else - m_bounding_box = m_bounding_box.transformed(selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(true, true, false, true)); + m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #endif // ENABLE_WORLD_COORDINATE // gets transform from first selected volume - const GLVolume& v = *selection.get_volume(*idxs.begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_WORLD_COORDINATE #if ENABLE_TRANSFORMATIONS_BY_MATRICES const Transform3d inst_trafo = v.get_instance_transformation().get_matrix_no_scaling_factor(); @@ -235,7 +233,7 @@ void GLGizmoScale3D::on_render() m_center = inst_trafo * m_bounding_box.center(); #else m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center()); - m_center = selection.get_volume(*idxs.begin())->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); + m_center = selection.get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_instance_center = v.get_instance_offset(); } @@ -251,7 +249,7 @@ void GLGizmoScale3D::on_render() } else if (selection.is_single_modifier() || selection.is_single_volume()) { #endif // ENABLE_WORLD_COORDINATE - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_WORLD_COORDINATE #if ENABLE_TRANSFORMATIONS_BY_MATRICES m_bounding_box.merge(v.transformed_convex_hull_bounding_box( @@ -267,7 +265,7 @@ void GLGizmoScale3D::on_render() m_instance_center = m_center; } else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES 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())); @@ -285,7 +283,7 @@ void GLGizmoScale3D::on_render() m_bounding_box = selection.get_bounding_box(); m_grabbers_transform = Geometry::assemble_transform(m_bounding_box.center()); m_center = m_bounding_box.center(); - m_instance_center = selection.is_single_full_instance() ? selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_offset() : m_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(); @@ -668,13 +666,13 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (coordinates_type == ECoordinatesType::World) { if (selection.is_single_full_instance()) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()); curr_scale = (m * curr_scale).cwiseAbs(); starting_scale = (m * starting_scale).cwiseAbs(); } else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()); + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()); const Transform3d m = mi * mv; curr_scale = (m * curr_scale).cwiseAbs(); starting_scale = (m * starting_scale).cwiseAbs(); @@ -685,10 +683,10 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) if (coordinates_type == ECoordinatesType::World) { if (selection.is_single_full_instance()) - m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); + m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_rotation()).inverse(); + const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); + const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()).inverse(); m_scale = (mv * mi * curr_scale).cwiseAbs(); } else @@ -714,7 +712,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) #if ENABLE_WORLD_COORDINATE Vec3d center_offset = m_starting.instance_center - m_starting.center; if (selection.is_single_full_instance() && coordinates_type != ECoordinatesType::World) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); center_offset = m * center_offset; } @@ -763,7 +761,7 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) Vec3d center_offset = m_starting.instance_center - m_starting.center; if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_rotation()).inverse(); + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); center_offset = m * center_offset; } @@ -816,7 +814,7 @@ Transform3d GLGizmoScale3D::local_transform(const Selection& selection) const { Transform3d ret = Geometry::assemble_transform(m_center); if (!wxGetApp().obj_manipul()->is_world_coordinates()) { - const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume& v = *selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); #else @@ -838,9 +836,9 @@ void GLGizmoScale3D::transform_to_local(const Selection& selection) const glsafe(::glTranslated(m_center.x(), m_center.y(), m_center.z())); if (!wxGetApp().obj_manipul()->is_world_coordinates()) { - Transform3d orient_matrix = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(true, false, true, true); + Transform3d orient_matrix = selection.get_first_volume()->get_instance_transformation().get_matrix(true, false, true, true); if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) - orient_matrix = orient_matrix * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_transformation().get_matrix(true, false, true, true); + orient_matrix = orient_matrix * selection.get_first_volume()->get_volume_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index f72ce3206..25b918e4b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -146,7 +146,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) }); #endif // ENABLE_LEGACY_OPENGL_REMOVAL - const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* vol = selection.get_first_volume(); Geometry::Transformation transformation(vol->get_instance_transformation().get_matrix() * vol->get_volume_transformation().get_matrix()); #if ENABLE_TRANSFORMATIONS_BY_MATRICES const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); @@ -361,7 +361,7 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pairget_camera(); const Selection& selection = m_parent.get_selection(); - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); Geometry::Transformation trafo = volume->get_instance_transformation() * volume->get_volume_transformation(); trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_c->selection_info()->get_sla_shift())); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index f1156f937..a77c1dd30 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -1,567 +1,567 @@ -#include "GLGizmosCommon.hpp" - -#include - -#include "slic3r/GUI/GLCanvas3D.hpp" -#include "libslic3r/SLAPrint.hpp" -#include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/Camera.hpp" -#include "slic3r/GUI/Plater.hpp" - -#include "libslic3r/PresetBundle.hpp" - -#include - -namespace Slic3r { -namespace GUI { - -using namespace CommonGizmosDataObjects; - -CommonGizmosDataPool::CommonGizmosDataPool(GLCanvas3D* canvas) - : m_canvas(canvas) -{ - using c = CommonGizmosDataID; - m_data[c::SelectionInfo].reset( new SelectionInfo(this)); - m_data[c::InstancesHider].reset( new InstancesHider(this)); - m_data[c::HollowedMesh].reset( new HollowedMesh(this)); - m_data[c::Raycaster].reset( new Raycaster(this)); - m_data[c::ObjectClipper].reset( new ObjectClipper(this)); - m_data[c::SupportsClipper].reset( new SupportsClipper(this)); - -} - -void CommonGizmosDataPool::update(CommonGizmosDataID required) -{ - assert(check_dependencies(required)); - for (auto& [id, data] : m_data) { - if (int(required) & int(CommonGizmosDataID(id))) - data->update(); - else - if (data->is_valid()) - data->release(); - - } -} - - -SelectionInfo* CommonGizmosDataPool::selection_info() const -{ - SelectionInfo* sel_info = dynamic_cast(m_data.at(CommonGizmosDataID::SelectionInfo).get()); - assert(sel_info); - return sel_info->is_valid() ? sel_info : nullptr; -} - - -InstancesHider* CommonGizmosDataPool::instances_hider() const -{ - InstancesHider* inst_hider = dynamic_cast(m_data.at(CommonGizmosDataID::InstancesHider).get()); - assert(inst_hider); - return inst_hider->is_valid() ? inst_hider : nullptr; -} - -HollowedMesh* CommonGizmosDataPool::hollowed_mesh() const -{ - HollowedMesh* hol_mesh = dynamic_cast(m_data.at(CommonGizmosDataID::HollowedMesh).get()); - assert(hol_mesh); - return hol_mesh->is_valid() ? hol_mesh : nullptr; -} - -Raycaster* CommonGizmosDataPool::raycaster() const -{ - Raycaster* rc = dynamic_cast(m_data.at(CommonGizmosDataID::Raycaster).get()); - assert(rc); - return rc->is_valid() ? rc : nullptr; -} - -ObjectClipper* CommonGizmosDataPool::object_clipper() const -{ - ObjectClipper* oc = dynamic_cast(m_data.at(CommonGizmosDataID::ObjectClipper).get()); - // ObjectClipper is used from outside the gizmos to report current clipping plane. - // This function can be called when oc is nullptr. - return (oc && oc->is_valid()) ? oc : nullptr; -} - -SupportsClipper* CommonGizmosDataPool::supports_clipper() const -{ - SupportsClipper* sc = dynamic_cast(m_data.at(CommonGizmosDataID::SupportsClipper).get()); - assert(sc); - return sc->is_valid() ? sc : nullptr; -} - -#ifndef NDEBUG -// Check the required resources one by one and return true if all -// dependencies are met. -bool CommonGizmosDataPool::check_dependencies(CommonGizmosDataID required) const -{ - // This should iterate over currently required data. Each of them should - // be asked about its dependencies and it must check that all dependencies - // are also in required and before the current one. - for (auto& [id, data] : m_data) { - // in case we don't use this, the deps are irrelevant - if (! (int(required) & int(CommonGizmosDataID(id)))) - continue; - - - CommonGizmosDataID deps = data->get_dependencies(); - assert(int(deps) == (int(deps) & int(required))); - } - - - return true; -} -#endif // NDEBUG - - - - -void SelectionInfo::on_update() -{ - const Selection& selection = get_pool()->get_canvas()->get_selection(); - if (selection.is_single_full_instance()) { - m_model_object = selection.get_model()->objects[selection.get_object_idx()]; - m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); - } - else - m_model_object = nullptr; -} - -void SelectionInfo::on_release() -{ - m_model_object = nullptr; -} - -int SelectionInfo::get_active_instance() const -{ - const Selection& selection = get_pool()->get_canvas()->get_selection(); - return selection.get_instance_idx(); -} - - - - - -void InstancesHider::on_update() -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - int active_inst = get_pool()->selection_info()->get_active_instance(); - GLCanvas3D* canvas = get_pool()->get_canvas(); - - if (mo && active_inst != -1) { - canvas->toggle_model_objects_visibility(false); - canvas->toggle_model_objects_visibility(true, mo, active_inst); - canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst); - canvas->set_use_clipping_planes(true); - // Some objects may be sinking, do not show whatever is below the bed. - canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); - canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits::max())); - - - std::vector meshes; - for (const ModelVolume* mv : mo->volumes) - meshes.push_back(&mv->mesh()); - - if (meshes != m_old_meshes) { - m_clippers.clear(); - for (const TriangleMesh* mesh : meshes) { - m_clippers.emplace_back(new MeshClipper); - m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); - m_clippers.back()->set_mesh(*mesh); - } - m_old_meshes = meshes; - } - } - else - canvas->toggle_model_objects_visibility(true); -} - -void InstancesHider::on_release() -{ - get_pool()->get_canvas()->toggle_model_objects_visibility(true); - get_pool()->get_canvas()->set_use_clipping_planes(false); - m_old_meshes.clear(); - m_clippers.clear(); -} - -void InstancesHider::show_supports(bool show) { - if (m_show_supports != show) { - m_show_supports = show; - on_update(); - } -} - -void InstancesHider::render_cut() const -{ - const SelectionInfo* sel_info = get_pool()->selection_info(); - const ModelObject* mo = sel_info->model_object(); - Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); - - size_t clipper_id = 0; - for (const ModelVolume* mv : mo->volumes) { - Geometry::Transformation vol_trafo = mv->get_transformation(); - Geometry::Transformation trafo = inst_trafo * vol_trafo; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - auto& clipper = m_clippers[clipper_id]; - clipper->set_transformation(trafo); - const ObjectClipper* obj_clipper = get_pool()->object_clipper(); - if (obj_clipper->is_valid() && obj_clipper->get_clipping_plane() - && obj_clipper->get_position() != 0.) { - ClippingPlane clp = *get_pool()->object_clipper()->get_clipping_plane(); - clp.set_normal(-clp.get_normal()); - clipper->set_limiting_plane(clp); - } - else - clipper->set_limiting_plane(ClippingPlane::ClipsNothing()); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPushMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#if !ENABLE_LEGACY_OPENGL_REMOVAL - if (mv->is_model_part()) - glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); - else { - const ColorRGBA color = color_from_model_volume(*mv); - glsafe(::glColor4fv(color.data())); - } -#endif // !ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glPushAttrib(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_DEPTH_TEST)); -#if ENABLE_LEGACY_OPENGL_REMOVAL - clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv)); -#else - clipper->render_cut(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL - glsafe(::glPopAttrib()); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - ++clipper_id; - } -} - - - -void HollowedMesh::on_update() -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; - if (! mo || ! is_sla) - return; - - const GLCanvas3D* canvas = get_pool()->get_canvas(); - const PrintObjects& print_objects = canvas->sla_print()->objects(); - const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size())) - ? print_objects[m_print_object_idx] - : nullptr; - - // Find the respective SLAPrintObject. - if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { - m_print_objects_count = print_objects.size(); - m_print_object_idx = -1; - for (const SLAPrintObject* po : print_objects) { - ++m_print_object_idx; - if (po->model_object()->id() == mo->id()) { - print_object = po; - break; - } - } - } - - // If there is a valid SLAPrintObject, check state of Hollowing step. - if (print_object) { - if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { - size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; - if (timestamp > m_old_hollowing_timestamp) { - const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice(); - if (! backend_mesh.empty()) { - m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); - Transform3d trafo_inv = (canvas->sla_print()->sla_trafo(*mo) * print_object->model_object()->volumes.front()->get_transformation().get_matrix()).inverse(); - m_hollowed_mesh_transformed->transform(trafo_inv); - m_drainholes = print_object->model_object()->sla_drain_holes; - m_old_hollowing_timestamp = timestamp; - - indexed_triangle_set interior = print_object->hollowed_interior_mesh(); - its_flip_triangles(interior); - m_hollowed_interior_transformed = std::make_unique(std::move(interior)); - m_hollowed_interior_transformed->transform(trafo_inv); - } - else { - m_hollowed_mesh_transformed.reset(nullptr); - } - } - } - else - m_hollowed_mesh_transformed.reset(nullptr); - } -} - - -void HollowedMesh::on_release() -{ - m_hollowed_mesh_transformed.reset(); - m_old_hollowing_timestamp = 0; - m_print_object_idx = -1; -} - - -const TriangleMesh* HollowedMesh::get_hollowed_mesh() const -{ - return m_hollowed_mesh_transformed.get(); -} - -const TriangleMesh* HollowedMesh::get_hollowed_interior() const -{ - return m_hollowed_interior_transformed.get(); -} - - - - -void Raycaster::on_update() -{ - wxBusyCursor wait; - const ModelObject* mo = get_pool()->selection_info()->model_object(); - - if (! mo) - return; - - std::vector meshes; - const std::vector& mvs = mo->volumes; - if (mvs.size() == 1) { - assert(mvs.front()->is_model_part()); - const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh(); - if (hollowed_mesh_tracker && hollowed_mesh_tracker->get_hollowed_mesh()) - meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh()); - } - if (meshes.empty()) { - for (const ModelVolume* mv : mvs) { - if (mv->is_model_part()) - meshes.push_back(&mv->mesh()); - } - } - - if (meshes != m_old_meshes) { - m_raycasters.clear(); - for (const TriangleMesh* mesh : meshes) - m_raycasters.emplace_back(new MeshRaycaster(*mesh)); - m_old_meshes = meshes; - } -} - -void Raycaster::on_release() -{ - m_raycasters.clear(); - m_old_meshes.clear(); -} - -std::vector Raycaster::raycasters() const -{ - std::vector mrcs; - for (const auto& raycaster_unique_ptr : m_raycasters) - mrcs.push_back(raycaster_unique_ptr.get()); - return mrcs; -} - - - - - -void ObjectClipper::on_update() -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - if (! mo) - return; - - // which mesh should be cut? - std::vector meshes; - bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh(); - if (has_hollowed) - meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh()); - - if (meshes.empty()) - for (const ModelVolume* mv : mo->volumes) - meshes.push_back(&mv->mesh()); - - if (meshes != m_old_meshes) { - m_clippers.clear(); - for (const TriangleMesh* mesh : meshes) { - m_clippers.emplace_back(new MeshClipper); - m_clippers.back()->set_mesh(*mesh); - } - m_old_meshes = meshes; - - if (has_hollowed) - m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); - - m_active_inst_bb_radius = - mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); - } -} - - -void ObjectClipper::on_release() -{ - m_clippers.clear(); - m_old_meshes.clear(); - m_clp.reset(); - m_clp_ratio = 0.; - -} - -void ObjectClipper::render_cut() const -{ - if (m_clp_ratio == 0.) - return; - const SelectionInfo* sel_info = get_pool()->selection_info(); - const ModelObject* mo = sel_info->model_object(); - const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); - - size_t clipper_id = 0; - for (const ModelVolume* mv : mo->volumes) { - const Geometry::Transformation vol_trafo = mv->get_transformation(); - Geometry::Transformation trafo = inst_trafo * vol_trafo; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - auto& clipper = m_clippers[clipper_id]; - clipper->set_plane(*m_clp); - clipper->set_transformation(trafo); - clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPushMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_LEGACY_OPENGL_REMOVAL - clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }); -#else - glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); - clipper->render_cut(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES - - ++clipper_id; - } -} - - -void ObjectClipper::set_position(double pos, bool keep_normal) -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - int active_inst = get_pool()->selection_info()->get_active_instance(); - double z_shift = get_pool()->selection_info()->get_sla_shift(); - - Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward(); - const Vec3d& center = mo->instances[active_inst]->get_offset() + Vec3d(0., 0., z_shift); - float dist = normal.dot(center); - - if (pos < 0.) - pos = m_clp_ratio; - - m_clp_ratio = pos; - m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2*m_active_inst_bb_radius))); - get_pool()->get_canvas()->set_as_dirty(); -} - - - -void SupportsClipper::on_update() -{ - const ModelObject* mo = get_pool()->selection_info()->model_object(); - bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; - if (! mo || ! is_sla) - return; - - const GLCanvas3D* canvas = get_pool()->get_canvas(); - const PrintObjects& print_objects = canvas->sla_print()->objects(); - const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size())) - ? print_objects[m_print_object_idx] - : nullptr; - - // Find the respective SLAPrintObject. - if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { - m_print_objects_count = print_objects.size(); - m_print_object_idx = -1; - for (const SLAPrintObject* po : print_objects) { - ++m_print_object_idx; - if (po->model_object()->id() == mo->id()) { - print_object = po; - break; - } - } - } - - if (print_object - && print_object->is_step_done(slaposSupportTree) - && ! print_object->support_mesh().empty()) - { - // If the supports are already calculated, save the timestamp of the respective step - // so we can later tell they were recalculated. - size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp; - if (! m_clipper || timestamp != m_old_timestamp) { - // The timestamp has changed. - m_clipper.reset(new MeshClipper); - // The mesh should already have the shared vertices calculated. - m_clipper->set_mesh(print_object->support_mesh()); - m_old_timestamp = timestamp; - } - } - else - // The supports are not valid. We better dump the cached data. - m_clipper.reset(); -} - - -void SupportsClipper::on_release() -{ - m_clipper.reset(); - m_old_timestamp = 0; - m_print_object_idx = -1; -} - -void SupportsClipper::render_cut() const -{ - const CommonGizmosDataObjects::ObjectClipper* ocl = get_pool()->object_clipper(); - if (ocl->get_position() == 0. - || ! get_pool()->instances_hider()->are_supports_shown() - || ! m_clipper) - return; - - const SelectionInfo* sel_info = get_pool()->selection_info(); - const ModelObject* mo = sel_info->model_object(); - const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); - //Geometry::Transformation vol_trafo = mo->volumes.front()->get_transformation(); - Geometry::Transformation trafo = inst_trafo;// * vol_trafo; - trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); - - - // Get transformation of supports - Geometry::Transformation supports_trafo = trafo; - supports_trafo.set_scaling_factor(Vec3d::Ones()); - supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), sel_info->get_sla_shift())); - supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2))); - // I don't know why, but following seems to be correct. - supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2), - 1, - 1.)); - - m_clipper->set_plane(*ocl->get_clipping_plane()); - m_clipper->set_transformation(supports_trafo); - -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPushMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_LEGACY_OPENGL_REMOVAL - m_clipper->render_cut({ 1.0f, 0.f, 0.37f, 1.0f }); -#else - glsafe(::glColor3f(1.0f, 0.f, 0.37f)); - m_clipper->render_cut(); -#endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glPopMatrix()); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -} - - -} // namespace GUI -} // namespace Slic3r +#include "GLGizmosCommon.hpp" + +#include + +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "libslic3r/SLAPrint.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Plater.hpp" + +#include "libslic3r/PresetBundle.hpp" + +#include + +namespace Slic3r { +namespace GUI { + +using namespace CommonGizmosDataObjects; + +CommonGizmosDataPool::CommonGizmosDataPool(GLCanvas3D* canvas) + : m_canvas(canvas) +{ + using c = CommonGizmosDataID; + m_data[c::SelectionInfo].reset( new SelectionInfo(this)); + m_data[c::InstancesHider].reset( new InstancesHider(this)); + m_data[c::HollowedMesh].reset( new HollowedMesh(this)); + m_data[c::Raycaster].reset( new Raycaster(this)); + m_data[c::ObjectClipper].reset( new ObjectClipper(this)); + m_data[c::SupportsClipper].reset( new SupportsClipper(this)); + +} + +void CommonGizmosDataPool::update(CommonGizmosDataID required) +{ + assert(check_dependencies(required)); + for (auto& [id, data] : m_data) { + if (int(required) & int(CommonGizmosDataID(id))) + data->update(); + else + if (data->is_valid()) + data->release(); + + } +} + + +SelectionInfo* CommonGizmosDataPool::selection_info() const +{ + SelectionInfo* sel_info = dynamic_cast(m_data.at(CommonGizmosDataID::SelectionInfo).get()); + assert(sel_info); + return sel_info->is_valid() ? sel_info : nullptr; +} + + +InstancesHider* CommonGizmosDataPool::instances_hider() const +{ + InstancesHider* inst_hider = dynamic_cast(m_data.at(CommonGizmosDataID::InstancesHider).get()); + assert(inst_hider); + return inst_hider->is_valid() ? inst_hider : nullptr; +} + +HollowedMesh* CommonGizmosDataPool::hollowed_mesh() const +{ + HollowedMesh* hol_mesh = dynamic_cast(m_data.at(CommonGizmosDataID::HollowedMesh).get()); + assert(hol_mesh); + return hol_mesh->is_valid() ? hol_mesh : nullptr; +} + +Raycaster* CommonGizmosDataPool::raycaster() const +{ + Raycaster* rc = dynamic_cast(m_data.at(CommonGizmosDataID::Raycaster).get()); + assert(rc); + return rc->is_valid() ? rc : nullptr; +} + +ObjectClipper* CommonGizmosDataPool::object_clipper() const +{ + ObjectClipper* oc = dynamic_cast(m_data.at(CommonGizmosDataID::ObjectClipper).get()); + // ObjectClipper is used from outside the gizmos to report current clipping plane. + // This function can be called when oc is nullptr. + return (oc && oc->is_valid()) ? oc : nullptr; +} + +SupportsClipper* CommonGizmosDataPool::supports_clipper() const +{ + SupportsClipper* sc = dynamic_cast(m_data.at(CommonGizmosDataID::SupportsClipper).get()); + assert(sc); + return sc->is_valid() ? sc : nullptr; +} + +#ifndef NDEBUG +// Check the required resources one by one and return true if all +// dependencies are met. +bool CommonGizmosDataPool::check_dependencies(CommonGizmosDataID required) const +{ + // This should iterate over currently required data. Each of them should + // be asked about its dependencies and it must check that all dependencies + // are also in required and before the current one. + for (auto& [id, data] : m_data) { + // in case we don't use this, the deps are irrelevant + if (! (int(required) & int(CommonGizmosDataID(id)))) + continue; + + + CommonGizmosDataID deps = data->get_dependencies(); + assert(int(deps) == (int(deps) & int(required))); + } + + + return true; +} +#endif // NDEBUG + + + + +void SelectionInfo::on_update() +{ + const Selection& selection = get_pool()->get_canvas()->get_selection(); + if (selection.is_single_full_instance()) { + m_model_object = selection.get_model()->objects[selection.get_object_idx()]; + m_z_shift = selection.get_first_volume()->get_sla_shift_z(); + } + else + m_model_object = nullptr; +} + +void SelectionInfo::on_release() +{ + m_model_object = nullptr; +} + +int SelectionInfo::get_active_instance() const +{ + const Selection& selection = get_pool()->get_canvas()->get_selection(); + return selection.get_instance_idx(); +} + + + + + +void InstancesHider::on_update() +{ + const ModelObject* mo = get_pool()->selection_info()->model_object(); + int active_inst = get_pool()->selection_info()->get_active_instance(); + GLCanvas3D* canvas = get_pool()->get_canvas(); + + if (mo && active_inst != -1) { + canvas->toggle_model_objects_visibility(false); + canvas->toggle_model_objects_visibility(true, mo, active_inst); + canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst); + canvas->set_use_clipping_planes(true); + // Some objects may be sinking, do not show whatever is below the bed. + canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); + canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits::max())); + + + std::vector meshes; + for (const ModelVolume* mv : mo->volumes) + meshes.push_back(&mv->mesh()); + + if (meshes != m_old_meshes) { + m_clippers.clear(); + for (const TriangleMesh* mesh : meshes) { + m_clippers.emplace_back(new MeshClipper); + m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); + m_clippers.back()->set_mesh(*mesh); + } + m_old_meshes = meshes; + } + } + else + canvas->toggle_model_objects_visibility(true); +} + +void InstancesHider::on_release() +{ + get_pool()->get_canvas()->toggle_model_objects_visibility(true); + get_pool()->get_canvas()->set_use_clipping_planes(false); + m_old_meshes.clear(); + m_clippers.clear(); +} + +void InstancesHider::show_supports(bool show) { + if (m_show_supports != show) { + m_show_supports = show; + on_update(); + } +} + +void InstancesHider::render_cut() const +{ + const SelectionInfo* sel_info = get_pool()->selection_info(); + const ModelObject* mo = sel_info->model_object(); + Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); + + size_t clipper_id = 0; + for (const ModelVolume* mv : mo->volumes) { + Geometry::Transformation vol_trafo = mv->get_transformation(); + Geometry::Transformation trafo = inst_trafo * vol_trafo; + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); + + auto& clipper = m_clippers[clipper_id]; + clipper->set_transformation(trafo); + const ObjectClipper* obj_clipper = get_pool()->object_clipper(); + if (obj_clipper->is_valid() && obj_clipper->get_clipping_plane() + && obj_clipper->get_position() != 0.) { + ClippingPlane clp = *get_pool()->object_clipper()->get_clipping_plane(); + clp.set_normal(-clp.get_normal()); + clipper->set_limiting_plane(clp); + } + else + clipper->set_limiting_plane(ClippingPlane::ClipsNothing()); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPushMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#if !ENABLE_LEGACY_OPENGL_REMOVAL + if (mv->is_model_part()) + glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); + else { + const ColorRGBA color = color_from_model_volume(*mv); + glsafe(::glColor4fv(color.data())); + } +#endif // !ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPushAttrib(GL_DEPTH_TEST)); + glsafe(::glDisable(GL_DEPTH_TEST)); +#if ENABLE_LEGACY_OPENGL_REMOVAL + clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv)); +#else + clipper->render_cut(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL + glsafe(::glPopAttrib()); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + ++clipper_id; + } +} + + + +void HollowedMesh::on_update() +{ + const ModelObject* mo = get_pool()->selection_info()->model_object(); + bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; + if (! mo || ! is_sla) + return; + + const GLCanvas3D* canvas = get_pool()->get_canvas(); + const PrintObjects& print_objects = canvas->sla_print()->objects(); + const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size())) + ? print_objects[m_print_object_idx] + : nullptr; + + // Find the respective SLAPrintObject. + if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { + m_print_objects_count = print_objects.size(); + m_print_object_idx = -1; + for (const SLAPrintObject* po : print_objects) { + ++m_print_object_idx; + if (po->model_object()->id() == mo->id()) { + print_object = po; + break; + } + } + } + + // If there is a valid SLAPrintObject, check state of Hollowing step. + if (print_object) { + if (print_object->is_step_done(slaposDrillHoles) && print_object->has_mesh(slaposDrillHoles)) { + size_t timestamp = print_object->step_state_with_timestamp(slaposDrillHoles).timestamp; + if (timestamp > m_old_hollowing_timestamp) { + const TriangleMesh& backend_mesh = print_object->get_mesh_to_slice(); + if (! backend_mesh.empty()) { + m_hollowed_mesh_transformed.reset(new TriangleMesh(backend_mesh)); + Transform3d trafo_inv = (canvas->sla_print()->sla_trafo(*mo) * print_object->model_object()->volumes.front()->get_transformation().get_matrix()).inverse(); + m_hollowed_mesh_transformed->transform(trafo_inv); + m_drainholes = print_object->model_object()->sla_drain_holes; + m_old_hollowing_timestamp = timestamp; + + indexed_triangle_set interior = print_object->hollowed_interior_mesh(); + its_flip_triangles(interior); + m_hollowed_interior_transformed = std::make_unique(std::move(interior)); + m_hollowed_interior_transformed->transform(trafo_inv); + } + else { + m_hollowed_mesh_transformed.reset(nullptr); + } + } + } + else + m_hollowed_mesh_transformed.reset(nullptr); + } +} + + +void HollowedMesh::on_release() +{ + m_hollowed_mesh_transformed.reset(); + m_old_hollowing_timestamp = 0; + m_print_object_idx = -1; +} + + +const TriangleMesh* HollowedMesh::get_hollowed_mesh() const +{ + return m_hollowed_mesh_transformed.get(); +} + +const TriangleMesh* HollowedMesh::get_hollowed_interior() const +{ + return m_hollowed_interior_transformed.get(); +} + + + + +void Raycaster::on_update() +{ + wxBusyCursor wait; + const ModelObject* mo = get_pool()->selection_info()->model_object(); + + if (! mo) + return; + + std::vector meshes; + const std::vector& mvs = mo->volumes; + if (mvs.size() == 1) { + assert(mvs.front()->is_model_part()); + const HollowedMesh* hollowed_mesh_tracker = get_pool()->hollowed_mesh(); + if (hollowed_mesh_tracker && hollowed_mesh_tracker->get_hollowed_mesh()) + meshes.push_back(hollowed_mesh_tracker->get_hollowed_mesh()); + } + if (meshes.empty()) { + for (const ModelVolume* mv : mvs) { + if (mv->is_model_part()) + meshes.push_back(&mv->mesh()); + } + } + + if (meshes != m_old_meshes) { + m_raycasters.clear(); + for (const TriangleMesh* mesh : meshes) + m_raycasters.emplace_back(new MeshRaycaster(*mesh)); + m_old_meshes = meshes; + } +} + +void Raycaster::on_release() +{ + m_raycasters.clear(); + m_old_meshes.clear(); +} + +std::vector Raycaster::raycasters() const +{ + std::vector mrcs; + for (const auto& raycaster_unique_ptr : m_raycasters) + mrcs.push_back(raycaster_unique_ptr.get()); + return mrcs; +} + + + + + +void ObjectClipper::on_update() +{ + const ModelObject* mo = get_pool()->selection_info()->model_object(); + if (! mo) + return; + + // which mesh should be cut? + std::vector meshes; + bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh(); + if (has_hollowed) + meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh()); + + if (meshes.empty()) + for (const ModelVolume* mv : mo->volumes) + meshes.push_back(&mv->mesh()); + + if (meshes != m_old_meshes) { + m_clippers.clear(); + for (const TriangleMesh* mesh : meshes) { + m_clippers.emplace_back(new MeshClipper); + m_clippers.back()->set_mesh(*mesh); + } + m_old_meshes = meshes; + + if (has_hollowed) + m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior()); + + m_active_inst_bb_radius = + mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); + } +} + + +void ObjectClipper::on_release() +{ + m_clippers.clear(); + m_old_meshes.clear(); + m_clp.reset(); + m_clp_ratio = 0.; + +} + +void ObjectClipper::render_cut() const +{ + if (m_clp_ratio == 0.) + return; + const SelectionInfo* sel_info = get_pool()->selection_info(); + const ModelObject* mo = sel_info->model_object(); + const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); + + size_t clipper_id = 0; + for (const ModelVolume* mv : mo->volumes) { + const Geometry::Transformation vol_trafo = mv->get_transformation(); + Geometry::Transformation trafo = inst_trafo * vol_trafo; + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); + + auto& clipper = m_clippers[clipper_id]; + clipper->set_plane(*m_clp); + clipper->set_transformation(trafo); + clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPushMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_LEGACY_OPENGL_REMOVAL + clipper->render_cut({ 1.0f, 0.37f, 0.0f, 1.0f }); +#else + glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); + clipper->render_cut(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES + + ++clipper_id; + } +} + + +void ObjectClipper::set_position(double pos, bool keep_normal) +{ + const ModelObject* mo = get_pool()->selection_info()->model_object(); + int active_inst = get_pool()->selection_info()->get_active_instance(); + double z_shift = get_pool()->selection_info()->get_sla_shift(); + + Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward(); + const Vec3d& center = mo->instances[active_inst]->get_offset() + Vec3d(0., 0., z_shift); + float dist = normal.dot(center); + + if (pos < 0.) + pos = m_clp_ratio; + + m_clp_ratio = pos; + m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2*m_active_inst_bb_radius))); + get_pool()->get_canvas()->set_as_dirty(); +} + + + +void SupportsClipper::on_update() +{ + const ModelObject* mo = get_pool()->selection_info()->model_object(); + bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; + if (! mo || ! is_sla) + return; + + const GLCanvas3D* canvas = get_pool()->get_canvas(); + const PrintObjects& print_objects = canvas->sla_print()->objects(); + const SLAPrintObject* print_object = (m_print_object_idx >= 0 && m_print_object_idx < int(print_objects.size())) + ? print_objects[m_print_object_idx] + : nullptr; + + // Find the respective SLAPrintObject. + if (m_print_object_idx < 0 || m_print_objects_count != int(print_objects.size())) { + m_print_objects_count = print_objects.size(); + m_print_object_idx = -1; + for (const SLAPrintObject* po : print_objects) { + ++m_print_object_idx; + if (po->model_object()->id() == mo->id()) { + print_object = po; + break; + } + } + } + + if (print_object + && print_object->is_step_done(slaposSupportTree) + && ! print_object->support_mesh().empty()) + { + // If the supports are already calculated, save the timestamp of the respective step + // so we can later tell they were recalculated. + size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp; + if (! m_clipper || timestamp != m_old_timestamp) { + // The timestamp has changed. + m_clipper.reset(new MeshClipper); + // The mesh should already have the shared vertices calculated. + m_clipper->set_mesh(print_object->support_mesh()); + m_old_timestamp = timestamp; + } + } + else + // The supports are not valid. We better dump the cached data. + m_clipper.reset(); +} + + +void SupportsClipper::on_release() +{ + m_clipper.reset(); + m_old_timestamp = 0; + m_print_object_idx = -1; +} + +void SupportsClipper::render_cut() const +{ + const CommonGizmosDataObjects::ObjectClipper* ocl = get_pool()->object_clipper(); + if (ocl->get_position() == 0. + || ! get_pool()->instances_hider()->are_supports_shown() + || ! m_clipper) + return; + + const SelectionInfo* sel_info = get_pool()->selection_info(); + const ModelObject* mo = sel_info->model_object(); + const Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); + //Geometry::Transformation vol_trafo = mo->volumes.front()->get_transformation(); + Geometry::Transformation trafo = inst_trafo;// * vol_trafo; + trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift())); + + + // Get transformation of supports + Geometry::Transformation supports_trafo = trafo; + supports_trafo.set_scaling_factor(Vec3d::Ones()); + supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), sel_info->get_sla_shift())); + supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2))); + // I don't know why, but following seems to be correct. + supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2), + 1, + 1.)); + + m_clipper->set_plane(*ocl->get_clipping_plane()); + m_clipper->set_transformation(supports_trafo); + +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPushMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +#if ENABLE_LEGACY_OPENGL_REMOVAL + m_clipper->render_cut({ 1.0f, 0.f, 0.37f, 1.0f }); +#else + glsafe(::glColor3f(1.0f, 0.f, 0.37f)); + m_clipper->render_cut(); +#endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if !ENABLE_GL_SHADERS_ATTRIBUTES + glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES +} + + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0c1985733..e50676ee1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2930,7 +2930,7 @@ int Plater::priv::get_selected_volume_idx() const if ((0 > idx) || (idx > 1000)) #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL return-1; - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); if (model.objects[idx]->volumes.size() > 1) return v->volume_idx(); return -1; @@ -3533,7 +3533,7 @@ void Plater::priv::replace_with_stl() if (selection.is_wipe_tower() || get_selection().get_volume_idxs().size() != 1) return; - const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* v = selection.get_first_volume(); int object_idx = v->object_idx(); int volume_idx = v->volume_idx(); @@ -6052,7 +6052,7 @@ void Plater::export_stl_obj(bool extended, bool selection_only) if (selection.get_mode() == Selection::Instance) mesh = mesh_to_export(*model_object, (selection.is_single_full_object() && model_object->instances.size() > 1) ? -1 : selection.get_instance_idx()); else { - const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); + const GLVolume* volume = selection.get_first_volume(); mesh = model_object->volumes[volume->volume_idx()]->mesh(); mesh.transform(volume->get_volume_transformation().get_matrix(), true); } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index c96e8efc7..18b3cfaa7 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -35,25 +35,25 @@ static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5 namespace Slic3r { namespace GUI { - Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) - : position(transform.get_offset()) - , rotation(transform.get_rotation()) - , scaling_factor(transform.get_scaling_factor()) - , mirror(transform.get_mirror()) - , full_matrix(transform.get_matrix()) +Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform) + : position(transform.get_offset()) + , rotation(transform.get_rotation()) + , scaling_factor(transform.get_scaling_factor()) + , mirror(transform.get_mirror()) + , full_matrix(transform.get_matrix()) #if ENABLE_TRANSFORMATIONS_BY_MATRICES - , transform(transform) - , rotation_matrix(transform.get_rotation_matrix()) - , scale_matrix(transform.get_scaling_factor_matrix()) - , mirror_matrix(transform.get_mirror_matrix()) + , transform(transform) + , rotation_matrix(transform.get_rotation_matrix()) + , scale_matrix(transform.get_scaling_factor_matrix()) + , mirror_matrix(transform.get_mirror_matrix()) #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - { +{ #if !ENABLE_TRANSFORMATIONS_BY_MATRICES - rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); - scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); - mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); + rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); + scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); + mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES - } +} Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) : m_volume(volume_transform) @@ -612,14 +612,14 @@ bool Selection::requires_uniform_scale() const ECoordinatesType coord_type = wxGetApp().obj_manipul()->get_coordinates_type(); if (is_single_volume_or_modifier()) { if (coord_type == ECoordinatesType::World) { - if (!Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_volume(*m_list.begin())->world_matrix()).get_rotation())) { + if (!Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_first_volume()->world_matrix()).get_rotation())) { if (reason != nullptr) *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_World; return true; } } else if (coord_type == ECoordinatesType::Instance) { - if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_volume_rotation())) { + if (!Geometry::is_rotation_ninety_degrees(get_first_volume()->get_volume_rotation())) { if (reason != nullptr) *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; return true; @@ -629,7 +629,7 @@ bool Selection::requires_uniform_scale() const } else if (is_single_full_instance()) { if (coord_type == ECoordinatesType::World) { - if (!Geometry::is_rotation_ninety_degrees(get_volume(*m_list.begin())->get_instance_rotation())) { + if (!Geometry::is_rotation_ninety_degrees(get_first_volume()->get_instance_rotation())) { if (reason != nullptr) *reason = EUniformScaleRequiredReason::InstanceNotAxisAligned_World; return true; @@ -1478,7 +1478,7 @@ int Selection::bake_transform_if_needed() const (is_single_volume_or_modifier() && !wxGetApp().obj_manipul()->is_local_coordinates())) { // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one - const GLVolume& volume = *get_volume(*get_volume_idxs().begin()); + const GLVolume& volume = *get_first_volume(); bool needs_baking = false; if (is_single_full_instance()) { // Is the instance angle close to a multiple of 90 degrees? @@ -1646,13 +1646,14 @@ void Selection::render(float scale_factor) trafo = Transform3d::Identity(); } else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) { - const GLVolume& v = *get_volume(*get_volume_idxs().begin()); + const GLVolume& v = *get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_scaling_factor_matrix()); - trafo = v.get_instance_transformation().get_matrix() * v.get_volume_transformation().get_matrix_no_scaling_factor(); + const Geometry::Transformation inst_trafo = get_first_volume()->get_instance_transformation(); + box = box.transformed(inst_trafo.get_scaling_factor_matrix()); + trafo = inst_trafo.get_matrix_no_scaling_factor(); #else - box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); - trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false); + box = box.transformed(get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); + trafo = get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 47a40877c..c9f0eb7c6 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -351,6 +351,7 @@ public: const IndicesList& get_volume_idxs() const { return m_list; } const GLVolume* get_volume(unsigned int volume_idx) const; + const GLVolume* get_first_volume() const { return get_volume(*m_list.begin()); } const ObjectIdxsToInstanceIdxsMap& get_content() const { return m_cache.content; } From f9f7e6e75995165cf8485cab506ada6522233d5b Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 5 May 2022 08:14:59 +0200 Subject: [PATCH 121/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::flattening_rotate(const Vec3d& normal) to use matrix multiplication Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 18b3cfaa7..e2a5b4a9d 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1158,13 +1158,21 @@ void Selection::flattening_rotate(const Vec3d& normal) for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; // Normal transformed from the object coordinate space to the world coordinate space. - const auto &voldata = m_cache.volumes_data[i]; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& old_inst_trafo = v.get_instance_transformation(); + const Vec3d tnormal = old_inst_trafo.get_matrix().matrix().block(0, 0, 3, 3).inverse().transpose() * normal; + // Additional rotation to align tnormal with the down vector in the world coordinate space. + const Transform3d rotation_matrix = Transform3d(Eigen::Quaterniond().setFromTwoVectors(tnormal, -Vec3d::UnitZ())); + v.set_instance_transformation(old_inst_trafo.get_offset_matrix() * rotation_matrix * old_inst_trafo.get_matrix_no_offset()); +#else + const auto& voldata = m_cache.volumes_data[i]; Vec3d tnormal = (Geometry::assemble_transform( - Vec3d::Zero(), voldata.get_instance_rotation(), + Vec3d::Zero(), voldata.get_instance_rotation(), voldata.get_instance_scaling_factor().cwiseInverse(), voldata.get_instance_mirror()) * normal).normalized(); // Additional rotation to align tnormal with the down vector in the world coordinate space. - auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, - Vec3d::UnitZ()); + auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, -Vec3d::UnitZ()); v.set_instance_rotation(Geometry::extract_euler_angles(extra_rotation.toRotationMatrix() * m_cache.volumes_data[i].get_instance_rotation_matrix())); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } #if !DISABLE_INSTANCES_SYNCH @@ -1648,12 +1656,11 @@ void Selection::render(float scale_factor) else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) { const GLVolume& v = *get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - const Geometry::Transformation inst_trafo = get_first_volume()->get_instance_transformation(); - box = box.transformed(inst_trafo.get_scaling_factor_matrix()); - trafo = inst_trafo.get_matrix_no_scaling_factor(); + box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_scaling_factor_matrix()); + trafo = v.get_instance_transformation().get_matrix() * v.get_volume_transformation().get_matrix_no_scaling_factor(); #else - box = box.transformed(get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); - trafo = get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false); + box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); + trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { @@ -1663,11 +1670,12 @@ void Selection::render(float scale_factor) box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } #if ENABLE_TRANSFORMATIONS_BY_MATRICES - box = box.transformed(get_volume(*ids.begin())->get_instance_transformation().get_scaling_factor_matrix()); - trafo = get_volume(*ids.begin())->get_instance_transformation().get_matrix_no_scaling_factor(); + const Geometry::Transformation inst_trafo = get_first_volume()->get_instance_transformation(); + box = box.transformed(inst_trafo.get_scaling_factor_matrix()); + trafo = inst_trafo.get_matrix_no_scaling_factor(); #else - box = box.transformed(get_volume(*ids.begin())->get_instance_transformation().get_matrix(true, true, false, true)); - trafo = get_volume(*ids.begin())->get_instance_transformation().get_matrix(false, false, true, false); + box = box.transformed(get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); + trafo = get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } From c8d93e69beab268681329a8132814b97e4957faf Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 5 May 2022 08:48:31 +0200 Subject: [PATCH 122/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::synchronize_unselected_volumes() to use the new matrix only implementation of Geometry::Transformation Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index e2a5b4a9d..b94c2eb38 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -3131,10 +3131,14 @@ void Selection::synchronize_unselected_volumes() #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL const int volume_idx = volume->volume_idx(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& trafo = volume->get_volume_transformation(); +#else const Vec3d& offset = volume->get_volume_offset(); const Vec3d& rotation = volume->get_volume_rotation(); const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); const Vec3d& mirror = volume->get_volume_mirror(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Process unselected volumes. for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { @@ -3145,10 +3149,14 @@ void Selection::synchronize_unselected_volumes() if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) continue; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + v->set_volume_transformation(trafo); +#else v->set_volume_offset(offset); v->set_volume_rotation(rotation); v->set_volume_scaling_factor(scaling_factor); v->set_volume_mirror(mirror); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } } } From 63c9ce23df0ffd6d8d4d1c6fa1dc0a185f554c6e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 5 May 2022 09:56:23 +0200 Subject: [PATCH 123/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void synchronize_unselected_instances(SyncRotationType sync_rotation_type) to use the new matrix only implementation of Geometry::Transformation Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 71 ++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index b94c2eb38..a3bf2bf7b 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -3026,22 +3026,30 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ if (done.size() == m_volumes->size()) break; - const GLVolume* volume = (*m_volumes)[i]; + const GLVolume* volume_i = (*m_volumes)[i]; #if ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - if (volume->is_wipe_tower) + if (volume_i->is_wipe_tower) continue; - const int object_idx = volume->object_idx(); + const int object_idx = volume_i->object_idx(); #else - const int object_idx = volume->object_idx(); + const int object_idx = volume_i->object_idx(); if (object_idx >= 1000) continue; #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL - const int instance_idx = volume->instance_idx(); - const Vec3d& rotation = volume->get_instance_rotation(); - const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); - const Vec3d& mirror = volume->get_instance_mirror(); + const int instance_idx = volume_i->instance_idx(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& curr_inst_trafo_i = volume_i->get_instance_transformation(); + const Vec3d curr_inst_rotation_i = curr_inst_trafo_i.get_rotation(); + const Vec3d& curr_inst_scaling_factor_i = curr_inst_trafo_i.get_scaling_factor(); + const Vec3d& curr_inst_mirror_i = curr_inst_trafo_i.get_mirror(); + const Vec3d old_inst_rotation_i = m_cache.volumes_data[i].get_instance_transform().get_rotation(); +#else + const Vec3d& rotation = volume_i->get_instance_rotation(); + const Vec3d& scaling_factor = volume_i->get_instance_scaling_factor(); + const Vec3d& mirror = volume_i->get_instance_mirror(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Process unselected instances. for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { @@ -3051,14 +3059,17 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ if (done.find(j) != done.end()) continue; - GLVolume* v = (*m_volumes)[j]; - if (v->object_idx() != object_idx || v->instance_idx() == instance_idx) + GLVolume* volume_j = (*m_volumes)[j]; + if (volume_j->object_idx() != object_idx || volume_j->instance_idx() == instance_idx) continue; #if ENABLE_TRANSFORMATIONS_BY_MATRICES - const Vec3d inst_rotation_i = m_cache.volumes_data[i].get_instance_transform().get_rotation(); - const Vec3d inst_rotation_j = m_cache.volumes_data[j].get_instance_transform().get_rotation(); - assert(is_rotation_xy_synchronized(inst_rotation_i, inst_rotation_j)); + const Vec3d old_inst_rotation_j = m_cache.volumes_data[j].get_instance_transform().get_rotation(); + assert(is_rotation_xy_synchronized(old_inst_rotation_i, old_inst_rotation_j)); + const Geometry::Transformation& curr_inst_trafo_j = volume_j->get_instance_transformation(); + const Vec3d curr_inst_rotation_j = curr_inst_trafo_j.get_rotation(); + Vec3d new_inst_offset_j = curr_inst_trafo_j.get_offset(); + Vec3d new_inst_rotation_j = curr_inst_rotation_j; #else assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -3066,45 +3077,59 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ case SyncRotationType::NONE: { // z only rotation -> synch instance z // The X,Y rotations should be synchronized from start to end of the rotation. - assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + assert(is_rotation_xy_synchronized(curr_inst_rotation_i, curr_inst_rotation_j)); if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) - v->set_instance_offset(Z, volume->get_instance_offset().z()); + new_inst_offset_j.z() = curr_inst_trafo_i.get_offset().z(); +#else + assert(is_rotation_xy_synchronized(rotation, volume_j->get_instance_rotation())); + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) + volume_j->set_instance_offset(Z, volume_i->get_instance_offset().z()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES break; } case SyncRotationType::GENERAL: { // generic rotation -> update instance z with the delta of the rotation. #if ENABLE_TRANSFORMATIONS_BY_MATRICES - const double z_diff = Geometry::rotation_diff_z(inst_rotation_i, inst_rotation_j); + const double z_diff = Geometry::rotation_diff_z(old_inst_rotation_i, old_inst_rotation_j); + new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); #else const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); + volume_j->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); break; } #if ENABLE_WORLD_COORDINATE case SyncRotationType::FULL: { // generic rotation -> update instance z with the delta of the rotation. #if ENABLE_TRANSFORMATIONS_BY_MATRICES - const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, inst_rotation_j)); + const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(curr_inst_rotation_i, old_inst_rotation_j)); #else const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, m_cache.volumes_data[j].get_instance_rotation())); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d& axis = angle_axis.axis(); const double z_diff = (std::abs(axis.x()) > EPSILON || std::abs(axis.y()) > EPSILON) ? #if ENABLE_TRANSFORMATIONS_BY_MATRICES - angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(rotation, inst_rotation_j); + angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(curr_inst_rotation_i, old_inst_rotation_j); + + new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); #else angle_axis.angle()* axis.z() : Geometry::rotation_diff_z(rotation, m_cache.volumes_data[j].get_instance_rotation()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); + volume_j->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES break; } #endif // ENABLE_WORLD_COORDINATE } - v->set_instance_scaling_factor(scaling_factor); - v->set_instance_mirror(mirror); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + volume_j->set_instance_transformation(Geometry::assemble_transform(new_inst_offset_j, new_inst_rotation_j, + curr_inst_scaling_factor_i, curr_inst_mirror_i)); +#else + volume_j->set_instance_scaling_factor(scaling_factor); + volume_j->set_instance_mirror(mirror); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES done.insert(j); } From b5d366d385a8f0f9e44f08470ee1910051fcf0fa Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 5 May 2022 10:12:16 +0200 Subject: [PATCH 124/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Allow skew in matrices Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 2 ++ src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 30 ++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index ddb878a7d..9b892eb53 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1289,6 +1289,7 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED if (!use_uniform_scale) { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES int res = selection.bake_transform_if_needed(); if (res == -1) { // Enforce uniform scaling. @@ -1296,6 +1297,7 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) return; } else if (res == 0) +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES // Recalculate cached values at this panel, refresh the screen. this->UpdateAndShow(true); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 675ed0b3f..061361e40 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -75,14 +75,36 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) { if (mouse_event.Dragging()) { if (m_dragging) { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + int res = 1; + if (m_scale.x() != m_scale.y() || m_scale.x() != m_scale.z()) + res = m_parent.get_selection().bake_transform_if_needed(); + + if (res != 1) { + do_stop_dragging(true); + return true; + } + else { +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES // Apply new temporary scale factors TransformationType transformation_type(TransformationType::Local_Absolute_Joint); if (mouse_event.AltDown()) transformation_type.set_independent(); - Selection &selection = m_parent.get_selection(); - selection.scale(get_scale(), transformation_type); - if (mouse_event.CmdDown()) selection.translate(m_offset, ECoordinatesType::Local); - } + Selection &selection = m_parent.get_selection(); + selection.scale(m_scale, transformation_type); +#if ENABLE_WORLD_COORDINATE + if (mouse_event.CmdDown()) selection.translate(m_offset, wxGetApp().obj_manipul()->get_coordinates_type()); +#else + if (mouse_event.CmdDown()) selection.translate(m_offset, true); +#endif // ENABLE_WORLD_COORDINATE +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + } +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES + } } return use_grabbers(mouse_event); } From 88ce6ccdef5f680709ea8b676688784a7af287dd Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 11 May 2022 10:54:42 +0200 Subject: [PATCH 125/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::scale(const Vec3d& scale, TransformationType transformation_type) to use matrix multiplication Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 32 +++- src/libslic3r/Geometry.hpp | 12 +- src/slic3r/GUI/GLCanvas3D.cpp | 31 +++- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 40 ++++ src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 35 +++- src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 5 + src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 12 ++ src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 154 ++++++++++++++- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 4 + src/slic3r/GUI/Plater.cpp | 2 +- src/slic3r/GUI/Selection.cpp | 217 ++++++++++++++-------- src/slic3r/GUI/Selection.hpp | 14 ++ 14 files changed, 464 insertions(+), 98 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 031e489f7..61217f4a1 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -313,7 +313,6 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation, return transform; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES void assemble_transform(Transform3d& transform, const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror) { transform = translation * rotation * scale * mirror; @@ -326,6 +325,19 @@ Transform3d assemble_transform(const Transform3d& translation, const Transform3d return transform; } +void translation_transform(Transform3d& transform, const Vec3d& translation) +{ + transform = Transform3d::Identity(); + transform.translate(translation); +} + +Transform3d translation_transform(const Vec3d& translation) +{ + Transform3d transform; + translation_transform(transform, translation); + return transform; +} + void rotation_transform(Transform3d& transform, const Vec3d& rotation) { transform = Transform3d::Identity(); @@ -351,7 +363,6 @@ Transform3d scale_transform(const Vec3d& scale) scale_transform(transform, scale); return transform; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix) { @@ -433,6 +444,14 @@ static std::pair extract_rotation_scale(const Transfor return { Transform3d(rotation), Transform3d(scale) }; } +static bool contains_skew(const Transform3d& trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return !scale.isDiagonal(); +} + Vec3d Transformation::get_rotation() const { return extract_euler_angles(extract_rotation(m_matrix)); @@ -623,7 +642,12 @@ void Transformation::set_mirror(Axis axis, double mirror) #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +bool Transformation::has_skew() const +{ + return contains_skew(m_matrix); +} +#else void Transformation::set_from_transform(const Transform3d& transform) { // offset @@ -661,7 +685,7 @@ void Transformation::set_from_transform(const Transform3d& transform) // if (!m_matrix.isApprox(transform)) // std::cout << "something went wrong in extracting data from matrix" << std::endl; } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void Transformation::reset() { diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index c55867023..bbca3a5e3 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -336,7 +336,6 @@ void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones()); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES // Sets the given transform by multiplying the given transformations in the following order: // T = translation * rotation * scale * mirror void assemble_transform(Transform3d& transform, const Transform3d& translation = Transform3d::Identity(), @@ -348,6 +347,12 @@ void assemble_transform(Transform3d& transform, const Transform3d& translation = Transform3d assemble_transform(const Transform3d& translation = Transform3d::Identity(), const Transform3d& rotation = Transform3d::Identity(), const Transform3d& scale = Transform3d::Identity(), const Transform3d& mirror = Transform3d::Identity()); +// Sets the given transform by assembling the given translation +void translation_transform(Transform3d& transform, const Vec3d& translation); + +// Returns the transform obtained by assembling the given translation +Transform3d translation_transform(const Vec3d& translation); + // Sets the given transform by assembling the given rotations in the following order: // 1) rotate X // 2) rotate Y @@ -365,7 +370,6 @@ void scale_transform(Transform3d& transform, const Vec3d& scale); // Returns the transform obtained by assembling the given scale factors Transform3d scale_transform(const Vec3d& scale); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Returns the euler angles extracted from the given rotation matrix // Warning -> The matrix should not contain any scale or shear !!! @@ -478,6 +482,10 @@ public: void set_mirror(const Vec3d& mirror); void set_mirror(Axis axis, double mirror); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + bool has_skew() const; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + #if !ENABLE_TRANSFORMATIONS_BY_MATRICES void set_from_transform(const Transform3d& transform); #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f3ba04c50..8a2599866 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2914,7 +2914,13 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) else displacement = multiplier * direction; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + TransformationType trafo_type; + trafo_type.set_relative(); + m_selection.translate(displacement, trafo_type); +#else m_selection.translate(displacement); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_dirty = true; } ); @@ -3579,7 +3585,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + TransformationType trafo_type; + trafo_type.set_relative(); + m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type); +#else m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) update_sequential_clearance(); wxGetApp().obj_manipul()->set_dirty(); @@ -3996,12 +4008,12 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) Selection::EMode selection_mode = m_selection.get_mode(); for (const GLVolume* v : m_volumes.volumes) { - int object_idx = v->object_idx(); + const int object_idx = v->object_idx(); if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) continue; - int instance_idx = v->instance_idx(); - int volume_idx = v->volume_idx(); + const int instance_idx = v->instance_idx(); + const int volume_idx = v->volume_idx(); done.insert(std::pair(object_idx, instance_idx)); @@ -4009,13 +4021,22 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); +#else model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor()); model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (selection_mode == Selection::Volume) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); +#else model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor()); model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } model_object->invalidate_bounding_box(); } @@ -4024,10 +4045,10 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) // Fixes sinking/flying instances for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; - double shift_z = m->get_instance_min_z(i.second); + const double shift_z = m->get_instance_min_z(i.second); // leave sinking instances as sinking if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) { - Vec3d shift(0.0, 0.0, -shift_z); + const Vec3d shift(0.0, 0.0, -shift_z); m_selection.translate(i.first, i.second, shift); m->translate_instance(i.second, shift); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 9b892eb53..871c90c51 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -461,9 +461,16 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_reset_scale_button->SetToolTip(_L("Reset scale")); m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset scale")); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + bool old_uniform = m_uniform_scale; + m_uniform_scale = true; + change_scale_value(0, 100.0); + m_uniform_scale = old_uniform; +#else change_scale_value(0, 100.); change_scale_value(1, 100.); change_scale_value(2, 100.); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES }); editors_grid_sizer->Add(m_reset_scale_button); @@ -616,6 +623,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = volume->get_instance_offset(); #endif // !ENABLE_WORLD_COORDINATE +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. #if ENABLE_WORLD_COORDINATE if (is_world_coordinates() && !m_uniform_scale && @@ -627,6 +635,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_uniform_scale = true; m_lock_bnt->SetLock(true); } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE if (is_world_coordinates()) { @@ -790,6 +799,7 @@ void ObjectManipulation::update_if_dirty() update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); } +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE Selection::EUniformScaleRequiredReason reason; if (selection.requires_uniform_scale(&reason)) { @@ -829,10 +839,13 @@ void ObjectManipulation::update_if_dirty() #endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED } else { +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_lock_bnt->SetLock(m_uniform_scale); m_lock_bnt->SetToolTip(wxEmptyString); m_lock_bnt->enable(); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_WORLD_COORDINATE { @@ -1048,7 +1061,19 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.setup_cache(); #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + TransformationType trafo_type; + trafo_type.set_relative(); + switch (get_coordinates_type()) + { + case ECoordinatesType::Instance: { trafo_type.set_instance(); break; } + case ECoordinatesType::Local: { trafo_type.set_local(); break; } + default: { break; } + } + selection.translate(position - m_cache.position, trafo_type); +#else selection.translate(position - m_cache.position, get_coordinates_type()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else selection.translate(position - m_cache.position, selection.requires_local_axes()); #endif // ENABLE_WORLD_COORDINATE @@ -1191,6 +1216,9 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const transformation_type.set_local(); #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; +#else bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; @@ -1203,6 +1231,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); } } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else TransformationType transformation_type(TransformationType::World_Relative_Joint); if (selection.is_single_full_instance()) { @@ -1231,6 +1260,12 @@ void ObjectManipulation::do_size(int axis, const Vec3d& scale) const else if (is_instance_coordinates()) transformation_type.set_instance(); + if (!is_local_coordinates()) + transformation_type.set_relative(); + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; +#else bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; @@ -1243,6 +1278,7 @@ void ObjectManipulation::do_size(int axis, const Vec3d& scale) const scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); } } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES selection.setup_cache(); selection.scale(scaling_factor, transformation_type); @@ -1303,6 +1339,10 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) } m_uniform_scale = use_uniform_scale; + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + set_dirty(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else #if ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance() && is_world_coordinates() && !use_uniform_scale) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 1e0ff6c9e..bee57ec6e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -333,7 +333,11 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { wxGetApp().obj_manipul()->set_dirty(); m_parent.set_as_dirty(); return true; - } else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) { + } + else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) { +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + do_stop_dragging(is_leaving); +#else for (auto &grabber : m_grabbers) grabber.dragging = false; m_dragging = false; @@ -356,12 +360,41 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); // updates camera target constraints m_parent.refresh_camera_scene_box(); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED return true; } } return false; } +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +void GLGizmoBase::do_stop_dragging(bool perform_mouse_cleanup) +{ + for (auto& grabber : m_grabbers) grabber.dragging = false; + m_dragging = false; + + // NOTE: This should be part of GLCanvas3D + // Reset hover_id when leave window + if (perform_mouse_cleanup) m_parent.mouse_up_cleanup(); + + on_stop_dragging(); + + // There is prediction that after draggign, data are changed + // Data are updated twice also by canvas3D::reload_scene. + // Should be fixed. + m_parent.get_gizmos_manager().update_data(); + + wxGetApp().obj_manipul()->set_dirty(); + + // Let the plater know that the dragging finished, so a delayed + // refresh of the scene with the background processing data should + // be performed. + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); + // updates camera target constraints + m_parent.refresh_camera_scene_box(); +} +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + std::string GLGizmoBase::format(float value, unsigned int decimals) const { return Slic3r::string_printf("%.*f", decimals, value); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index f61654183..fff699479 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -227,6 +227,11 @@ protected: /// Keep information about mouse click /// same as on_mouse bool use_grabbers(const wxMouseEvent &mouse_event); + +#if ENABLE_WORLD_COORDINATE + void do_stop_dragging(bool perform_mouse_cleanup); +#endif // ENABLE_WORLD_COORDINATE + private: // Flag for dirty visible state of Gizmo // When True then need new rendering diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 15030a0fa..8ef23d538 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -126,7 +126,7 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) #else const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix(); + const Transform3d instance_matrix = Geometry::translation_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); const Transform3d& view_matrix = camera.get_view_matrix(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index afb0b9140..19422e167 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -141,7 +141,19 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data) Selection &selection = m_parent.get_selection(); #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + TransformationType trafo_type; + trafo_type.set_relative(); + switch (wxGetApp().obj_manipul()->get_coordinates_type()) + { + case ECoordinatesType::Instance: { trafo_type.set_instance(); break; } + case ECoordinatesType::Local: { trafo_type.set_local(); break; } + default: { break; } + } + selection.translate(m_displacement, trafo_type); +#else selection.translate(m_displacement, wxGetApp().obj_manipul()->get_coordinates_type()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else selection.translate(m_displacement); #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index a89173460..b72c6761f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -787,7 +787,7 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const } #if ENABLE_WORLD_COORDINATE - return Geometry::assemble_transform(m_center) * m_orient_matrix * ret; + return Geometry::translation_transform(m_center) * 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; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 061361e40..af5b3adb2 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -41,6 +41,9 @@ GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filen std::string GLGizmoScale3D::get_tooltip() const { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Vec3d scale = 100.0 * m_scale; +#else const Selection& selection = m_parent.get_selection(); Vec3d scale = 100.0 * Vec3d::Ones(); @@ -52,6 +55,7 @@ std::string GLGizmoScale3D::get_tooltip() const else if (selection.is_single_modifier() || selection.is_single_volume()) #endif // ENABLE_WORLD_COORDINATE scale = 100.0 * selection.get_first_volume()->get_volume_scaling_factor(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging) return "X: " + format(scale.x(), 4) + "%"; @@ -89,32 +93,65 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES // Apply new temporary scale factors - TransformationType transformation_type(TransformationType::Local_Absolute_Joint); - if (mouse_event.AltDown()) transformation_type.set_independent(); +#if ENABLE_WORLD_COORDINATE + TransformationType transformation_type; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + if (wxGetApp().obj_manipul()->is_local_coordinates()) + transformation_type.set_local(); + else if (wxGetApp().obj_manipul()->is_instance_coordinates()) + transformation_type.set_instance(); - Selection &selection = m_parent.get_selection(); + transformation_type.set_relative(); +#else + if (!wxGetApp().obj_manipul()->is_world_coordinates()) + transformation_type.set_local(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#else + TransformationType transformation_type(TransformationType::Local_Absolute_Joint); +#endif // ENABLE_WORLD_COORDINATE + if (mouse_event.AltDown()) transformation_type.set_independent(); + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_parent.get_selection().scale_and_translate(m_scale, m_offset, transformation_type); +#else + Selection& selection = m_parent.get_selection(); selection.scale(m_scale, transformation_type); #if ENABLE_WORLD_COORDINATE if (mouse_event.CmdDown()) selection.translate(m_offset, wxGetApp().obj_manipul()->get_coordinates_type()); #else if (mouse_event.CmdDown()) selection.translate(m_offset, true); #endif // ENABLE_WORLD_COORDINATE +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED } #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES - } + } } return use_grabbers(mouse_event); } void GLGizmoScale3D::data_changed() { - const Selection &selection = m_parent.get_selection(); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES + const Selection &selection = m_parent.get_selection(); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE +#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED + bool enable_scale_xyz = !selection.requires_uniform_scale(); +#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#else bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); +#endif // ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + set_scale(Vec3d::Ones()); +#else +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { +#else for (unsigned int i = 0; i < 6; ++i) m_grabbers[i].enabled = enable_scale_xyz; @@ -129,6 +166,8 @@ void GLGizmoScale3D::data_changed() } else set_scale(Vec3d::Ones()); +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } bool GLGizmoScale3D::on_init() @@ -251,7 +290,7 @@ void GLGizmoScale3D::on_render() #if ENABLE_WORLD_COORDINATE #if ENABLE_TRANSFORMATIONS_BY_MATRICES const Transform3d inst_trafo = v.get_instance_transformation().get_matrix_no_scaling_factor(); - m_grabbers_transform = inst_trafo * Geometry::assemble_transform(m_bounding_box.center()); + m_grabbers_transform = inst_trafo * Geometry::translation_transform(m_bounding_box.center()); m_center = inst_trafo * m_bounding_box.center(); #else m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center()); @@ -677,6 +716,58 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int } #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) +{ + double ratio = calc_ratio(data); + if (ratio > 0.0) { + Vec3d curr_scale = m_scale; + Vec3d starting_scale = m_starting.scale; + const Selection& selection = m_parent.get_selection(); + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + + curr_scale(axis) = starting_scale(axis) * ratio; + m_scale = curr_scale; + + if (m_starting.ctrl_down && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { + double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); + + if (m_hover_id == 2 * axis) + local_offset *= -1.0; + + Vec3d center_offset = m_starting.instance_center - m_starting.center; // world coordinates (== Vec3d::Zero() for single volume selection) + if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local) + // from world coordinates to instance coordinates + center_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix().inverse() * center_offset; + + local_offset += (ratio - 1.0) * center_offset(axis); + + switch (axis) + { + case X: { m_offset = local_offset * Vec3d::UnitX(); break; } + case Y: { m_offset = local_offset * Vec3d::UnitY(); break; } + case Z: { m_offset = local_offset * Vec3d::UnitZ(); break; } + default: { m_offset = Vec3d::Zero(); break; } + } + + if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local) + // from instance coordinates to world coordinates + m_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix() * m_offset; + + if (selection.is_single_volume_or_modifier()) { + if (coordinates_type == ECoordinatesType::Instance) + m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * m_offset; + else if (coordinates_type == ECoordinatesType::Local) { + m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * + selection.get_first_volume()->get_volume_transformation().get_rotation_matrix() * m_offset; + } + } + } + else + m_offset = Vec3d::Zero(); + } +} +#else void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { #if ENABLE_WORLD_COORDINATE @@ -721,6 +812,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; #endif // ENABLE_WORLD_COORDINATE + if (m_starting.ctrl_down) { #if ENABLE_WORLD_COORDINATE double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); @@ -734,7 +826,7 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) #if ENABLE_WORLD_COORDINATE Vec3d center_offset = m_starting.instance_center - m_starting.center; if (selection.is_single_full_instance() && coordinates_type != ECoordinatesType::World) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); + const Transform3d m = Geometry::rotation_transform(selection.get_first_volume()->get_instance_rotation()).inverse(); center_offset = m * center_offset; } @@ -764,12 +856,57 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) m_offset = Vec3d::Zero(); } } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void GLGizmoScale3D::do_scale_uniform(const UpdateData & data) +{ + const double ratio = calc_ratio(data); + if (ratio > 0.0) { + m_scale = m_starting.scale * ratio; + + const Selection& selection = m_parent.get_selection(); + const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); + if (m_starting.ctrl_down && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { + m_offset = 0.5 * (ratio - 1.0) * m_starting.box.size(); + + if (m_hover_id == 6 || m_hover_id == 9) + m_offset.x() *= -1.0; + if (m_hover_id == 6 || m_hover_id == 7) + m_offset.y() *= -1.0; + + Vec3d center_offset = m_starting.instance_center - m_starting.center; // world coordinates (== Vec3d::Zero() for single volume selection) + + if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local) + // from world coordinates to instance coordinates + center_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix().inverse() * center_offset; + + m_offset += (ratio - 1.0) * center_offset; + + if (selection.is_single_full_instance() && coordinates_type == ECoordinatesType::Local) + // from instance coordinates to world coordinates + m_offset = selection.get_first_volume()->get_instance_transformation().get_rotation_matrix() * m_offset; + + if (selection.is_single_volume_or_modifier()) { + if (coordinates_type == ECoordinatesType::Instance) + m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * m_offset; + else if (coordinates_type == ECoordinatesType::Local) { + m_offset = selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix().inverse() * + selection.get_first_volume()->get_volume_transformation().get_rotation_matrix() * m_offset; + } + } + } + else + m_offset = Vec3d::Zero(); + } +} +#else void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) { const double ratio = calc_ratio(data); if (ratio > 0.0) { m_scale = m_starting.scale * ratio; + #if ENABLE_WORLD_COORDINATE if (m_starting.ctrl_down) { m_offset = 0.5 * (ratio - 1.0) * m_starting.box.size(); @@ -783,7 +920,7 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) Vec3d center_offset = m_starting.instance_center - m_starting.center; if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); + const Transform3d m = Geometry::rotation_transform(selection.get_first_volume()->get_instance_rotation()).inverse(); center_offset = m * center_offset; } @@ -794,6 +931,7 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) m_offset = Vec3d::Zero(); } } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index b8001d7a9..754b588b4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -67,7 +67,11 @@ public: void set_snap_step(double step) { m_snap_step = step; } const Vec3d& get_scale() const { return m_scale; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; m_offset = Vec3d::Zero(); } +#else void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED const Vec3d& get_starting_scale() const { return m_starting.scale; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e50676ee1..c7ddf8f78 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3852,7 +3852,7 @@ void Plater::priv::reload_from_disk() new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - new_volume->set_transformation(Geometry::assemble_transform(old_volume->source.transform.get_offset()) * + new_volume->set_transformation(Geometry::translation_transform(old_volume->source.transform.get_offset()) * old_volume->get_transformation().get_matrix_no_offset() * old_volume->source.transform.get_matrix_no_offset()); new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); #else diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index a3bf2bf7b..33ecd2412 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -599,6 +599,7 @@ bool Selection::matches(const std::vector& volume_idxs) const return count == (unsigned int)m_list.size(); } +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE bool Selection::requires_uniform_scale(EUniformScaleRequiredReason* reason) const #else @@ -667,6 +668,7 @@ bool Selection::requires_uniform_scale() const return true; #endif // ENABLE_WORLD_COORDINATE } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES int Selection::get_object_idx() const { @@ -760,58 +762,29 @@ void Selection::setup_cache() } #if ENABLE_TRANSFORMATIONS_BY_MATRICES -void Selection::translate(const Vec3d& displacement, ECoordinatesType type) +void Selection::translate(const Vec3d& displacement, TransformationType transformation_type) { if (!m_valid) return; + assert(transformation_type.relative()); + for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; if (m_mode == Instance && !is_wipe_tower()) { assert(is_from_fully_selected_instance(i)); - switch (type) - { - case ECoordinatesType::World: - { - v.set_instance_transformation(Geometry::assemble_transform(displacement) * volume_data.get_instance_full_matrix()); - break; - } - case ECoordinatesType::Local: - { - const Vec3d world_displacemet = volume_data.get_instance_rotation_matrix() * displacement; - v.set_instance_transformation(Geometry::assemble_transform(world_displacemet) * volume_data.get_instance_full_matrix()); - break; - } - default: { assert(false); break; } - } - } - else { - switch (type) - { - case ECoordinatesType::World: - { - const Transform3d inst_matrix_no_offset = volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix(); - const Vec3d inst_displacement = inst_matrix_no_offset.inverse() * displacement; - v.set_volume_transformation(Geometry::assemble_transform(inst_displacement) * volume_data.get_volume_full_matrix()); - break; - } - case ECoordinatesType::Instance: - { - const Vec3d inst_displacement = volume_data.get_instance_scale_matrix().inverse() * displacement; - v.set_volume_transformation(Geometry::assemble_transform(inst_displacement) * volume_data.get_volume_full_matrix()); - break; - } - case ECoordinatesType::Local: - { - const Vec3d inst_displacement = volume_data.get_instance_scale_matrix().inverse() * - volume_data.get_volume_rotation_matrix() * displacement; - v.set_volume_transformation(Geometry::assemble_transform(inst_displacement) * volume_data.get_volume_full_matrix()); - break; - } - default: { assert(false); break; } + if (transformation_type.world()) + v.set_instance_transformation(Geometry::translation_transform(displacement) * volume_data.get_instance_full_matrix()); + else if (transformation_type.local()) { + const Vec3d world_displacement = volume_data.get_instance_rotation_matrix() * displacement; + v.set_instance_transformation(Geometry::translation_transform(world_displacement) * volume_data.get_instance_full_matrix()); } + else + assert(false); } + else + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(displacement)); } #if !DISABLE_INSTANCES_SYNCH @@ -914,51 +887,35 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); if (m_mode == Instance && !is_wipe_tower()) { assert(is_from_fully_selected_instance(i)); - const Geometry::Transformation& old_trafo = volume_data.get_instance_transform(); Transform3d new_rotation_matrix = Transform3d::Identity(); if (transformation_type.absolute()) new_rotation_matrix = rotation_matrix; else { if (transformation_type.world()) - new_rotation_matrix = rotation_matrix * old_trafo.get_rotation_matrix(); + new_rotation_matrix = rotation_matrix * inst_trafo.get_rotation_matrix(); else if (transformation_type.local()) - new_rotation_matrix = old_trafo.get_rotation_matrix() * rotation_matrix; + new_rotation_matrix = inst_trafo.get_rotation_matrix() * rotation_matrix; else assert(false); } - const Vec3d new_offset = transformation_type.independent() ? old_trafo.get_offset() : - m_cache.dragging_center + new_rotation_matrix * old_trafo.get_rotation_matrix().inverse() * - (old_trafo.get_offset() - m_cache.dragging_center); - v.set_instance_transformation(Geometry::assemble_transform(Geometry::assemble_transform(new_offset), new_rotation_matrix, - old_trafo.get_scaling_factor_matrix(), old_trafo.get_mirror_matrix())); + const Vec3d new_offset = transformation_type.independent() ? inst_trafo.get_offset() : + m_cache.dragging_center + new_rotation_matrix * inst_trafo.get_rotation_matrix().inverse() * + (inst_trafo.get_offset() - m_cache.dragging_center); + v.set_instance_transformation(Geometry::assemble_transform(Geometry::translation_transform(new_offset), new_rotation_matrix, + inst_trafo.get_scaling_factor_matrix(), inst_trafo.get_mirror_matrix())); } else { - const Geometry::Transformation& old_trafo = volume_data.get_volume_transform(); - Transform3d new_rotation_matrix = Transform3d::Identity(); - - if (transformation_type.absolute()) - new_rotation_matrix = rotation_matrix; - else { - if (transformation_type.world()) { - const Transform3d inst_rotation_matrix = volume_data.get_instance_transform().get_rotation_matrix(); - new_rotation_matrix = inst_rotation_matrix.inverse() * rotation_matrix * inst_rotation_matrix * old_trafo.get_rotation_matrix(); - } - else if (transformation_type.instance()) - new_rotation_matrix = rotation_matrix * old_trafo.get_rotation_matrix(); - else if (transformation_type.local()) - new_rotation_matrix = old_trafo.get_rotation_matrix() * rotation_matrix; - else - assert(false); + if (transformation_type.absolute()) { + const Geometry::Transformation& volume_trafo = volume_data.get_volume_transform(); + v.set_volume_transformation(Geometry::assemble_transform(volume_trafo.get_offset_matrix(), Geometry::rotation_transform(rotation), + volume_trafo.get_scaling_factor_matrix(), volume_trafo.get_mirror_matrix())); } - - const Vec3d new_offset = !is_wipe_tower() ? old_trafo.get_offset() : - m_cache.dragging_center + new_rotation_matrix * old_trafo.get_rotation_matrix().inverse() * - (old_trafo.get_offset() - m_cache.dragging_center); - v.set_volume_transformation(Geometry::assemble_transform(Geometry::assemble_transform(new_offset), new_rotation_matrix, - old_trafo.get_scaling_factor_matrix(), old_trafo.get_mirror_matrix())); + else + transform_volume_relative(v, volume_data, transformation_type, Geometry::rotation_transform(rotation)); } } @@ -1185,6 +1142,12 @@ void Selection::flattening_rotate(const Vec3d& normal) this->set_bounding_boxes_dirty(); } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void Selection::scale(const Vec3d& scale, TransformationType transformation_type) +{ + scale_and_translate(scale, Vec3d::Zero(), transformation_type); +} +#else void Selection::scale(const Vec3d& scale, TransformationType transformation_type) { if (!m_valid) @@ -1259,6 +1222,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void Selection::scale_to_fit_print_volume(const BuildVolume& volume) { @@ -1281,7 +1245,13 @@ void Selection::scale_to_fit_print_volume(const BuildVolume& volume) // center selection on print bed setup_cache(); offset.z() = -get_bounding_box().min.z(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + TransformationType trafo_type; + trafo_type.set_relative(); + translate(offset, trafo_type); +#else translate(offset); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot wxGetApp().obj_manipul()->set_dirty(); @@ -1371,7 +1341,78 @@ void Selection::mirror(Axis axis) set_bounding_boxes_dirty(); } -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type) +{ + if (!m_valid) + return; + + std::cout << "Selection::scale_and_translate: " << to_string(scale) << " - " << to_string(translation) << "\n"; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); + if (m_mode == Instance) { + assert(is_from_fully_selected_instance(i)); + if (transformation_type.absolute()) { + assert(transformation_type.local()); + assert(transformation_type.joint()); + v.set_instance_transformation(Geometry::assemble_transform(inst_trafo.get_offset_matrix(), inst_trafo.get_rotation_matrix(), + Geometry::scale_transform(scale), inst_trafo.get_mirror_matrix())); + } + else { + if (transformation_type.world()) { + const Transform3d scale_matrix = Geometry::scale_transform(scale); + const Transform3d offset_matrix = (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) ? + // non-constrained scaling - add offset to scale around selection center + Geometry::translation_transform(m_cache.dragging_center + scale_matrix * (inst_trafo.get_offset() - m_cache.dragging_center)) : + // constrained scaling - add offset to keep constraint + Geometry::translation_transform(translation) * inst_trafo.get_offset_matrix(); + v.set_instance_transformation(offset_matrix * scale_matrix * inst_trafo.get_matrix_no_offset()); + } + else if (transformation_type.local()) { + const Transform3d scale_matrix = Geometry::scale_transform(scale); + Vec3d offset; + if (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) { + // non-constrained scaling - add offset to scale around selection center + offset = inst_trafo.get_matrix_no_offset().inverse() * (inst_trafo.get_offset() - m_cache.dragging_center); + offset = inst_trafo.get_matrix_no_offset() * (scale_matrix * offset - offset); + } + else + // constrained scaling - add offset to keep constraint + offset = translation; + + v.set_instance_transformation(Geometry::translation_transform(offset) * inst_trafo.get_matrix() * scale_matrix); + } + else + assert(false); + } + } + else { + if (transformation_type.absolute()) { + assert(transformation_type.local()); + const Geometry::Transformation& volume_trafo = volume_data.get_volume_transform(); + v.set_volume_transformation(Geometry::assemble_transform(volume_trafo.get_offset_matrix(), volume_trafo.get_rotation_matrix(), + Geometry::scale_transform(scale), volume_trafo.get_mirror_matrix())); + } + else + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(scale)); + } + } + +#if !DISABLE_INSTANCES_SYNCH + if (m_mode == Instance) + synchronize_unselected_instances(SyncRotationType::NONE); + else if (m_mode == Volume) + synchronize_unselected_volumes(); +#endif // !DISABLE_INSTANCES_SYNCH + + ensure_on_bed(); + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} +#else void Selection::translate(unsigned int object_idx, const Vec3d& displacement) { if (!m_valid) @@ -1420,7 +1461,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) this->set_bounding_boxes_dirty(); } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) { @@ -1431,7 +1472,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co GLVolume& v = *(*m_volumes)[i]; if (v.object_idx() == (int)object_idx && v.instance_idx() == (int)instance_idx) #if ENABLE_TRANSFORMATIONS_BY_MATRICES - v.set_instance_transformation(Geometry::assemble_transform(displacement) * v.get_instance_transformation().get_matrix()); + v.set_instance_transformation(Geometry::translation_transform(displacement) * v.get_instance_transformation().get_matrix()); #else v.set_instance_offset(v.get_instance_offset() + displacement); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -1468,7 +1509,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co continue; #if ENABLE_TRANSFORMATIONS_BY_MATRICES - v.set_instance_transformation(Geometry::assemble_transform(displacement) * v.get_instance_transformation().get_matrix()); + v.set_instance_transformation(Geometry::translation_transform(displacement) * v.get_instance_transformation().get_matrix()); #else v.set_instance_offset(v.get_instance_offset() + displacement); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -2770,7 +2811,11 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, GLS void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) #endif // ENABLE_GL_SHADERS_ATTRIBUTES { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const bool uniform_scale = wxGetApp().obj_manipul()->get_uniform_scaling(); +#else const bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_GL_SHADERS_ATTRIBUTES auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis, GLShaderProgram& shader, const Transform3d& matrix) { @@ -3380,5 +3425,27 @@ void Selection::paste_objects_from_clipboard() #endif /* _DEBUG */ } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, + const Transform3d& transform) +{ + const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); + const Geometry::Transformation& volume_trafo = volume_data.get_volume_transform(); + if (transformation_type.world()) { + const Transform3d inst_matrix_no_offset = inst_trafo.get_matrix_no_offset(); + const Transform3d new_volume_matrix = inst_matrix_no_offset.inverse() * transform * inst_matrix_no_offset; + volume.set_volume_transformation(volume_trafo.get_offset_matrix() * new_volume_matrix * volume_trafo.get_matrix_no_offset()); + } + else if (transformation_type.instance()) + volume.set_volume_transformation(volume_trafo.get_offset_matrix() * transform * volume_trafo.get_matrix_no_offset()); + else if (transformation_type.local()) { + const Geometry::Transformation trafo(transform); + volume.set_volume_transformation(trafo.get_offset_matrix() * volume_trafo.get_matrix() * trafo.get_matrix_no_offset()); + } + else + assert(false); +} +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index c9f0eb7c6..bd9c9216c 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -336,7 +336,9 @@ public: VolumeNotAxisAligned_Instance, MultipleSelection, }; +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES bool requires_uniform_scale(EUniformScaleRequiredReason* reason = nullptr) const; +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #else bool requires_uniform_scale() const; #endif // ENABLE_WORLD_COORDINATE @@ -365,7 +367,11 @@ public: void setup_cache(); #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void translate(const Vec3d& displacement, TransformationType transformation_type); +#else void translate(const Vec3d& displacement, ECoordinatesType type = ECoordinatesType::World); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else void translate(const Vec3d& displacement, bool local = false); #endif // ENABLE_WORLD_COORDINATE @@ -374,6 +380,9 @@ public: void scale(const Vec3d& scale, TransformationType transformation_type); void scale_to_fit_print_volume(const BuildVolume& volume); void mirror(Axis axis); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_TRANSFORMATIONS_BY_MATRICES void translate(unsigned int object_idx, const Vec3d& displacement); @@ -468,6 +477,11 @@ private: void paste_volumes_from_clipboard(); void paste_objects_from_clipboard(); + +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, + const Transform3d& transform); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES }; } // namespace GUI From eeb81b1ef8d3e34d4b8cd755f2bb9bddde714a54 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 11 May 2022 11:54:01 +0200 Subject: [PATCH 126/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Let reset buttons in object manipulator to be always visible when needed, no matter what is the current selected reference system --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 871c90c51..141c34c9b 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -882,15 +882,21 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_drop_to_bed = false; #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { +#else if ((m_coordinates_type == ECoordinatesType::World && selection.is_single_full_instance()) || (m_coordinates_type == ECoordinatesType::Instance && selection.is_single_volume_or_modifier())) { +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() : get_volume_min_z(*selection.get_first_volume()); show_drop_to_bed = std::abs(min_z) > EPSILON; +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES } if (m_coordinates_type == ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES const GLVolume* volume = selection.get_first_volume(); Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); From 19712749c3eedaed1fc25d10e1ec6f66571c3607 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 12 May 2022 09:41:01 +0200 Subject: [PATCH 127/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Added reset button to remove skew, when detected, in object manipulator panel Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 24 +++++++ src/libslic3r/Geometry.hpp | 1 + src/slic3r/GUI/GLCanvas3D.cpp | 50 ++++++++++++++ src/slic3r/GUI/GLCanvas3D.hpp | 6 ++ src/slic3r/GUI/GUI_ObjectManipulation.cpp | 83 +++++++++++++++++++++++ src/slic3r/GUI/GUI_ObjectManipulation.hpp | 13 +++- src/slic3r/GUI/Plater.cpp | 3 + src/slic3r/GUI/Selection.cpp | 42 +++++++++++- src/slic3r/GUI/Selection.hpp | 1 + 9 files changed, 218 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 61217f4a1..a611e10c0 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -702,6 +702,30 @@ void Transformation::reset() } #if ENABLE_TRANSFORMATIONS_BY_MATRICES +void Transformation::reset_skew() +{ + Matrix3d rotation; + Matrix3d scale; + m_matrix.computeRotationScaling(&rotation, &scale); + + const double average_scale = std::cbrt(scale(0, 0) * scale(1, 1) * scale(2, 2)); + + scale(0, 0) = average_scale; + scale(1, 1) = average_scale; + scale(2, 2) = average_scale; + + scale(0, 1) = 0.0; + scale(0, 2) = 0.0; + scale(1, 0) = 0.0; + scale(1, 2) = 0.0; + scale(2, 0) = 0.0; + scale(2, 1) = 0.0; + + const Vec3d offset = get_offset(); + m_matrix = rotation * scale; + m_matrix.translation() = offset; +} + Transform3d Transformation::get_matrix_no_offset() const { Transformation copy(*this); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index bbca3a5e3..0d804c52c 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -496,6 +496,7 @@ public: void reset_rotation() { set_rotation(Vec3d::Zero()); } void reset_scaling_factor() { set_scaling_factor(Vec3d::Ones()); } void reset_mirror() { set_mirror(Vec3d::Ones()); } + void reset_skew(); const Transform3d& get_matrix() const { return m_matrix; } Transform3d get_matrix_no_offset() const; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8a2599866..45ef3c002 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1101,6 +1101,9 @@ wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +wxDEFINE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); @@ -4098,9 +4101,17 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); +#else model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES else if (selection_mode == Selection::Volume) +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); +#else model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES model_object->invalidate_bounding_box(); } @@ -4124,6 +4135,45 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) m_dirty = true; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +void GLCanvas3D::do_reset_skew(const std::string& snapshot_type) +{ + if (m_model == nullptr) + return; + + if (!snapshot_type.empty()) + wxGetApp().plater()->take_snapshot(_(snapshot_type)); + + std::set> done; // keeps track of modified instances + + Selection::EMode selection_mode = m_selection.get_mode(); + const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); + + for (unsigned int id : idxs) { + const GLVolume* v = m_volumes.volumes[id]; + int object_idx = v->object_idx(); + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) + continue; + + int instance_idx = v->instance_idx(); + int volume_idx = v->volume_idx(); + + done.insert(std::pair(object_idx, instance_idx)); + + ModelObject* model_object = m_model->objects[object_idx]; + if (model_object != nullptr) { + model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); + model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); + model_object->invalidate_bounding_box(); + } + } + + post_event(SimpleEvent(EVT_GLCANVAS_RESET_SKEW)); + + m_dirty = true; +} +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + void GLCanvas3D::update_gizmos_on_off_state() { set_as_dirty(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 1876500e2..2daa10321 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -156,6 +156,9 @@ wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +wxDECLARE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); @@ -807,6 +810,9 @@ public: void do_rotate(const std::string& snapshot_type); void do_scale(const std::string& snapshot_type); void do_mirror(const std::string& snapshot_type); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void do_reset_skew(const std::string& snapshot_type); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void update_gizmos_on_off_state(); void reset_all_gizmos() { m_gizmos.reset_all_states(); } diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 141c34c9b..c82e06d7d 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -481,6 +481,25 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_skew_label = new wxStaticText(parent, wxID_ANY, _L("Skew")); + m_main_grid_sizer->Add(m_skew_label, 1, wxEXPAND); + + m_reset_skew_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); + m_reset_skew_button->SetToolTip(_L("Reset skew")); + m_reset_skew_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { + selection.setup_cache(); + selection.reset_skew(); + canvas->do_reset_skew(L("Reset skew")); + UpdateAndShow(true); + } + }); + m_main_grid_sizer->Add(m_reset_skew_button); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + m_check_inch = new wxCheckBox(parent, wxID_ANY, _L("Inches")); m_check_inch->SetFont(wxGetApp().normal_font()); @@ -880,6 +899,9 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_rotation = false; bool show_scale = false; bool show_drop_to_bed = false; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + bool show_skew = false; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE #if ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -898,8 +920,14 @@ void ObjectManipulation::update_reset_buttons_visibility() if (m_coordinates_type == ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES const GLVolume* volume = selection.get_first_volume(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + Transform3d rotation = Transform3d::Identity(); + Transform3d scale = Transform3d::Identity(); + Geometry::Transformation skew; +#else Vec3d rotation = Vec3d::Zero(); Vec3d scale = Vec3d::Ones(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { const GLVolume* volume = selection.get_first_volume(); @@ -909,27 +937,72 @@ void ObjectManipulation::update_reset_buttons_visibility() #endif // ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance()) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& trafo = volume->get_instance_transformation(); + rotation = trafo.get_rotation_matrix(); + scale = trafo.get_scaling_factor_matrix(); + if (trafo.has_skew()) + // the instance transform contains skew + skew = trafo; + else { + // the world transform contains skew + 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; + } + } + } +#else rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_WORLD_COORDINATE min_z = selection.get_scaled_instance_bounding_box().min.z(); #endif // !ENABLE_WORLD_COORDINATE } else { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + const Geometry::Transformation& trafo = volume->get_volume_transformation(); + rotation = trafo.get_rotation_matrix(); + scale = trafo.get_scaling_factor_matrix(); + if (trafo.has_skew()) + // the volume transform contains skew + skew = trafo; + else { + // the world transform contains skew + const Geometry::Transformation world_trafo(volume->world_matrix()); + if (world_trafo.has_skew()) + skew = world_trafo; + } +#else rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_WORLD_COORDINATE min_z = get_volume_min_z(*volume); #endif // !ENABLE_WORLD_COORDINATE } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + 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()); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_WORLD_COORDINATE show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; #endif // !ENABLE_WORLD_COORDINATE } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed, show_skew] { +#else wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] { +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // There is a case (under OSX), when this function is called after the Manipulation panel is hidden // So, let check if Manipulation panel is still shown for this moment if (!this->IsShown()) @@ -937,6 +1010,10 @@ void ObjectManipulation::update_reset_buttons_visibility() m_reset_rotation_button->Show(show_rotation); m_reset_scale_button->Show(show_scale); m_drop_to_bed_button->Show(show_drop_to_bed); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_reset_skew_button->Show(show_skew); + m_skew_label->Show(show_skew); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time Sidebar& panel = wxGetApp().sidebar(); @@ -1423,6 +1500,9 @@ void ObjectManipulation::msw_rescale() m_mirror_bitmap_hidden.msw_rescale(); m_reset_scale_button->msw_rescale(); m_reset_rotation_button->msw_rescale(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_reset_skew_button->msw_rescale(); +#endif /// ENABLE_TRANSFORMATIONS_BY_MATRICES m_drop_to_bed_button->msw_rescale(); m_lock_bnt->msw_rescale(); @@ -1462,6 +1542,9 @@ void ObjectManipulation::sys_color_changed() m_mirror_bitmap_hidden.msw_rescale(); m_reset_scale_button->msw_rescale(); m_reset_rotation_button->msw_rescale(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_reset_skew_button->msw_rescale(); +#endif /// ENABLE_TRANSFORMATIONS_BY_MATRICES m_drop_to_bed_button->msw_rescale(); m_lock_bnt->msw_rescale(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 289485dad..696f75128 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -120,9 +120,12 @@ private: wxStaticText* m_empty_str = nullptr; // Non-owning pointers to the reset buttons, so we can hide and show them. - ScalableButton* m_reset_scale_button = nullptr; - ScalableButton* m_reset_rotation_button = nullptr; - ScalableButton* m_drop_to_bed_button = nullptr; + ScalableButton* m_reset_scale_button{ nullptr }; + ScalableButton* m_reset_rotation_button{ nullptr }; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + ScalableButton* m_reset_skew_button{ nullptr }; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + ScalableButton* m_drop_to_bed_button{ nullptr }; wxCheckBox* m_check_inch {nullptr}; @@ -176,6 +179,10 @@ private: wxFlexGridSizer* m_main_grid_sizer; wxFlexGridSizer* m_labels_grid_sizer; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + wxStaticText* m_skew_label{ nullptr }; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + // sizers, used for msw_rescale wxBoxSizer* m_word_local_combo_sizer; std::vector m_rescalable_sizers; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c7ddf8f78..c9cc7fc22 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2085,6 +2085,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); }); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + view3D_canvas->Bind(EVT_GLCANVAS_RESET_SKEW, [this](SimpleEvent&) { update(); }); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event& evt) { this->sidebar->enable_buttons(evt.data); }); view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 33ecd2412..346d0fe9b 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1347,8 +1347,6 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation if (!m_valid) return; - std::cout << "Selection::scale_and_translate: " << to_string(scale) << " - " << to_string(translation) << "\n"; - for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; @@ -1412,6 +1410,46 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } + +void Selection::reset_skew() +{ + if (!m_valid) + return; + + for (unsigned int i : m_list) { + GLVolume& v = *(*m_volumes)[i]; + const VolumeCache& volume_data = m_cache.volumes_data[i]; + const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); + const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform(); + if (m_mode == Instance && inst_trafo.has_skew()) { + Geometry::Transformation trafo = inst_trafo; + trafo.reset_skew(); + v.set_instance_transformation(trafo); + } + else if (m_mode == Volume && vol_trafo.has_skew()) { + Geometry::Transformation trafo = vol_trafo; + trafo.reset_skew(); + v.set_volume_transformation(trafo); + } + else { + const Geometry::Transformation world_trafo = inst_trafo * vol_trafo; + if (world_trafo.has_skew()) { + if (m_mode == Instance) { + // TODO + int a = 0; + } + else { + // TODO + int a = 0; + } + } + } + } + + ensure_on_bed(); + set_bounding_boxes_dirty(); + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +} #else void Selection::translate(unsigned int object_idx, const Vec3d& displacement) { diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index bd9c9216c..0be33b1e8 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -382,6 +382,7 @@ public: void mirror(Axis axis); #if ENABLE_TRANSFORMATIONS_BY_MATRICES void scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type); + void reset_skew(); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_TRANSFORMATIONS_BY_MATRICES From fd45d0eeed99df3f9c5f0aa50e55db2d667cbaad Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 12 May 2022 11:26:44 +0200 Subject: [PATCH 128/169] Tech ENABLE_WORLD_COORDINATE_SHOW_AXES - Fixed bed axes visualization --- src/slic3r/GUI/CoordAxes.cpp | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp index 75038e23c..88f4b6c86 100644 --- a/src/slic3r/GUI/CoordAxes.cpp +++ b/src/slic3r/GUI/CoordAxes.cpp @@ -47,25 +47,14 @@ void CoordAxes::render(float emission_factor) m_arrow.init_from(stilized_arrow(16, m_tip_radius, m_tip_length, m_stem_radius, m_stem_length)); GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); -#if ENABLE_GL_SHADERS_ATTRIBUTES - bool shader_differs = (curr_shader == nullptr || curr_shader->get_name() != "gouraud_light_attr"); -#else - bool shader_differs = (curr_shader == nullptr || curr_shader->get_name() != "gouraud_light"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES - -#if ENABLE_GL_SHADERS_ATTRIBUTES - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light_attr"); -#else GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES if (shader == nullptr) return; - if (shader_differs) { - if (curr_shader != nullptr) - curr_shader->stop_using(); - shader->start_using(); - } + if (curr_shader != nullptr) + curr_shader->stop_using(); + + shader->start_using(); shader->set_uniform("emission_factor", emission_factor); // x axis @@ -104,11 +93,9 @@ void CoordAxes::render(float emission_factor) render_axis(Geometry::assemble_transform(m_origin).cast()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES - if (shader_differs) { - shader->stop_using(); - if (curr_shader != nullptr) - curr_shader->start_using(); - } + shader->stop_using(); + if (curr_shader != nullptr) + curr_shader->start_using(); } } // GUI From 3fcfd04921a4eed5fca2b7e16d46eb92fd0de26e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 12 May 2022 12:07:27 +0200 Subject: [PATCH 129/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Fixed translation of volumes in local coordinate system Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 346d0fe9b..b6d9cfbd4 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -783,8 +783,11 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor else assert(false); } - else - transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(displacement)); + else { + const Vec3d offset = transformation_type.local() ? + volume_data.get_volume_transform().get_rotation_matrix() * displacement : displacement; + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(offset)); + } } #if !DISABLE_INSTANCES_SYNCH From 243985173e70c189ad9a86eefaaea0757d9749cb Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 12 May 2022 14:33:41 +0200 Subject: [PATCH 130/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Allow for relative rotations only when using the object manipulator panel --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 24 ++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index c82e06d7d..8af365457 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -677,9 +677,16 @@ void ObjectManipulation::update_settings_value(const Selection& selection) else { #if ENABLE_WORLD_COORDINATE m_new_move_label_string = L("Translate"); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_new_rotate_label_string = L("Rotate"); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_position = Vec3d::Zero(); #endif // ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_new_rotation = Vec3d::Zero(); +#else m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES 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; } @@ -728,8 +735,15 @@ void ObjectManipulation::update_settings_value(const Selection& selection) } else if (is_local_coordinates()) { m_new_move_label_string = L("Translate"); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_new_rotate_label_string = L("Rotate"); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_position = Vec3d::Zero(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + m_new_rotation = Vec3d::Zero(); +#else m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()); } @@ -1178,19 +1192,27 @@ void ObjectManipulation::change_rotation_value(int axis, double value) GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); - TransformationType transformation_type(TransformationType::World_Relative_Joint); #if ENABLE_WORLD_COORDINATE +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + TransformationType transformation_type; + transformation_type.set_relative(); +#else + TransformationType transformation_type(TransformationType::World_Relative_Joint); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (selection.is_single_full_instance()) transformation_type.set_independent(); if (is_local_coordinates()) { transformation_type.set_local(); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES transformation_type.set_absolute(); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES } if (is_instance_coordinates()) transformation_type.set_instance(); #else + TransformationType transformation_type(TransformationType::World_Relative_Joint); if (selection.is_single_full_instance() || selection.requires_local_axes()) transformation_type.set_independent(); if (selection.is_single_full_instance() && ! m_world_coordinates) { From 9efeb0b9e594b250987f2f2ff589cdc75b898d18 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 13 May 2022 09:27:16 +0200 Subject: [PATCH 131/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Fixed missing update of object manipulator panel after selecting an object in the 3D scene --- src/slic3r/GUI/GLCanvas3D.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 2daa10321..9cf6501fc 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -742,7 +742,11 @@ public: void update_volumes_colors_by_extruder(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + bool is_dragging() const { return m_gizmos.is_dragging() || (m_moving && !m_mouse.scene_position.isApprox(m_mouse.drag.start_position_3D)); } +#else bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void render(); // printable_only == false -> render also non printable volumes as grayed From 9062a74c5c29c9f124d172838ade3cd6088b035d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 13 May 2022 09:46:09 +0200 Subject: [PATCH 132/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Fixed scale reset --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 8af365457..6b11b46bf 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -460,13 +460,24 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); m_reset_scale_button->SetToolTip(_L("Reset scale")); m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset scale")); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - bool old_uniform = m_uniform_scale; - m_uniform_scale = true; - change_scale_value(0, 100.0); - m_uniform_scale = old_uniform; + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + if (selection.is_single_volume_or_modifier()) + const_cast(selection.get_first_volume())->set_volume_scaling_factor(Vec3d::Ones()); + else if (selection.is_single_full_instance()) { + for (unsigned int idx : selection.get_volume_idxs()) { + const_cast(selection.get_volume(idx))->set_instance_scaling_factor(Vec3d::Ones()); + } + } + else + return; + + canvas->do_scale(L("Reset scale")); + + UpdateAndShow(true); #else + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Reset scale")); change_scale_value(0, 100.); change_scale_value(1, 100.); change_scale_value(2, 100.); From 882a5ffec5ced2cddcf1078ed304b160562e4cfa Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 13 May 2022 12:07:33 +0200 Subject: [PATCH 133/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Fixed uniform scale using object manipulator panel Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 34 +++++++++++++++++------ src/slic3r/GUI/Selection.cpp | 2 -- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 6b11b46bf..0317d0d72 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -468,7 +468,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : else if (selection.is_single_full_instance()) { for (unsigned int idx : selection.get_volume_idxs()) { const_cast(selection.get_volume(idx))->set_instance_scaling_factor(Vec3d::Ones()); - } + } } else return; @@ -674,16 +674,18 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (m_world_coordinates) { #endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_scale_label_string = L("Scale"); #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = Vec3d(100.0, 100.0, 100.0); #else - m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.0; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED + m_new_scale = m_new_size.cwiseQuotient(selection.get_unscaled_instance_bounding_box().size()) * 100.0; +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES } else { #if ENABLE_WORLD_COORDINATE @@ -733,16 +735,22 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = offset; m_new_rotate_label_string = L("Rotate"); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_scale_label_string = L("Scale"); #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = Vec3d(100.0, 100.0, 100.0); +#else +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + 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; #else m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (is_local_coordinates()) { m_new_move_label_string = L("Translate"); @@ -762,17 +770,23 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); m_new_rotate_label_string = L("Rotate"); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_scale_label_string = L("Scale"); #endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES 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(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = Vec3d(100.0, 100.0, 100.0); +#else +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + 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; #else m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES } #else m_new_scale = volume->get_volume_scaling_factor() * 100.0; @@ -1325,8 +1339,10 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const else if (is_instance_coordinates()) transformation_type.set_instance(); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES if (!is_local_coordinates()) transformation_type.set_relative(); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #else if (!is_world_coordinates()) transformation_type.set_local(); @@ -1376,8 +1392,10 @@ void ObjectManipulation::do_size(int axis, const Vec3d& scale) const else if (is_instance_coordinates()) transformation_type.set_instance(); +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES if (!is_local_coordinates()) transformation_type.set_relative(); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_TRANSFORMATIONS_BY_MATRICES Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index b6d9cfbd4..a3161e786 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1357,7 +1357,6 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation if (m_mode == Instance) { assert(is_from_fully_selected_instance(i)); if (transformation_type.absolute()) { - assert(transformation_type.local()); assert(transformation_type.joint()); v.set_instance_transformation(Geometry::assemble_transform(inst_trafo.get_offset_matrix(), inst_trafo.get_rotation_matrix(), Geometry::scale_transform(scale), inst_trafo.get_mirror_matrix())); @@ -1392,7 +1391,6 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation } else { if (transformation_type.absolute()) { - assert(transformation_type.local()); const Geometry::Transformation& volume_trafo = volume_data.get_volume_transform(); v.set_volume_transformation(Geometry::assemble_transform(volume_trafo.get_offset_matrix(), volume_trafo.get_rotation_matrix(), Geometry::scale_transform(scale), volume_trafo.get_mirror_matrix())); From 7c86cf84a39cd868a5b20f28d61d424c2e2cd431 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 13 May 2022 12:27:37 +0200 Subject: [PATCH 134/169] Tech ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX merged into tech ENABLE_WORLD_COORDINATE Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 4 +--- src/slic3r/GUI/Selection.cpp | 28 ++++++++++++++-------------- src/slic3r/GUI/Selection.hpp | 4 ++-- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index c44b914b4..a9de30fb2 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -72,9 +72,7 @@ // Enable editing volumes transformation in world coordinates and instances in local coordinates #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them -#define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (1 && ENABLE_WORLD_COORDINATE) -// Enable rendering the selection bounding box in the current reference system -#define ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX (1 && ENABLE_WORLD_COORDINATE) +#define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (0 && ENABLE_WORLD_COORDINATE) // Enable showing the axes of the current reference system when sidebar hints are active #define ENABLE_WORLD_COORDINATE_SHOW_AXES (1 && ENABLE_WORLD_COORDINATE) // Enable alternate implementation of manipulating scale for instances and volumes diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index a3161e786..1ce1f899c 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1725,7 +1725,7 @@ void Selection::render(float scale_factor) m_scale_factor = scale_factor; // render cumulative bounding box of selected volumes #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE BoundingBoxf3 box; Transform3d trafo; const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); @@ -1762,7 +1762,7 @@ void Selection::render(float scale_factor) render_bounding_box(box, trafo, ColorRGB::WHITE()); #else render_bounding_box(get_bounding_box(), ColorRGB::WHITE()); -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE #else render_selected_volumes(); #endif // ENABLE_LEGACY_OPENGL_REMOVAL @@ -2477,11 +2477,11 @@ void Selection::render_synchronized_volumes() float color[3] = { 1.0f, 1.0f, 0.0f }; #endif // !ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); BoundingBoxf3 box; Transform3d trafo; -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE for (unsigned int i : m_list) { const GLVolume& volume = *(*m_volumes)[i]; @@ -2496,7 +2496,7 @@ void Selection::render_synchronized_volumes() continue; #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE if (coordinates_type == ECoordinatesType::World) { box = v.transformed_convex_hull_bounding_box(); trafo = Transform3d::Identity(); @@ -2512,7 +2512,7 @@ void Selection::render_synchronized_volumes() render_bounding_box(box, trafo, ColorRGB::YELLOW()); #else render_bounding_box(v.transformed_convex_hull_bounding_box(), ColorRGB::YELLOW()); -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE #else render_bounding_box(v.transformed_convex_hull_bounding_box(), color); #endif // ENABLE_LEGACY_OPENGL_REMOVAL @@ -2521,11 +2521,11 @@ void Selection::render_synchronized_volumes() } #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE void Selection::render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const ColorRGB& color) #else void Selection::render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color) -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE { #else void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) const @@ -2629,32 +2629,32 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con if (shader == nullptr) return; -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo.data())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE shader->set_uniform("view_model_matrix", camera.get_view_matrix() * trafo); #else shader->set_uniform("view_model_matrix", camera.get_view_matrix()); -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE shader->set_uniform("projection_matrix", camera.get_projection_matrix()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES m_box.set_color(to_rgba(color)); m_box.render(); shader->stop_using(); -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE #else ::glBegin(GL_LINES); diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 0be33b1e8..6f42f622c 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -436,11 +436,11 @@ private: void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); } void render_synchronized_volumes(); #if ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#if ENABLE_WORLD_COORDINATE void render_bounding_box(const BoundingBoxf3& box, const Transform3d& trafo, const ColorRGB& color); #else void render_bounding_box(const BoundingBoxf3& box, const ColorRGB& color); -#endif // ENABLE_COORDINATE_DEPENDENT_SELECTION_BOX +#endif // ENABLE_WORLD_COORDINATE #else void render_selected_volumes() const; void render_bounding_box(const BoundingBoxf3& box, float* color) const; From ebb9a4aadbba4bf42ad85b6593bd8d4da45568f2 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 13 May 2022 14:27:18 +0200 Subject: [PATCH 135/169] Tech ENABLE_WORLD_COORDINATE_SHOW_AXES merged into tech ENABLE_WORLD_COORDINATE Fixed conflicts during rebase with master --- src/libslic3r/Technologies.hpp | 2 - src/slic3r/GUI/3DBed.cpp | 12 ++--- src/slic3r/GUI/3DBed.hpp | 12 ++--- src/slic3r/GUI/CoordAxes.cpp | 4 +- src/slic3r/GUI/CoordAxes.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 10 +++- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 29 ++++++++++-- src/slic3r/GUI/Selection.cpp | 65 +++++++++----------------- src/slic3r/GUI/Selection.hpp | 8 +--- 9 files changed, 74 insertions(+), 72 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a9de30fb2..e8f9fc965 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -73,8 +73,6 @@ #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them #define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (0 && ENABLE_WORLD_COORDINATE) -// Enable showing the axes of the current reference system when sidebar hints are active -#define ENABLE_WORLD_COORDINATE_SHOW_AXES (1 && ENABLE_WORLD_COORDINATE) // Enable alternate implementation of manipulating scale for instances and volumes #define ENABLE_WORLD_COORDINATE_SCALE_REVISITED (1 && ENABLE_WORLD_COORDINATE) // Enable implementation of Geometry::Transformation using matrices only diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 7b34d59bf..503c35a9b 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -102,7 +102,7 @@ const float* GeometryBuffer::get_vertices_data() const } #endif // !ENABLE_LEGACY_OPENGL_REMOVAL -#if !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if !ENABLE_WORLD_COORDINATE const float Bed3D::Axes::DefaultStemRadius = 0.5f; const float Bed3D::Axes::DefaultStemLength = 25.0f; const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius; @@ -180,7 +180,7 @@ void Bed3D::Axes::render() glsafe(::glDisable(GL_DEPTH_TEST)); } -#endif // !ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // !ENABLE_WORLD_COORDINATE bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) { @@ -343,11 +343,11 @@ BoundingBoxf3 Bed3D::calc_extended_bounding_box() const out.max.z() = 0.0; // extend to contain axes out.merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones()); -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE out.merge(out.min + Vec3d(-m_axes.get_tip_radius(), -m_axes.get_tip_radius(), out.max.z())); #else out.merge(out.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, out.max.z())); -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE // extend to contain model, if any BoundingBoxf3 model_bb = m_model.get_bounding_box(); if (model_bb.defined) { @@ -545,7 +545,7 @@ std::tuple Bed3D::detect_type(const Point void Bed3D::render_axes() { if (m_build_volume.valid()) -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE #if ENABLE_GL_SHADERS_ATTRIBUTES m_axes.render(Transform3d::Identity(), 0.25f); #else @@ -553,7 +553,7 @@ void Bed3D::render_axes() #endif // ENABLE_GL_SHADERS_ATTRIBUTES #else m_axes.render(); -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE } #if ENABLE_GL_SHADERS_ATTRIBUTES diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 61f01f021..3c48a3901 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -3,11 +3,11 @@ #include "GLTexture.hpp" #include "3DScene.hpp" -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE #include "CoordAxes.hpp" #else #include "GLModel.hpp" -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE #include "libslic3r/BuildVolume.hpp" #if ENABLE_LEGACY_OPENGL_REMOVAL @@ -48,7 +48,7 @@ public: class Bed3D { -#if !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if !ENABLE_WORLD_COORDINATE class Axes { public: @@ -72,7 +72,7 @@ class Bed3D float get_total_length() const { return m_stem_length + DefaultTipLength; } void render(); }; -#endif // !ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // !ENABLE_WORLD_COORDINATE public: enum class Type : unsigned char @@ -113,11 +113,11 @@ private: #if !ENABLE_LEGACY_OPENGL_REMOVAL unsigned int m_vbo_id{ 0 }; #endif // !ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE CoordAxes m_axes; #else Axes m_axes; -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE float m_scale_factor{ 1.0f }; diff --git a/src/slic3r/GUI/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp index 88f4b6c86..edd1d4f03 100644 --- a/src/slic3r/GUI/CoordAxes.cpp +++ b/src/slic3r/GUI/CoordAxes.cpp @@ -10,7 +10,7 @@ #include -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE namespace Slic3r { namespace GUI { @@ -101,4 +101,4 @@ void CoordAxes::render(float emission_factor) } // GUI } // Slic3r -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/CoordAxes.hpp b/src/slic3r/GUI/CoordAxes.hpp index d2e38e184..9e49d1391 100644 --- a/src/slic3r/GUI/CoordAxes.hpp +++ b/src/slic3r/GUI/CoordAxes.hpp @@ -1,7 +1,7 @@ #ifndef slic3r_CoordAxes_hpp_ #define slic3r_CoordAxes_hpp_ -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE #include "GLModel.hpp" namespace Slic3r { @@ -59,6 +59,6 @@ public: } // GUI } // Slic3r -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE #endif // slic3r_CoordAxes_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 19422e167..04ccfb4c8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -267,7 +267,11 @@ void GLGizmoMove3D::on_render() #if ENABLE_GL_SHADERS_ATTRIBUTES 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()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES @@ -327,7 +331,11 @@ void GLGizmoMove3D::on_render() #if ENABLE_GL_SHADERS_ATTRIBUTES const Camera& camera = wxGetApp().plater()->get_camera(); - shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); +#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()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index af5b3adb2..58095375f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -357,9 +357,10 @@ void GLGizmoScale3D::on_render() else m_bounding_box = selection.get_bounding_box(); - Vec3d offset_x = offsets_transform * (Offset * Vec3d::UnitX()); - Vec3d offset_y = offsets_transform * (Offset * Vec3d::UnitY()); - Vec3d offset_z = offsets_transform * (Offset * Vec3d::UnitZ()); + 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); bool ctrl_down = m_dragging && m_starting.ctrl_down || !m_dragging && wxGetKeyState(WXK_CONTROL); #endif // ENABLE_WORLD_COORDINATE @@ -397,8 +398,6 @@ void GLGizmoScale3D::on_render() m_grabbers[9].color = (use_constrain && m_hover_id == 7) ? CONSTRAINED_COLOR : m_highlight_color; #else // x axis - const Vec3d center = m_bounding_box.center(); - 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; @@ -457,7 +456,11 @@ void GLGizmoScale3D::on_render() shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES 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()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES if (m_grabbers[0].enabled && m_grabbers[1].enabled) @@ -504,7 +507,11 @@ void GLGizmoScale3D::on_render() shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES 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()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES render_grabbers_connection(0, 1, m_grabbers[0].color); @@ -537,7 +544,11 @@ void GLGizmoScale3D::on_render() shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES 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()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES render_grabbers_connection(2, 3, m_grabbers[2].color); @@ -570,7 +581,11 @@ void GLGizmoScale3D::on_render() shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES 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()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES render_grabbers_connection(4, 5, m_grabbers[4].color); @@ -603,7 +618,11 @@ void GLGizmoScale3D::on_render() shader->start_using(); #if ENABLE_GL_SHADERS_ATTRIBUTES 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()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES render_grabbers_connection(6, 7, m_drag_color); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 1ce1f899c..0212ed305 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -119,12 +119,12 @@ Selection::Selection() , m_scale_factor(1.0f) { this->set_bounding_boxes_dirty(); -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE m_axes.set_stem_radius(0.15f); m_axes.set_stem_length(3.0f); m_axes.set_tip_radius(0.45f); m_axes.set_tip_length(1.5f); -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE } @@ -1844,46 +1844,39 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #if ENABLE_GL_SHADERS_ATTRIBUTES const Transform3d base_matrix = Geometry::assemble_transform(get_bounding_box().center()); #endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE Transform3d orient_matrix = Transform3d::Identity(); #else glsafe(::glPushMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE const Vec3d center = get_bounding_box().center(); Vec3d axes_center = center; -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE if (!boost::starts_with(sidebar_field, "layer")) { #if ENABLE_GL_SHADERS_ATTRIBUTES shader->set_uniform("emission_factor", 0.05f); #endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE const Vec3d& center = get_bounding_box().center(); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE if (is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { #else if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) { #endif // ENABLE_WORLD_COORDINATE -#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE glsafe(::glTranslated(center.x(), center.y(), center.z())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE -#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES - Transform3d orient_matrix = Transform3d::Identity(); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES #if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); #else orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SHOW_AXES axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); -#else - glsafe(::glMultMatrixd(orient_matrix.data())); -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES #else if (!boost::starts_with(sidebar_field, "position")) { #if !ENABLE_GL_SHADERS_ATTRIBUTES @@ -1913,14 +1906,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #else else if (is_single_volume() || is_single_modifier()) { #endif // ENABLE_WORLD_COORDINATE -#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE glsafe(::glTranslated(center.x(), center.y(), center.z())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE if (!wxGetApp().obj_manipul()->is_world_coordinates()) { -#if !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES - Transform3d orient_matrix = Transform3d::Identity(); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE_SHOW_AXES if (wxGetApp().obj_manipul()->is_local_coordinates()) { const GLVolume* v = (*m_volumes)[*m_list.begin()]; #if ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -1928,9 +1918,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #else orient_matrix = v->get_instance_transformation().get_matrix(true, false, true, true) * v->get_volume_transformation().get_matrix(true, false, true, true); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SHOW_AXES axes_center = (*m_volumes)[*m_list.begin()]->world_matrix().translation(); -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES } else { #if ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -1938,15 +1926,8 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #else orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SHOW_AXES axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); } -#else - } -#if !ENABLE_GL_SHADERS_ATTRIBUTES - glsafe(::glMultMatrixd(orient_matrix.data())); -#endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES } #else #if ENABLE_GL_SHADERS_ATTRIBUTES @@ -1962,7 +1943,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #endif // ENABLE_WORLD_COORDINATE } else { -#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE if (requires_local_axes()) #if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); @@ -1975,16 +1956,16 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); glsafe(::glMultMatrixd(orient_matrix.data())); } -#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE_SHOW_AXES - } +#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE } + } #if ENABLE_LEGACY_OPENGL_REMOVAL if (!boost::starts_with(sidebar_field, "layer")) glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); #endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE if (!boost::starts_with(sidebar_field, "layer")) { shader->set_uniform("emission_factor", 0.1f); #if !ENABLE_GL_SHADERS_ATTRIBUTES @@ -1993,7 +1974,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) glsafe(::glMultMatrixd(orient_matrix.data())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES } -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_GL_SHADERS_ATTRIBUTES if (boost::starts_with(sidebar_field, "position")) @@ -2004,12 +1985,12 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) render_sidebar_scale_hints(sidebar_field, *shader, base_matrix * orient_matrix); else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field, *shader); -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE if (!boost::starts_with(sidebar_field, "layer")) { if (!wxGetApp().obj_manipul()->is_world_coordinates()) m_axes.render(Geometry::assemble_transform(axes_center) * orient_matrix, 0.25f); } -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE #else if (boost::starts_with(sidebar_field, "position")) render_sidebar_position_hints(sidebar_field); @@ -2020,7 +2001,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field); -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE if (!boost::starts_with(sidebar_field, "layer")) { glsafe(::glPopMatrix()); glsafe(::glPushMatrix()); @@ -2030,14 +2011,14 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) m_axes.render(0.25f); glsafe(::glPopMatrix()); } -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE #endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE #if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE #if !ENABLE_LEGACY_OPENGL_REMOVAL if (!boost::starts_with(sidebar_field, "layer")) diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 6f42f622c..b62db2e6e 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -4,13 +4,9 @@ #include "libslic3r/Geometry.hpp" #if ENABLE_WORLD_COORDINATE #include "GUI_Geometry.hpp" -#if ENABLE_WORLD_COORDINATE_SHOW_AXES #include "CoordAxes.hpp" #else #include "GLModel.hpp" -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES -#else -#include "GLModel.hpp" #endif // ENABLE_WORLD_COORDINATE #include @@ -238,9 +234,9 @@ private: GLModel m_vbo_sphere; #endif // ENABLE_RENDER_SELECTION_CENTER -#if ENABLE_WORLD_COORDINATE_SHOW_AXES +#if ENABLE_WORLD_COORDINATE CoordAxes m_axes; -#endif // ENABLE_WORLD_COORDINATE_SHOW_AXES +#endif // ENABLE_WORLD_COORDINATE GLModel m_arrow; GLModel m_curved_arrow; #if ENABLE_LEGACY_OPENGL_REMOVAL From e4fb142afcc8db7f2fa63084d5b107d62e570c2e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Fri, 13 May 2022 15:32:06 +0200 Subject: [PATCH 136/169] Tech ENABLE_WORLD_COORDINATE_SCALE_REVISITED merged into tech ENABLE_WORLD_COORDINATE Fixed conflicts during rebase with master --- src/libslic3r/Model.cpp | 4 +- src/libslic3r/Technologies.hpp | 2 - src/slic3r/GUI/GUI_ObjectManipulation.cpp | 91 ++----- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 6 +- src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmoBase.hpp | 306 +++++++++++----------- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 21 +- src/slic3r/GUI/Selection.cpp | 8 +- src/slic3r/GUI/Selection.hpp | 4 +- src/slic3r/GUI/wxExtensions.cpp | 4 +- 10 files changed, 202 insertions(+), 252 deletions(-) diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 7fb7ed9f1..f3f31ed4f 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1425,11 +1425,11 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) assert(instance_idx < this->instances.size()); const Geometry::Transformation reference_trafo = this->instances[instance_idx]->get_transformation(); -#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if !ENABLE_WORLD_COORDINATE if (Geometry::is_rotation_ninety_degrees(reference_trafo.get_rotation())) // nothing to do, scaling in the world coordinate space is possible in the representation of Geometry::Transformation. return; -#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // !ENABLE_WORLD_COORDINATE bool left_handed = reference_trafo.is_left_handed(); bool has_mirrorring = ! reference_trafo.get_mirror().isApprox(Vec3d(1., 1., 1.)); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index e8f9fc965..250a45f3f 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -73,8 +73,6 @@ #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) // Enable showing world coordinates of volumes' offset relative to the instance containing them #define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (0 && ENABLE_WORLD_COORDINATE) -// Enable alternate implementation of manipulating scale for instances and volumes -#define ENABLE_WORLD_COORDINATE_SCALE_REVISITED (1 && ENABLE_WORLD_COORDINATE) // Enable implementation of Geometry::Transformation using matrices only #define ENABLE_TRANSFORMATIONS_BY_MATRICES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 0317d0d72..6fbc55511 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -675,17 +675,17 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); #if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE m_new_scale_label_string = L("Scale"); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES - m_new_scale = Vec3d(100.0, 100.0, 100.0); +#if ENABLE_WORLD_COORDINATE && !ENABLE_TRANSFORMATIONS_BY_MATRICES + m_new_scale = Vec3d(100.0, 100.0, 100.0); #else m_new_scale = m_new_size.cwiseQuotient(selection.get_unscaled_instance_bounding_box().size()) * 100.0; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE && !ENABLE_TRANSFORMATIONS_BY_MATRICES } else { #if ENABLE_WORLD_COORDINATE @@ -736,21 +736,15 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = offset; m_new_rotate_label_string = L("Rotate"); #if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_new_scale_label_string = L("Scale"); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES - m_new_scale = Vec3d(100.0, 100.0, 100.0); -#else #if ENABLE_TRANSFORMATIONS_BY_MATRICES 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; #else - m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_instance_transformation().get_matrix() * volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; + m_new_scale = Vec3d(100.0, 100.0, 100.0); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (is_local_coordinates()) { m_new_move_label_string = L("Translate"); @@ -771,22 +765,18 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = volume->get_volume_offset(); m_new_rotate_label_string = L("Rotate"); #if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE m_new_scale_label_string = L("Scale"); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES 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(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES - m_new_scale = Vec3d(100.0, 100.0, 100.0); -#else #if ENABLE_TRANSFORMATIONS_BY_MATRICES 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; #else - m_new_scale = m_new_size.cwiseProduct(volume->transformed_convex_hull_bounding_box(volume->get_volume_transformation().get_matrix(false, false, true, false)).size().cwiseInverse()) * 100.0; + m_new_scale = Vec3d(100.0, 100.0, 100.0); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED && !ENABLE_TRANSFORMATIONS_BY_MATRICES } #else m_new_scale = volume->get_volume_scaling_factor() * 100.0; @@ -861,40 +851,14 @@ void ObjectManipulation::update_if_dirty() #if ENABLE_WORLD_COORDINATE Selection::EUniformScaleRequiredReason reason; if (selection.requires_uniform_scale(&reason)) { -#else - if (selection.requires_uniform_scale()) { -#endif // ENABLE_WORLD_COORDINATE -#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED - m_lock_bnt->SetLock(true); -#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED -#if ENABLE_WORLD_COORDINATE - wxString tooltip; - if (selection.is_single_volume_or_modifier()) { -#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED - if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) - tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the instance local axes"); - else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_World) - tooltip = _L("You cannot use non-uniform scaling mode for parts non aligned with the printer axes"); -#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED - } - else if (selection.is_single_full_instance()) { -#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED - if (reason == Selection::EUniformScaleRequiredReason::InstanceNotAxisAligned_World) - tooltip = _L("You cannot use non-uniform scaling mode for instances non aligned with the printer axes"); - else if (reason == Selection::EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance) - tooltip = _L("You cannot use non-uniform scaling mode for instances containing non locally axis-aligned parts"); -#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED - } - else - tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection"); - + wxString tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection"); m_lock_bnt->SetToolTip(tooltip); #else + if (selection.requires_uniform_scale()) { + m_lock_bnt->SetLock(true); m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); -#endif // ENABLE_WORLD_COORDINATE -#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED m_lock_bnt->disable(); -#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE } else { #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES @@ -1313,11 +1277,11 @@ void ObjectManipulation::change_size_value(int axis, double value) selection.get_unscaled_instance_bounding_box().size() : wxGetApp().model().objects[selection.get_first_volume()->object_idx()]->raw_mesh_bounding_box().size(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE this->do_size(axis, size.cwiseQuotient(ref_size)); #else this->do_scale(axis, size.cwiseQuotient(ref_size)); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE m_cache.size = size; m_cache.size_rounded(axis) = DBL_MAX; @@ -1333,24 +1297,17 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED if (is_local_coordinates()) transformation_type.set_local(); else if (is_instance_coordinates()) transformation_type.set_instance(); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - if (!is_local_coordinates()) - transformation_type.set_relative(); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES -#else - if (!is_world_coordinates()) - transformation_type.set_local(); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED - #if ENABLE_TRANSFORMATIONS_BY_MATRICES Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; #else + if (!is_local_coordinates()) + transformation_type.set_relative(); + bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; @@ -1381,7 +1338,7 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); } -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE void ObjectManipulation::do_size(int axis, const Vec3d& scale) const { Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); @@ -1418,7 +1375,7 @@ void ObjectManipulation::do_size(int axis, const Vec3d& scale) const selection.scale(scaling_factor, transformation_type); wxGetApp().plater()->canvas3D()->do_scale(L("Set Size")); } -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE void ObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value) { @@ -1457,7 +1414,7 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) { const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE if (!use_uniform_scale) { #if !ENABLE_TRANSFORMATIONS_BY_MATRICES int res = selection.bake_transform_if_needed(); @@ -1477,12 +1434,8 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) #if ENABLE_TRANSFORMATIONS_BY_MATRICES set_dirty(); #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#else -#if ENABLE_WORLD_COORDINATE - if (selection.is_single_full_instance() && is_world_coordinates() && !use_uniform_scale) { #else if (selection.is_single_full_instance() && m_world_coordinates && !use_uniform_scale) { -#endif // ENABLE_WORLD_COORDINATE // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one const GLVolume* volume = selection.get_first_volume(); @@ -1513,7 +1466,7 @@ void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) } m_uniform_scale = use_uniform_scale; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#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 696f75128..e25e05f60 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -263,11 +263,9 @@ private: void change_scale_value(int axis, double value); void change_size_value(int axis, double value); void do_scale(int axis, const Vec3d &scale) const; -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED - void do_size(int axis, const Vec3d& scale) const; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED - #if ENABLE_WORLD_COORDINATE + void do_size(int axis, const Vec3d& scale) const; + void set_coordinates_type(const wxString& type_string); #endif // ENABLE_WORLD_COORDINATE }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index bee57ec6e..0810ddc97 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -335,7 +335,7 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { return true; } else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) { -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE do_stop_dragging(is_leaving); #else for (auto &grabber : m_grabbers) grabber.dragging = false; @@ -360,14 +360,14 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); // updates camera target constraints m_parent.refresh_camera_scene_box(); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE return true; } } return false; } -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE void GLGizmoBase::do_stop_dragging(bool perform_mouse_cleanup) { for (auto& grabber : m_grabbers) grabber.dragging = false; @@ -393,7 +393,7 @@ void GLGizmoBase::do_stop_dragging(bool perform_mouse_cleanup) // updates camera target constraints m_parent.refresh_camera_scene_box(); } -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE std::string GLGizmoBase::format(float value, unsigned int decimals) const { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index fff699479..95763f004 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -89,156 +89,156 @@ protected: static GLModel s_cone; #else GLModel m_cube; -#endif // ENABLE_GIZMO_GRABBER_REFACTOR - }; - -public: - enum EState - { - Off, - On, - Num_States - }; - - struct UpdateData - { - const Linef3& mouse_ray; - const Point& mouse_pos; - - UpdateData(const Linef3& mouse_ray, const Point& mouse_pos) - : mouse_ray(mouse_ray), mouse_pos(mouse_pos) - {} - }; - -protected: - GLCanvas3D& m_parent; - int m_group_id; // TODO: remove only for rotate - EState m_state; - int m_shortcut_key; - std::string m_icon_filename; - unsigned int m_sprite_id; - int m_hover_id; - bool m_dragging; - mutable std::vector m_grabbers; - ImGuiWrapper* m_imgui; - bool m_first_input_window_render; - CommonGizmosDataPool* m_c; -public: - GLGizmoBase(GLCanvas3D& parent, - const std::string& icon_filename, - unsigned int sprite_id); - virtual ~GLGizmoBase() = default; - - bool init() { return on_init(); } - - void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); } - void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); } - - std::string get_name(bool include_shortcut = true) const; - - EState get_state() const { return m_state; } - void set_state(EState state) { m_state = state; on_set_state(); } - - int get_shortcut_key() const { return m_shortcut_key; } - - const std::string& get_icon_filename() const { return m_icon_filename; } - - bool is_activable() const { return on_is_activable(); } - bool is_selectable() const { return on_is_selectable(); } - CommonGizmosDataID get_requirements() const { return on_get_requirements(); } - virtual bool wants_enter_leave_snapshots() const { return false; } - virtual std::string get_gizmo_entering_text() const { assert(false); return ""; } - virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; } - virtual std::string get_action_snapshot_name() { return _u8L("Gizmo action"); } - void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; } - - unsigned int get_sprite_id() const { return m_sprite_id; } - - int get_hover_id() const { return m_hover_id; } - void set_hover_id(int id); - - bool is_dragging() const { return m_dragging; } - - // returns True when Gizmo changed its state - bool update_items_state(); - - void render() { on_render(); } - void render_for_picking() { on_render_for_picking(); } - void render_input_window(float x, float y, float bottom_limit); - - /// - /// Mouse tooltip text - /// - /// Text to be visible in mouse tooltip - virtual std::string get_tooltip() const { return ""; } - - /// - /// Is called when data (Selection) is changed - /// - virtual void data_changed(){}; - - /// - /// Implement when want to process mouse events in gizmo - /// Click, Right click, move, drag, ... - /// - /// Keep information about mouse click - /// Return True when use the information and don't want to propagate it otherwise False. - virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; } -protected: - virtual bool on_init() = 0; - virtual void on_load(cereal::BinaryInputArchive& ar) {} - virtual void on_save(cereal::BinaryOutputArchive& ar) const {} - virtual std::string on_get_name() const = 0; - virtual void on_set_state() {} - virtual void on_set_hover_id() {} - virtual bool on_is_activable() const { return true; } - virtual bool on_is_selectable() const { return true; } - virtual CommonGizmosDataID on_get_requirements() const { return CommonGizmosDataID(0); } - virtual void on_enable_grabber(unsigned int id) {} - virtual void on_disable_grabber(unsigned int id) {} - - // called inside use_grabbers - virtual void on_start_dragging() {} - virtual void on_stop_dragging() {} - virtual void on_dragging(const UpdateData& data) {} - - virtual void on_render() = 0; - virtual void on_render_for_picking() = 0; - virtual void on_render_input_window(float x, float y, float bottom_limit) {} - - // Returns the picking color for the given id, based on the BASE_ID constant - // No check is made for clashing with other picking color (i.e. GLVolumes) - ColorRGBA picking_color_component(unsigned int id) const; - - void render_grabbers(const BoundingBoxf3& box) const; - void render_grabbers(float size) const; - void render_grabbers_for_picking(const BoundingBoxf3& box) const; - - std::string format(float value, unsigned int decimals) const; - - // Mark gizmo as dirty to Re-Render when idle() - void set_dirty(); - - /// - /// function which - /// Set up m_dragging and call functions - /// on_start_dragging / on_dragging / on_stop_dragging - /// - /// Keep information about mouse click - /// same as on_mouse - bool use_grabbers(const wxMouseEvent &mouse_event); - -#if ENABLE_WORLD_COORDINATE - void do_stop_dragging(bool perform_mouse_cleanup); -#endif // ENABLE_WORLD_COORDINATE - -private: - // Flag for dirty visible state of Gizmo - // When True then need new rendering - bool m_dirty; -}; - -} // namespace GUI -} // namespace Slic3r - -#endif // slic3r_GLGizmoBase_hpp_ +#endif // ENABLE_GIZMO_GRABBER_REFACTOR + }; + +public: + enum EState + { + Off, + On, + Num_States + }; + + struct UpdateData + { + const Linef3& mouse_ray; + const Point& mouse_pos; + + UpdateData(const Linef3& mouse_ray, const Point& mouse_pos) + : mouse_ray(mouse_ray), mouse_pos(mouse_pos) + {} + }; + +protected: + GLCanvas3D& m_parent; + int m_group_id; // TODO: remove only for rotate + EState m_state; + int m_shortcut_key; + std::string m_icon_filename; + unsigned int m_sprite_id; + int m_hover_id; + bool m_dragging; + mutable std::vector m_grabbers; + ImGuiWrapper* m_imgui; + bool m_first_input_window_render; + CommonGizmosDataPool* m_c; +public: + GLGizmoBase(GLCanvas3D& parent, + const std::string& icon_filename, + unsigned int sprite_id); + virtual ~GLGizmoBase() = default; + + bool init() { return on_init(); } + + void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); } + void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); } + + std::string get_name(bool include_shortcut = true) const; + + EState get_state() const { return m_state; } + void set_state(EState state) { m_state = state; on_set_state(); } + + int get_shortcut_key() const { return m_shortcut_key; } + + const std::string& get_icon_filename() const { return m_icon_filename; } + + bool is_activable() const { return on_is_activable(); } + bool is_selectable() const { return on_is_selectable(); } + CommonGizmosDataID get_requirements() const { return on_get_requirements(); } + virtual bool wants_enter_leave_snapshots() const { return false; } + virtual std::string get_gizmo_entering_text() const { assert(false); return ""; } + virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; } + virtual std::string get_action_snapshot_name() { return _u8L("Gizmo action"); } + void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; } + + unsigned int get_sprite_id() const { return m_sprite_id; } + + int get_hover_id() const { return m_hover_id; } + void set_hover_id(int id); + + bool is_dragging() const { return m_dragging; } + + // returns True when Gizmo changed its state + bool update_items_state(); + + void render() { on_render(); } + void render_for_picking() { on_render_for_picking(); } + void render_input_window(float x, float y, float bottom_limit); + + /// + /// Mouse tooltip text + /// + /// Text to be visible in mouse tooltip + virtual std::string get_tooltip() const { return ""; } + + /// + /// Is called when data (Selection) is changed + /// + virtual void data_changed(){}; + + /// + /// Implement when want to process mouse events in gizmo + /// Click, Right click, move, drag, ... + /// + /// Keep information about mouse click + /// Return True when use the information and don't want to propagate it otherwise False. + virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; } +protected: + virtual bool on_init() = 0; + virtual void on_load(cereal::BinaryInputArchive& ar) {} + virtual void on_save(cereal::BinaryOutputArchive& ar) const {} + virtual std::string on_get_name() const = 0; + virtual void on_set_state() {} + virtual void on_set_hover_id() {} + virtual bool on_is_activable() const { return true; } + virtual bool on_is_selectable() const { return true; } + virtual CommonGizmosDataID on_get_requirements() const { return CommonGizmosDataID(0); } + virtual void on_enable_grabber(unsigned int id) {} + virtual void on_disable_grabber(unsigned int id) {} + + // called inside use_grabbers + virtual void on_start_dragging() {} + virtual void on_stop_dragging() {} + virtual void on_dragging(const UpdateData& data) {} + + virtual void on_render() = 0; + virtual void on_render_for_picking() = 0; + virtual void on_render_input_window(float x, float y, float bottom_limit) {} + + // Returns the picking color for the given id, based on the BASE_ID constant + // No check is made for clashing with other picking color (i.e. GLVolumes) + ColorRGBA picking_color_component(unsigned int id) const; + + void render_grabbers(const BoundingBoxf3& box) const; + void render_grabbers(float size) const; + void render_grabbers_for_picking(const BoundingBoxf3& box) const; + + std::string format(float value, unsigned int decimals) const; + + // Mark gizmo as dirty to Re-Render when idle() + void set_dirty(); + + /// + /// function which + /// Set up m_dragging and call functions + /// on_start_dragging / on_dragging / on_stop_dragging + /// + /// Keep information about mouse click + /// same as on_mouse + bool use_grabbers(const wxMouseEvent &mouse_event); + +#if ENABLE_WORLD_COORDINATE + void do_stop_dragging(bool perform_mouse_cleanup); +#endif // ENABLE_WORLD_COORDINATE + +private: + // Flag for dirty visible state of Gizmo + // When True then need new rendering + bool m_dirty; +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoBase_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 58095375f..1d87d772a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -80,7 +80,7 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) if (mouse_event.Dragging()) { if (m_dragging) { #if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE int res = 1; if (m_scale.x() != m_scale.y() || m_scale.x() != m_scale.z()) res = m_parent.get_selection().bake_transform_if_needed(); @@ -90,7 +90,7 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) return true; } else { -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES // Apply new temporary scale factors #if ENABLE_WORLD_COORDINATE @@ -123,9 +123,9 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) #endif // ENABLE_WORLD_COORDINATE #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED - } -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE + } +#endif // ENABLE_WORLD_COORDINATE #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES } } @@ -138,9 +138,6 @@ void GLGizmoScale3D::data_changed() const Selection &selection = m_parent.get_selection(); #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE -#if !ENABLE_WORLD_COORDINATE_SCALE_REVISITED - bool enable_scale_xyz = !selection.requires_uniform_scale(); -#endif // !ENABLE_WORLD_COORDINATE_SCALE_REVISITED #else bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || @@ -149,24 +146,28 @@ void GLGizmoScale3D::data_changed() #if ENABLE_TRANSFORMATIONS_BY_MATRICES set_scale(Vec3d::Ones()); #else -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { #else for (unsigned int i = 0; i < 6; ++i) m_grabbers[i].enabled = enable_scale_xyz; if (enable_scale_xyz) { +#endif // ENABLE_WORLD_COORDINATE // all volumes in the selection belongs to the same instance, any of // them contains the needed data, so we take the first const GLVolume* volume = selection.get_first_volume(); if (selection.is_single_full_instance()) set_scale(volume->get_instance_scaling_factor()); +#if ENABLE_WORLD_COORDINATE + else if (selection.is_single_volume_or_modifier()) +#else else if (selection.is_single_volume() || selection.is_single_modifier()) +#endif // ENABLE_WORLD_COORDINATE set_scale(volume->get_volume_scaling_factor()); } else set_scale(Vec3d::Ones()); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED #endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 0212ed305..c377fda16 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -10,9 +10,9 @@ #include "Camera.hpp" #include "Plater.hpp" -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE #include "MsgDialog.hpp" -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE #include "Gizmos/GLGizmoBase.hpp" @@ -1559,7 +1559,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co this->set_bounding_boxes_dirty(); } -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE int Selection::bake_transform_if_needed() const { if ((is_single_full_instance() && wxGetApp().obj_manipul()->is_world_coordinates()) || @@ -1610,7 +1610,7 @@ int Selection::bake_transform_if_needed() const return 1; } -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE void Selection::erase() { diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index b62db2e6e..c919927ea 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -386,13 +386,13 @@ public: #endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE // returns: // -1 if the user refused to proceed with baking when asked // 0 if the baking was performed // 1 if no baking was needed int bake_transform_if_needed() const; -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE void erase(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index bacdb1f9c..876398510 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -581,12 +581,12 @@ void LockButton::OnButton(wxCommandEvent& event) if (m_disabled) return; -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#if ENABLE_WORLD_COORDINATE SetLock(!m_is_pushed); #else m_is_pushed = !m_is_pushed; update_button_bitmaps(); -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED +#endif // ENABLE_WORLD_COORDINATE event.Skip(); } From 2602c6bf92f13ebb02a96c3ba0c15252b12c4d82 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 18 May 2022 09:11:20 +0200 Subject: [PATCH 137/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Implemented reset skew button for the case when volume world matrix contains skew while volume and instance matrices do not Fixed conflicts during rebase with master --- src/slic3r/GUI/Selection.cpp | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index c377fda16..60bc8346e 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1422,7 +1422,13 @@ void Selection::reset_skew() const VolumeCache& volume_data = m_cache.volumes_data[i]; const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform(); - if (m_mode == Instance && inst_trafo.has_skew()) { + const Geometry::Transformation world_trafo = inst_trafo * vol_trafo; + if (!inst_trafo.has_skew() && !vol_trafo.has_skew() && world_trafo.has_skew()) { + Geometry::Transformation mod_world_trafo = Geometry::Transformation(world_trafo.get_matrix_no_offset()); + mod_world_trafo.reset_skew(); + v.set_volume_transformation(vol_trafo.get_offset_matrix() * inst_trafo.get_matrix_no_offset().inverse() * mod_world_trafo.get_matrix()); + } + else if (m_mode == Instance && inst_trafo.has_skew()) { Geometry::Transformation trafo = inst_trafo; trafo.reset_skew(); v.set_instance_transformation(trafo); @@ -1432,19 +1438,6 @@ void Selection::reset_skew() trafo.reset_skew(); v.set_volume_transformation(trafo); } - else { - const Geometry::Transformation world_trafo = inst_trafo * vol_trafo; - if (world_trafo.has_skew()) { - if (m_mode == Instance) { - // TODO - int a = 0; - } - else { - // TODO - int a = 0; - } - } - } } ensure_on_bed(); From e3d648c8025edfd0465ae543c58a21846f04e685 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 18 May 2022 10:58:27 +0200 Subject: [PATCH 138/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reoworked calculation of volume matrix for newly added modifiers and parts Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 6 ++---- src/libslic3r/Geometry.hpp | 2 ++ src/slic3r/GUI/GUI_ObjectList.cpp | 14 +++++++++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index a611e10c0..6e593af94 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -764,6 +764,7 @@ Transformation Transformation::operator * (const Transformation& other) const return Transformation(get_matrix() * other.get_matrix()); } +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES Transformation Transformation::volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox) { Transformation out; @@ -771,11 +772,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation if (instance_transformation.is_scaling_uniform()) { // No need to run the non-linear least squares fitting for uniform scaling. // Just set the inverse. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - out.set_matrix(instance_transformation.get_matrix_no_offset().inverse()); -#else out.set_from_transform(instance_transformation.get_matrix(true).inverse()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (is_rotation_ninety_degrees(instance_transformation.get_rotation())) { // Anisotropic scaling, rotation by multiples of ninety degrees. @@ -823,6 +820,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation return out; } +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES // For parsing a transformation matrix from 3MF / AMF. Transform3d transform3d_from_string(const std::string& transform_str) diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 0d804c52c..725ced361 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -509,10 +509,12 @@ public: Transformation operator * (const Transformation& other) const; +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES // Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity // as possible in least squares norm in regard to the 8 corners of bbox. // Bounding box is expected to be centered around zero in all axes. static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES private: friend class cereal::access; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index ecf015b6d..73946786d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1584,9 +1584,15 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo new_volume->source.mesh_offset = model.objects.front()->volumes.front()->source.mesh_offset; if (from_galery) { +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); + // Transform the new modifier to be aligned with the print bed. + const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); +#else // Transform the new modifier to be aligned with the print bed. const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(inst_transform, mesh_bb)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Set the modifier position. // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. const Vec3d offset = Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - instance_offset; @@ -1655,9 +1661,15 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_first_volume(); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); // Transform the new modifier to be aligned with the print bed. - const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); + const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); +#else + // Transform the new modifier to be aligned with the print bed. + const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES // Set the modifier position. auto offset = (type_name == "Slab") ? // Slab: Lift to print bed From 3b3edb5a9743c9597ecb894fe74bb5bd9c2852f0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 18 May 2022 12:40:38 +0200 Subject: [PATCH 139/169] Fixed build and warnings on Linux and Mac Fixed conflicts during rebase with master --- src/slic3r/GUI/GLCanvas3D.cpp | 1 - src/slic3r/GUI/GUI_ObjectManipulation.cpp | 2 ++ src/slic3r/GUI/Selection.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 45ef3c002..959611bea 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4146,7 +4146,6 @@ void GLCanvas3D::do_reset_skew(const std::string& snapshot_type) std::set> done; // keeps track of modified instances - Selection::EMode selection_mode = m_selection.get_mode(); const Selection::IndicesList& idxs = m_selection.get_volume_idxs(); for (unsigned int id : idxs) { diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 6fbc55511..74cb64e9f 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1413,7 +1413,9 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) { +#if !ENABLE_TRANSFORMATIONS_BY_MATRICES const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); +#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE if (!use_uniform_scale) { #if !ENABLE_TRANSFORMATIONS_BY_MATRICES diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 60bc8346e..4a60cbe63 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -785,7 +785,7 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor } else { const Vec3d offset = transformation_type.local() ? - volume_data.get_volume_transform().get_rotation_matrix() * displacement : displacement; + (Vec3d)(volume_data.get_volume_transform().get_rotation_matrix() * displacement) : displacement; transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(offset)); } } From b76f9fc2eef4776d1945727f0c860e26f6d0102e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 19 May 2022 10:01:54 +0200 Subject: [PATCH 140/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Scaling using object manipulator fields Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectList.cpp | 4 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 5 +- src/slic3r/GUI/Selection.cpp | 147 ++++++++++++++++------ src/slic3r/GUI/Selection.hpp | 46 ++++++- 4 files changed, 159 insertions(+), 43 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 73946786d..a8c9819a6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1585,8 +1585,8 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo if (from_galery) { #if ENABLE_TRANSFORMATIONS_BY_MATRICES - new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); // Transform the new modifier to be aligned with the print bed. + new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); #else // Transform the new modifier to be aligned with the print bed. @@ -1662,8 +1662,8 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_first_volume(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); // Transform the new modifier to be aligned with the print bed. + new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); #else // Transform the new modifier to be aligned with the print bed. diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 74cb64e9f..ff8fe940c 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -1303,7 +1303,10 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const transformation_type.set_instance(); #if ENABLE_TRANSFORMATIONS_BY_MATRICES - Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; + if (!selection.is_single_full_instance() && !selection.is_single_volume_or_modifier()) + transformation_type.set_relative(); + + const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; #else if (!is_local_coordinates()) transformation_type.set_relative(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 4a60cbe63..be6abeee3 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -713,6 +713,8 @@ const BoundingBoxf3& Selection::get_bounding_box() const const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const { + assert(is_single_full_instance()); + if (!m_unscaled_instance_bounding_box.has_value()) { std::optional* bbox = const_cast*>(&m_unscaled_instance_bounding_box); *bbox = BoundingBoxf3(); @@ -736,6 +738,8 @@ const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const { + assert(is_single_full_instance()); + if (!m_scaled_instance_bounding_box.has_value()) { std::optional* bbox = const_cast*>(&m_scaled_instance_bounding_box); *bbox = BoundingBoxf3(); @@ -753,6 +757,65 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const return *m_scaled_instance_bounding_box; } +#if ENABLE_TRANSFORMATIONS_BY_MATRICES +const BoundingBoxf3& Selection::get_full_unscaled_instance_bounding_box() const +{ + assert(is_single_full_instance()); + + if (!m_full_unscaled_instance_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_full_unscaled_instance_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_instance_transformation().get_matrix_no_scaling_factor() * volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_full_unscaled_instance_bounding_box; +} + +const BoundingBoxf3& Selection::get_full_scaled_instance_bounding_box() const +{ + assert(is_single_full_instance()); + + if (!m_full_scaled_instance_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_full_scaled_instance_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_instance_transformation().get_matrix() * volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_full_scaled_instance_bounding_box; +} + +const BoundingBoxf3& Selection::get_full_unscaled_instance_local_bounding_box() const +{ + assert(is_single_full_instance()); + + if (!m_full_unscaled_instance_local_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_full_unscaled_instance_local_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_full_unscaled_instance_local_bounding_box; +} +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + void Selection::setup_cache() { if (!m_valid) @@ -1350,54 +1413,66 @@ void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation if (!m_valid) return; + Vec3d relative_scale = scale; + for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); - if (m_mode == Instance) { - assert(is_from_fully_selected_instance(i)); - if (transformation_type.absolute()) { - assert(transformation_type.joint()); - v.set_instance_transformation(Geometry::assemble_transform(inst_trafo.get_offset_matrix(), inst_trafo.get_rotation_matrix(), - Geometry::scale_transform(scale), inst_trafo.get_mirror_matrix())); + + if (transformation_type.absolute()) { + // convert from absolute scaling to relative scaling + BoundingBoxf3 original_box; + if (m_mode == Instance) { + assert(is_from_fully_selected_instance(i)); + if (transformation_type.world()) + original_box = get_full_unscaled_instance_bounding_box(); + else + original_box = get_full_unscaled_instance_local_bounding_box(); } else { - if (transformation_type.world()) { - const Transform3d scale_matrix = Geometry::scale_transform(scale); - const Transform3d offset_matrix = (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) ? - // non-constrained scaling - add offset to scale around selection center - Geometry::translation_transform(m_cache.dragging_center + scale_matrix * (inst_trafo.get_offset() - m_cache.dragging_center)) : - // constrained scaling - add offset to keep constraint - Geometry::translation_transform(translation) * inst_trafo.get_offset_matrix(); - v.set_instance_transformation(offset_matrix * scale_matrix * inst_trafo.get_matrix_no_offset()); - } - else if (transformation_type.local()) { - const Transform3d scale_matrix = Geometry::scale_transform(scale); - Vec3d offset; - if (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) { - // non-constrained scaling - add offset to scale around selection center - offset = inst_trafo.get_matrix_no_offset().inverse() * (inst_trafo.get_offset() - m_cache.dragging_center); - offset = inst_trafo.get_matrix_no_offset() * (scale_matrix * offset - offset); - } - else - // constrained scaling - add offset to keep constraint - offset = translation; + if (transformation_type.world()) + original_box = v.transformed_convex_hull_bounding_box((volume_data.get_instance_transform() * + volume_data.get_volume_transform()).get_matrix_no_scaling_factor()); + else if (transformation_type.instance()) + original_box = v.transformed_convex_hull_bounding_box(volume_data.get_volume_transform().get_matrix_no_scaling_factor()); + else + original_box = v.bounding_box(); + } - v.set_instance_transformation(Geometry::translation_transform(offset) * inst_trafo.get_matrix() * scale_matrix); + relative_scale = original_box.size().cwiseProduct(scale).cwiseQuotient(m_box.get_bounding_box().size()); + } + + if (m_mode == Instance) { + assert(is_from_fully_selected_instance(i)); + if (transformation_type.world()) { + const Transform3d scale_matrix = Geometry::scale_transform(relative_scale); + const Transform3d offset_matrix = (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) ? + // non-constrained scaling - add offset to scale around selection center + Geometry::translation_transform(m_cache.dragging_center + scale_matrix * (inst_trafo.get_offset() - m_cache.dragging_center)) : + // constrained scaling - add offset to keep constraint + Geometry::translation_transform(translation) * inst_trafo.get_offset_matrix(); + v.set_instance_transformation(offset_matrix * scale_matrix * inst_trafo.get_matrix_no_offset()); + } + else if (transformation_type.local()) { + const Transform3d scale_matrix = Geometry::scale_transform(relative_scale); + Vec3d offset; + if (transformation_type.joint() && translation.isApprox(Vec3d::Zero())) { + // non-constrained scaling - add offset to scale around selection center + offset = inst_trafo.get_matrix_no_offset().inverse() * (inst_trafo.get_offset() - m_cache.dragging_center); + offset = inst_trafo.get_matrix_no_offset() * (scale_matrix * offset - offset); } else - assert(false); - } - } - else { - if (transformation_type.absolute()) { - const Geometry::Transformation& volume_trafo = volume_data.get_volume_transform(); - v.set_volume_transformation(Geometry::assemble_transform(volume_trafo.get_offset_matrix(), volume_trafo.get_rotation_matrix(), - Geometry::scale_transform(scale), volume_trafo.get_mirror_matrix())); + // constrained scaling - add offset to keep constraint + offset = translation; + + v.set_instance_transformation(Geometry::translation_transform(offset) * inst_trafo.get_matrix() * scale_matrix); } else - transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(scale)); + assert(false); } + else + transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(translation) * Geometry::scale_transform(relative_scale)); } #if !DISABLE_INSTANCES_SYNCH diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index c919927ea..a20e952f3 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -225,10 +225,24 @@ private: Cache m_cache; Clipboard m_clipboard; std::optional m_bounding_box; - // Bounding box of a selection, with no instance scaling applied. This bounding box - // is useful for absolute scaling of tilted objects in world coordinate space. + // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. + // This bounding box is useful for absolute scaling of tilted objects in world coordinate space. + // Modifiers are NOT taken in account std::optional m_unscaled_instance_bounding_box; + // Bounding box of a single full instance selection, in world coordinates. + // Modifiers are NOT taken in account std::optional m_scaled_instance_bounding_box; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. + // Modifiers are taken in account + std::optional m_full_unscaled_instance_bounding_box; + // Bounding box of a single full instance selection, in world coordinates. + // Modifiers are taken in account + std::optional m_full_scaled_instance_bounding_box; + // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied. + // Modifiers are taken in account + std::optional m_full_unscaled_instance_local_bounding_box; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_RENDER_SELECTION_CENTER GLModel m_vbo_sphere; @@ -355,10 +369,25 @@ public: unsigned int volumes_count() const { return (unsigned int)m_list.size(); } const BoundingBoxf3& get_bounding_box() const; - // Bounding box of a selection, with no instance scaling applied. This bounding box - // is useful for absolute scaling of tilted objects in world coordinate space. + // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. + // This bounding box is useful for absolute scaling of tilted objects in world coordinate space. + // Modifiers are NOT taken in account const BoundingBoxf3& get_unscaled_instance_bounding_box() const; + // Bounding box of a single full instance selection, in world coordinates. + // Modifiers are NOT taken in account const BoundingBoxf3& get_scaled_instance_bounding_box() const; +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. + // Modifiers are taken in account + const BoundingBoxf3& get_full_unscaled_instance_bounding_box() const; + // Bounding box of a single full instance selection, in world coordinates. + // Modifiers are taken in account + const BoundingBoxf3& get_full_scaled_instance_bounding_box() const; + + // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied. + // Modifiers are taken in account + const BoundingBoxf3& get_full_unscaled_instance_local_bounding_box() const; +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void setup_cache(); @@ -429,7 +458,16 @@ private: void do_remove_volume(unsigned int volume_idx); void do_remove_instance(unsigned int object_idx, unsigned int instance_idx); void do_remove_object(unsigned int object_idx); +#if ENABLE_TRANSFORMATIONS_BY_MATRICES + void set_bounding_boxes_dirty() { + m_bounding_box.reset(); + m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); + m_full_unscaled_instance_bounding_box.reset(); m_full_scaled_instance_bounding_box.reset(); + m_full_unscaled_instance_local_bounding_box.reset();; + } +#else void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); } +#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES void render_synchronized_volumes(); #if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_WORLD_COORDINATE From 4846b504a2977dc31fe8db3855356cc2cc621466 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 19 May 2022 12:19:18 +0200 Subject: [PATCH 141/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Improved detection and removal of skew in matrices Fixed conflicts during rebase with master --- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 30 ++++---------- src/slic3r/GUI/Selection.cpp | 50 +++++++++++++++-------- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index ff8fe940c..c6ca3f746 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -944,18 +944,12 @@ void ObjectManipulation::update_reset_buttons_visibility() const Geometry::Transformation& trafo = volume->get_instance_transformation(); rotation = trafo.get_rotation_matrix(); scale = trafo.get_scaling_factor_matrix(); - if (trafo.has_skew()) - // the instance transform contains skew - skew = trafo; - else { - // the world transform contains skew - 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 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; } } #else @@ -971,15 +965,9 @@ void ObjectManipulation::update_reset_buttons_visibility() const Geometry::Transformation& trafo = volume->get_volume_transformation(); rotation = trafo.get_rotation_matrix(); scale = trafo.get_scaling_factor_matrix(); - if (trafo.has_skew()) - // the volume transform contains skew - skew = trafo; - else { - // the world transform contains skew - const Geometry::Transformation world_trafo(volume->world_matrix()); - if (world_trafo.has_skew()) - skew = world_trafo; - } + const Geometry::Transformation world_trafo(volume->world_matrix()); + if (world_trafo.has_skew()) + skew = world_trafo; #else rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index be6abeee3..02edd9c31 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1495,23 +1495,41 @@ void Selection::reset_skew() for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Geometry::Transformation& inst_trafo = volume_data.get_instance_transform(); - const Geometry::Transformation& vol_trafo = volume_data.get_volume_transform(); - const Geometry::Transformation world_trafo = inst_trafo * vol_trafo; - if (!inst_trafo.has_skew() && !vol_trafo.has_skew() && world_trafo.has_skew()) { - Geometry::Transformation mod_world_trafo = Geometry::Transformation(world_trafo.get_matrix_no_offset()); - mod_world_trafo.reset_skew(); - v.set_volume_transformation(vol_trafo.get_offset_matrix() * inst_trafo.get_matrix_no_offset().inverse() * mod_world_trafo.get_matrix()); + Geometry::Transformation inst_trafo = volume_data.get_instance_transform(); + Geometry::Transformation vol_trafo = volume_data.get_volume_transform(); + Geometry::Transformation world_trafo = inst_trafo * vol_trafo; + if (world_trafo.has_skew()) { + if (!inst_trafo.has_skew() && !vol_trafo.has_skew()) { + // = [I][V] + world_trafo.reset_offset(); + world_trafo.reset_skew(); + v.set_volume_transformation(vol_trafo.get_offset_matrix() * inst_trafo.get_matrix_no_offset().inverse() * world_trafo.get_matrix()); + } + else { + // = + // = [V] + // = [I] + if (inst_trafo.has_skew()) { + inst_trafo.reset_skew(); + v.set_instance_transformation(inst_trafo); + } + if (vol_trafo.has_skew()) { + vol_trafo.reset_skew(); + v.set_volume_transformation(vol_trafo); + } + } } - else if (m_mode == Instance && inst_trafo.has_skew()) { - Geometry::Transformation trafo = inst_trafo; - trafo.reset_skew(); - v.set_instance_transformation(trafo); - } - else if (m_mode == Volume && vol_trafo.has_skew()) { - Geometry::Transformation trafo = vol_trafo; - trafo.reset_skew(); - v.set_volume_transformation(trafo); + else { + // [W] = [I][V] + // [W] = + if (inst_trafo.has_skew()) { + inst_trafo.reset_skew(); + v.set_instance_transformation(inst_trafo); + } + if (vol_trafo.has_skew()) { + vol_trafo.reset_skew(); + v.set_volume_transformation(vol_trafo); + } } } From f591535d2050aa306a18bb85d2d533cfdb39e5a3 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 1 Jun 2022 10:13:36 +0200 Subject: [PATCH 142/169] Removed tech ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET --- src/libslic3r/Technologies.hpp | 2 -- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 4 ---- 2 files changed, 6 deletions(-) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 250a45f3f..4f0a04df1 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -71,8 +71,6 @@ #define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1) // Enable editing volumes transformation in world coordinates and instances in local coordinates #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) -// Enable showing world coordinates of volumes' offset relative to the instance containing them -#define ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET (0 && ENABLE_WORLD_COORDINATE) // Enable implementation of Geometry::Transformation using matrices only #define ENABLE_TRANSFORMATIONS_BY_MATRICES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index c6ca3f746..8f5445f54 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -727,11 +727,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (is_world_coordinates()) { const Geometry::Transformation trafo(volume->world_matrix()); -#if ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET - const Vec3d offset = trafo.get_offset() - volume->get_instance_offset(); -#else const Vec3d& offset = trafo.get_offset(); -#endif // ENABLE_WORLD_COORDINATE_VOLUMES_LOCAL_OFFSET m_new_position = offset; m_new_rotate_label_string = L("Rotate"); From 00878fb330dcdf2034ac7f7b7e05c9d98ac3ad91 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 1 Jun 2022 14:14:46 +0200 Subject: [PATCH 143/169] Tech ENABLE_TRANSFORMATIONS_BY_MATRICES merged into ENABLE_WORLD_COORDINATE Fixed conflicts during rebase with master --- src/libslic3r/Geometry.cpp | 59 ++-- src/libslic3r/Geometry.hpp | 62 ++-- src/libslic3r/Model.cpp | 50 ++-- src/libslic3r/Model.hpp | 43 ++- src/libslic3r/Point.hpp | 4 +- src/libslic3r/PrintApply.cpp | 8 +- src/libslic3r/Technologies.hpp | 2 - src/slic3r/GUI/3DScene.hpp | 28 +- src/slic3r/GUI/GLCanvas3D.cpp | 48 ++-- src/slic3r/GUI/GLCanvas3D.hpp | 12 +- src/slic3r/GUI/GUI_ObjectList.cpp | 16 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 212 +++----------- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 8 +- src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoMove.cpp | 29 -- src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp | 24 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 56 +--- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 203 ++----------- src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp | 4 +- src/slic3r/GUI/MeshUtils.cpp | 8 +- src/slic3r/GUI/Plater.cpp | 12 +- src/slic3r/GUI/Selection.cpp | 286 ++++--------------- src/slic3r/GUI/Selection.hpp | 47 ++- 26 files changed, 340 insertions(+), 897 deletions(-) diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 6e593af94..115d21693 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -414,7 +414,7 @@ Vec3d extract_euler_angles(const Transform3d& transform) return extract_euler_angles(m); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Transform3d Transformation::get_offset_matrix() const { return assemble_transform(get_offset()); @@ -499,11 +499,11 @@ void Transformation::set_offset(Axis axis, double offset) m_dirty = true; } } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Transformation::set_rotation(const Vec3d& rotation) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Vec3d offset = get_offset(); m_matrix = rotation_transform(rotation) * extract_scale(m_matrix); m_matrix.translation() = offset; @@ -511,7 +511,7 @@ void Transformation::set_rotation(const Vec3d& rotation) set_rotation(X, rotation.x()); set_rotation(Y, rotation.y()); set_rotation(Z, rotation.z()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } void Transformation::set_rotation(Axis axis, double rotation) @@ -520,7 +520,7 @@ void Transformation::set_rotation(Axis axis, double rotation) if (is_approx(std::abs(rotation), 2.0 * double(PI))) rotation = 0.0; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE auto [curr_rotation, scale] = extract_rotation_scale(m_matrix); Vec3d angles = extract_euler_angles(curr_rotation); angles[axis] = rotation; @@ -533,10 +533,10 @@ void Transformation::set_rotation(Axis axis, double rotation) m_rotation(axis) = rotation; m_dirty = true; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d Transformation::get_scaling_factor() const { const Transform3d scale = extract_scale(m_matrix); @@ -547,11 +547,11 @@ Transform3d Transformation::get_scaling_factor_matrix() const { return extract_scale(m_matrix); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Transformation::set_scaling_factor(const Vec3d& scaling_factor) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE assert(scaling_factor.x() > 0.0 && scaling_factor.y() > 0.0 && scaling_factor.z() > 0.0); const Vec3d offset = get_offset(); @@ -561,12 +561,12 @@ void Transformation::set_scaling_factor(const Vec3d& scaling_factor) set_scaling_factor(X, scaling_factor.x()); set_scaling_factor(Y, scaling_factor.y()); set_scaling_factor(Z, scaling_factor.z()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } void Transformation::set_scaling_factor(Axis axis, double scaling_factor) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE assert(scaling_factor > 0.0); auto [rotation, scale] = extract_rotation_scale(m_matrix); scale(axis, axis) = scaling_factor; @@ -579,10 +579,10 @@ void Transformation::set_scaling_factor(Axis axis, double scaling_factor) m_scaling_factor(axis) = std::abs(scaling_factor); m_dirty = true; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d Transformation::get_mirror() const { const Transform3d scale = extract_scale(m_matrix); @@ -594,11 +594,11 @@ Transform3d Transformation::get_mirror_matrix() const const Vec3d scale = get_scaling_factor(); return scale_transform({ scale.x() / std::abs(scale.x()), scale.y() / std::abs(scale.y()), scale.z() / std::abs(scale.z()) }); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Transformation::set_mirror(const Vec3d& mirror) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d copy(mirror); const Vec3d abs_mirror = copy.cwiseAbs(); for (int i = 0; i < 3; ++i) { @@ -619,7 +619,7 @@ void Transformation::set_mirror(const Vec3d& mirror) set_mirror(X, mirror.x()); set_mirror(Y, mirror.y()); set_mirror(Z, mirror.z()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } void Transformation::set_mirror(Axis axis, double mirror) @@ -630,7 +630,7 @@ void Transformation::set_mirror(Axis axis, double mirror) else if (abs_mirror != 1.0) mirror /= abs_mirror; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const double curr_scale = get_scaling_factor(axis); const double sign = curr_scale * mirror; set_scaling_factor(axis, sign < 0.0 ? std::abs(curr_scale) * mirror : curr_scale); @@ -639,10 +639,10 @@ void Transformation::set_mirror(Axis axis, double mirror) m_mirror(axis) = mirror; m_dirty = true; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE bool Transformation::has_skew() const { return contains_skew(m_matrix); @@ -685,23 +685,23 @@ void Transformation::set_from_transform(const Transform3d& transform) // if (!m_matrix.isApprox(transform)) // std::cout << "something went wrong in extracting data from matrix" << std::endl; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Transformation::reset() { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE m_offset = Vec3d::Zero(); m_rotation = Vec3d::Zero(); m_scaling_factor = Vec3d::Ones(); m_mirror = Vec3d::Ones(); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE m_matrix = Transform3d::Identity(); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE m_dirty = false; -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void Transformation::reset_skew() { Matrix3d rotation; @@ -757,14 +757,14 @@ const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rot return m_matrix; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE Transformation Transformation::operator * (const Transformation& other) const { return Transformation(get_matrix() * other.get_matrix()); } -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE Transformation Transformation::volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox) { Transformation out; @@ -810,8 +810,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation out.set_scaling_factor(Vec3d(std::abs(scale.x()), std::abs(scale.y()), std::abs(scale.z()))); out.set_mirror(Vec3d(scale.x() > 0 ? 1. : -1, scale.y() > 0 ? 1. : -1, scale.z() > 0 ? 1. : -1)); } - else - { + else { // General anisotropic scaling, general rotation. // Keep the modifier mesh in the instance coordinate system, so the modifier mesh will not be aligned with the world. // Scale it to get the required size. @@ -820,7 +819,7 @@ Transformation Transformation::volume_to_bed_transformation(const Transformation return out; } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE // For parsing a transformation matrix from 3MF / AMF. Transform3d transform3d_from_string(const std::string& transform_str) diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 725ced361..6ff52016e 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -381,7 +381,7 @@ Vec3d extract_euler_angles(const Transform3d& transform); class Transformation { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Transform3d m_matrix{ Transform3d::Identity() }; #else struct Flags @@ -403,47 +403,43 @@ class Transformation mutable Transform3d m_matrix{ Transform3d::Identity() }; mutable Flags m_flags; mutable bool m_dirty{ false }; -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE public: -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Transformation() = default; - explicit Transformation(const Transform3d& transform) : m_matrix(transform) {} -#else - Transformation(); - explicit Transformation(const Transform3d& transform); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES + explicit Transformation(const Transform3d & transform) : m_matrix(transform) {} -#if ENABLE_TRANSFORMATIONS_BY_MATRICES Vec3d get_offset() const { return m_matrix.translation(); } double get_offset(Axis axis) const { return get_offset()[axis]; } Transform3d get_offset_matrix() const; - void set_offset(const Vec3d& offset) { m_matrix.translation() = offset; } + void set_offset(const Vec3d & offset) { m_matrix.translation() = offset; } void set_offset(Axis axis, double offset) { m_matrix.translation()[axis] = offset; } -#else - const Vec3d& get_offset() const { return m_offset; } - double get_offset(Axis axis) const { return m_offset(axis); } - void set_offset(const Vec3d& offset); - void set_offset(Axis axis, double offset); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - -#if ENABLE_TRANSFORMATIONS_BY_MATRICES Vec3d get_rotation() const; double get_rotation(Axis axis) const { return get_rotation()[axis]; } Transform3d get_rotation_matrix() const; #else + Transformation(); + explicit Transformation(const Transform3d & transform); + + const Vec3d& get_offset() const { return m_offset; } + double get_offset(Axis axis) const { return m_offset(axis); } + + void set_offset(const Vec3d & offset); + void set_offset(Axis axis, double offset); + const Vec3d& get_rotation() const { return m_rotation; } double get_rotation(Axis axis) const { return m_rotation(axis); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void set_rotation(const Vec3d& rotation); void set_rotation(Axis axis, double rotation); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_scaling_factor() const; double get_scaling_factor(Axis axis) const { return get_scaling_factor()[axis]; } @@ -456,12 +452,12 @@ public: #else const Vec3d& get_scaling_factor() const { return m_scaling_factor; } double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void set_scaling_factor(const Vec3d& scaling_factor); void set_scaling_factor(Axis axis, double scaling_factor); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_mirror() const; double get_mirror(Axis axis) const { return get_mirror()[axis]; } @@ -477,21 +473,19 @@ public: const Vec3d& get_mirror() const { return m_mirror; } double get_mirror(Axis axis) const { return m_mirror(axis); } bool is_left_handed() const { return m_mirror.x() * m_mirror.y() * m_mirror.z() < 0.; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void set_mirror(const Vec3d& mirror); void set_mirror(Axis axis, double mirror); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE bool has_skew() const; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#else void set_from_transform(const Transform3d& transform); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void reset(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void reset_offset() { set_offset(Vec3d::Zero()); } void reset_rotation() { set_rotation(Vec3d::Zero()); } void reset_scaling_factor() { set_scaling_factor(Vec3d::Ones()); } @@ -505,20 +499,20 @@ public: void set_matrix(const Transform3d& transform) { m_matrix = transform; } #else const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE Transformation operator * (const Transformation& other) const; -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE // Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity // as possible in least squares norm in regard to the 8 corners of bbox. // Bounding box is expected to be centered around zero in all axes. static Transformation volume_to_bed_transformation(const Transformation& instance_transformation, const BoundingBoxf3& bbox); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE private: friend class cereal::access; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE template void serialize(Archive& ar) { ar(m_matrix); } explicit Transformation(int) {} template static void load_and_construct(Archive& ar, cereal::construct& construct) @@ -536,7 +530,7 @@ private: construct(1); ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE }; // For parsing a transformation matrix from 3MF / AMF. diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index f3f31ed4f..1f8083aca 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -945,11 +945,11 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const if (this->instances.empty()) throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances"); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d inst_matrix = this->instances.front()->get_transformation().get_matrix_no_offset(); #else const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE for (const ModelVolume *v : this->volumes) if (v->is_model_part()) m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); @@ -961,14 +961,14 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_translate) const { BoundingBoxf3 bb; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - const Transform3d inst_matrix = dont_translate ? +#if ENABLE_WORLD_COORDINATE + const Transform3d inst_matrix = dont_translate ? this->instances[instance_idx]->get_transformation().get_matrix_no_offset() : this->instances[instance_idx]->get_transformation().get_matrix(); #else const Transform3d& inst_matrix = this->instances[instance_idx]->get_transformation().get_matrix(dont_translate); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE for (ModelVolume *v : this->volumes) { if (v->is_model_part()) bb.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix())); @@ -1379,11 +1379,11 @@ void ModelObject::split(ModelObjectPtrs* new_objects) ModelVolume* new_vol = new_object->add_volume(*volume, std::move(mesh)); for (ModelInstance* model_instance : new_object->instances) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d shift = model_instance->get_transformation().get_matrix_no_offset() * new_vol->get_offset(); #else Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE model_instance->set_offset(model_instance->get_offset() + shift); } @@ -1447,7 +1447,7 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) // Adjust the meshes. // Transformation to be applied to the meshes. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Geometry::Transformation reference_trafo_mod = reference_trafo; reference_trafo_mod.reset_offset(); if (uniform_scaling) @@ -1457,7 +1457,7 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo_mod.get_matrix().matrix().block<3, 3>(0, 0); #else Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix(); for (ModelVolume *model_volume : this->volumes) { const Geometry::Transformation volume_trafo = model_volume->get_transformation(); @@ -1467,7 +1467,7 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) std::abs(volume_trafo.get_scaling_factor().x() - volume_trafo.get_scaling_factor().z()) < EPSILON; double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.; // Transform the mesh. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Geometry::Transformation volume_trafo_mod = volume_trafo; volume_trafo_mod.reset_offset(); if (volume_uniform_scaling) @@ -1477,7 +1477,7 @@ void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx) Eigen::Matrix3d volume_trafo_3x3 = volume_trafo_mod.get_matrix().matrix().block<3, 3>(0, 0); #else Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Following method creates a new shared_ptr model_volume->transform_this_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed); // Reset the rotation, scaling and mirroring. @@ -1524,11 +1524,11 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const double min_z = DBL_MAX; const ModelInstance* inst = instances[instance_idx]; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - const Transform3d& mi = inst->get_matrix_no_offset(); +#if ENABLE_WORLD_COORDINATE + const Transform3d mi = inst->get_matrix_no_offset(); #else const Transform3d& mi = inst->get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE for (const ModelVolume* v : volumes) { if (!v->is_model_part()) @@ -1549,11 +1549,11 @@ double ModelObject::get_instance_max_z(size_t instance_idx) const double max_z = -DBL_MAX; const ModelInstance* inst = instances[instance_idx]; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - const Transform3d& mi = inst->get_matrix_no_offset(); +#if ENABLE_WORLD_COORDINATE + const Transform3d mi = inst->get_matrix_no_offset(); #else const Transform3d& mi = inst->get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE for (const ModelVolume* v : volumes) { if (!v->is_model_part()) @@ -1979,22 +1979,22 @@ void ModelVolume::convert_from_meters() void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE mesh->transform(dont_translate ? get_matrix_no_offset() : get_matrix()); #else mesh->transform(get_matrix(dont_translate)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate) const { // Rotate around mesh origin. TriangleMesh copy(mesh); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE copy.transform(get_transformation().get_rotation_matrix()); #else copy.transform(get_matrix(true, false, true, true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE BoundingBoxf3 bbox = copy.bounding_box(); if (!empty(bbox)) { @@ -2019,20 +2019,20 @@ BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mes BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE return bbox.transformed(dont_translate ? get_matrix_no_offset() : get_matrix()); #else return bbox.transformed(get_matrix(dont_translate)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE return dont_translate ? get_matrix_no_offset() * v : get_matrix() * v; #else return get_matrix(dont_translate) * v; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } void ModelInstance::transform_polygon(Polygon* polygon) const diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 016689a46..828358419 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -691,27 +691,26 @@ public: const Geometry::Transformation& get_transformation() const { return m_transformation; } void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void set_transformation(const Transform3d& trafo) { m_transformation.set_matrix(trafo); } -#else - void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_TRANSFORMATIONS_BY_MATRICES Vec3d get_offset() const { return m_transformation.get_offset(); } #else + void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); } + const Vec3d& get_offset() const { return m_transformation.get_offset(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE + double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_rotation() const { return m_transformation.get_rotation(); } #else const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } @@ -723,11 +722,11 @@ public: void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_mirror() const { return m_transformation.get_mirror(); } #else const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } bool is_left_handed() const { return m_transformation.is_left_handed(); } @@ -736,12 +735,12 @@ public: void convert_from_imperial_units(); void convert_from_meters(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d& get_matrix() const { return m_transformation.get_matrix(); } Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); } #else const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void set_new_unique_id() { ObjectBase::set_new_unique_id(); @@ -944,41 +943,41 @@ public: const Geometry::Transformation& get_transformation() const { return m_transformation; } void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_offset() const { return m_transformation.get_offset(); } #else const Vec3d& get_offset() const { return m_transformation.get_offset(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_offset(Axis axis) const { return m_transformation.get_offset(axis); } void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); } void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_rotation() const { return m_transformation.get_rotation(); } #else const Vec3d& get_rotation() const { return m_transformation.get_rotation(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); } void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } #else const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_mirror() const { return m_transformation.get_mirror(); } #else const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } bool is_left_handed() const { return m_transformation.is_left_handed(); } @@ -996,12 +995,12 @@ public: // To be called on an external polygon. It does not translate the polygon, only rotates and scales. void transform_polygon(Polygon* polygon) const; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d& get_matrix() const { return m_transformation.get_matrix(); } Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); } #else const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); } diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 4fc76800c..ec071673b 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -545,10 +545,10 @@ namespace cereal { template void load(Archive& archive, Slic3r::Matrix2f &m) { archive.loadBinary((char*)m.data(), sizeof(float) * 4); } template void save(Archive& archive, Slic3r::Matrix2f &m) { archive.saveBinary((char*)m.data(), sizeof(float) * 4); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE template void load(Archive& archive, Slic3r::Transform3d& m) { archive.loadBinary((char*)m.data(), sizeof(double) * 16); } template void save(Archive& archive, const Slic3r::Transform3d& m) { archive.saveBinary((char*)m.data(), sizeof(double) * 16); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } // To be able to use Vec<> and Mat<> in range based for loops: diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index b88ae9e50..4c085728c 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -790,13 +790,13 @@ void update_volume_bboxes( if (it != volumes_old.end() && it->volume_id == model_volume->id()) layer_range.volumes.emplace_back(*it); } else -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE layer_range.volumes.push_back({ model_volume->id(), transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix()), offset) }); #else layer_range.volumes.push_back({ model_volume->id(), transformed_its_bbox2d(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), offset) }); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } } else { std::vector> volumes_old; @@ -828,11 +828,11 @@ void update_volume_bboxes( layer_range.volumes.emplace_back(*it); } } else { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix()), ranges, bboxes, offset); #else transformed_its_bboxes_in_z_ranges(model_volume->mesh().its, trafo_for_bbox(object_trafo, model_volume->get_matrix(false)), ranges, bboxes, offset); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE for (PrintObjectRegions::LayerRangeRegions &layer_range : layer_ranges) if (auto &bbox = bboxes[&layer_range - layer_ranges.data()]; bbox.second) layer_range.volumes.push_back({ model_volume->id(), bbox.first }); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 4f0a04df1..302c62879 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -71,8 +71,6 @@ #define ENABLE_VOLUMETRIC_RATE_TOOLPATHS_RECALC (1 && ENABLE_2_5_0_ALPHA1) // Enable editing volumes transformation in world coordinates and instances in local coordinates #define ENABLE_WORLD_COORDINATE (1 && ENABLE_2_5_0_ALPHA1) -// Enable implementation of Geometry::Transformation using matrices only -#define ENABLE_TRANSFORMATIONS_BY_MATRICES (1 && ENABLE_WORLD_COORDINATE) // Enable modified camera control using mouse #define ENABLE_NEW_CAMERA_MOVEMENTS (1 && ENABLE_2_5_0_ALPHA1) // Enable modified rectangle selection diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 9fb5a6965..4ef095223 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -432,23 +432,23 @@ public: const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; } void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void set_instance_transformation(const Transform3d& transform) { m_instance_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); } Vec3d get_instance_offset() const { return m_instance_transformation.get_offset(); } #else const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); } void set_instance_offset(const Vec3d& offset) { m_instance_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } void set_instance_offset(Axis axis, double offset) { m_instance_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_instance_rotation() const { return m_instance_transformation.get_rotation(); } #else const Vec3d& get_instance_rotation() const { return m_instance_transformation.get_rotation(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_instance_rotation(Axis axis) const { return m_instance_transformation.get_rotation(axis); } void set_instance_rotation(const Vec3d& rotation) { m_instance_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } @@ -460,11 +460,11 @@ public: void set_instance_scaling_factor(const Vec3d& scaling_factor) { m_instance_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } void set_instance_scaling_factor(Axis axis, double scaling_factor) { m_instance_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_instance_mirror() const { return m_instance_transformation.get_mirror(); } #else const Vec3d& get_instance_mirror() const { return m_instance_transformation.get_mirror(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_instance_mirror(Axis axis) const { return m_instance_transformation.get_mirror(axis); } void set_instance_mirror(const Vec3d& mirror) { m_instance_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } @@ -472,43 +472,43 @@ public: const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; } void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void set_volume_transformation(const Transform3d& transform) { m_volume_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); } Vec3d get_volume_offset() const { return m_volume_transformation.get_offset(); } #else const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); } void set_volume_offset(const Vec3d& offset) { m_volume_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); } void set_volume_offset(Axis axis, double offset) { m_volume_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_volume_rotation() const { return m_volume_transformation.get_rotation(); } #else const Vec3d& get_volume_rotation() const { return m_volume_transformation.get_rotation(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_volume_rotation(Axis axis) const { return m_volume_transformation.get_rotation(axis); } void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); } void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } #else const Vec3d& get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); } void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); } void set_volume_scaling_factor(Axis axis, double scaling_factor) { m_volume_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Vec3d get_volume_mirror() const { return m_volume_transformation.get_mirror(); } #else const Vec3d& get_volume_mirror() const { return m_volume_transformation.get_mirror(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double get_volume_mirror(Axis axis) const { return m_volume_transformation.get_mirror(axis); } void set_volume_mirror(const Vec3d& mirror) { m_volume_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 959611bea..026734711 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1101,9 +1101,9 @@ wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event); wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE wxDEFINE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); @@ -2917,13 +2917,13 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) else displacement = multiplier * direction; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE TransformationType trafo_type; trafo_type.set_relative(); m_selection.translate(displacement, trafo_type); #else m_selection.translate(displacement); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE m_dirty = true; } ); @@ -3588,13 +3588,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE TransformationType trafo_type; trafo_type.set_relative(); m_selection.translate(cur_pos - m_mouse.drag.start_position_3D, trafo_type); #else m_selection.translate(cur_pos - m_mouse.drag.start_position_3D); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE if (current_printer_technology() == ptFFF && fff_print()->config().complete_objects) update_sequential_clearance(); wxGetApp().obj_manipul()->set_dirty(); @@ -3840,17 +3840,17 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); #else model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE else if (selection_mode == Selection::Volume) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); #else model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE object_moved = true; model_object->invalidate_bounding_box(); @@ -3948,20 +3948,20 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); #else model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } else if (selection_mode == Selection::Volume) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); #else model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation()); model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } model_object->invalidate_bounding_box(); } @@ -4024,22 +4024,22 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); #else model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor()); model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } else if (selection_mode == Selection::Volume) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); #else model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor()); model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } model_object->invalidate_bounding_box(); } @@ -4101,17 +4101,17 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) ModelObject* model_object = m_model->objects[object_idx]; if (model_object != nullptr) { if (selection_mode == Selection::Instance) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->instances[instance_idx]->set_transformation(v->get_instance_transformation()); #else model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE else if (selection_mode == Selection::Volume) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE model_object->volumes[volume_idx]->set_transformation(v->get_volume_transformation()); #else model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE model_object->invalidate_bounding_box(); } @@ -4135,7 +4135,7 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type) m_dirty = true; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void GLCanvas3D::do_reset_skew(const std::string& snapshot_type) { if (m_model == nullptr) @@ -4171,7 +4171,7 @@ void GLCanvas3D::do_reset_skew(const std::string& snapshot_type) m_dirty = true; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void GLCanvas3D::update_gizmos_on_off_state() { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 9cf6501fc..18ad42ae1 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -156,9 +156,9 @@ wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE wxDECLARE_EVENT(EVT_GLCANVAS_RESET_SKEW, SimpleEvent); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event); @@ -742,11 +742,11 @@ public: void update_volumes_colors_by_extruder(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE bool is_dragging() const { return m_gizmos.is_dragging() || (m_moving && !m_mouse.scene_position.isApprox(m_mouse.drag.start_position_3D)); } #else bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void render(); // printable_only == false -> render also non printable volumes as grayed @@ -814,9 +814,9 @@ public: void do_rotate(const std::string& snapshot_type); void do_scale(const std::string& snapshot_type); void do_mirror(const std::string& snapshot_type); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void do_reset_skew(const std::string& snapshot_type); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void update_gizmos_on_off_state(); void reset_all_gizmos() { m_gizmos.reset_all_states(); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index a8c9819a6..bfedd8e1e 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1532,11 +1532,11 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_first_volume(); const Geometry::Transformation inst_transform = v->get_instance_transformation(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d inv_inst_transform = inst_transform.get_matrix_no_offset().inverse(); #else const Transform3d inv_inst_transform = inst_transform.get_matrix(true).inverse(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE const Vec3d instance_offset = v->get_instance_offset(); for (size_t i = 0; i < input_files.size(); ++i) { @@ -1584,7 +1584,7 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo new_volume->source.mesh_offset = model.objects.front()->volumes.front()->source.mesh_offset; if (from_galery) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE // Transform the new modifier to be aligned with the print bed. new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); @@ -1592,7 +1592,7 @@ void ObjectList::load_modifier(const wxArrayString& input_files, ModelObject& mo // Transform the new modifier to be aligned with the print bed. const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(inst_transform, mesh_bb)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Set the modifier position. // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. const Vec3d offset = Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - instance_offset; @@ -1661,7 +1661,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // First (any) GLVolume of the selected instance. They all share the same instance matrix. const GLVolume* v = selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE // Transform the new modifier to be aligned with the print bed. new_volume->set_transformation(v->get_instance_transformation().get_matrix_no_offset().inverse()); const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); @@ -1669,18 +1669,18 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode // Transform the new modifier to be aligned with the print bed. const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box(); new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Set the modifier position. auto offset = (type_name == "Slab") ? // Slab: Lift to print bed Vec3d(0., 0., 0.5 * mesh_bb.size().z() + instance_bb.min.z() - v->get_instance_offset().z()) : // Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed. Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - v->get_instance_offset(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE new_volume->set_offset(v->get_instance_transformation().get_matrix_no_offset().inverse() * offset); #else new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE const wxString name = _L("Generic") + "-" + _(type_name); new_volume->name = into_u8(name); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 8f5445f54..24ae01389 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -354,11 +354,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const GLVolume* volume = selection.get_first_volume(); const double min_z = get_volume_min_z(*volume); if (!is_world_coordinates()) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); -#else - const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); @@ -385,11 +381,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const double min_z = selection.get_scaled_instance_bounding_box().min.z(); if (!is_world_coordinates()) { const GLVolume* volume = selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); -#else - const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix(true).inverse() * (min_z * Vec3d::UnitZ()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); @@ -460,7 +452,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); m_reset_scale_button->SetToolTip(_L("Reset scale")); m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); Selection& selection = canvas->get_selection(); if (selection.is_single_volume_or_modifier()) @@ -481,8 +473,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : change_scale_value(0, 100.); change_scale_value(1, 100.); change_scale_value(2, 100.); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - }); +#endif // ENABLE_WORLD_COORDINATE + }); editors_grid_sizer->Add(m_reset_scale_button); for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) @@ -492,7 +484,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE m_skew_label = new wxStaticText(parent, wxID_ANY, _L("Skew")); m_main_grid_sizer->Add(m_skew_label, 1, wxEXPAND); @@ -509,7 +501,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : } }); m_main_grid_sizer->Add(m_reset_skew_button); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE m_check_inch = new wxCheckBox(parent, wxID_ANY, _L("Inches")); m_check_inch->SetFont(wxGetApp().normal_font()); @@ -651,21 +643,15 @@ void ObjectManipulation::update_settings_value(const Selection& selection) const GLVolume* volume = selection.get_first_volume(); #if !ENABLE_WORLD_COORDINATE m_new_position = volume->get_instance_offset(); -#endif // !ENABLE_WORLD_COORDINATE -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. -#if ENABLE_WORLD_COORDINATE - if (is_world_coordinates() && !m_uniform_scale && -#else if (m_world_coordinates && ! m_uniform_scale && -#endif // ENABLE_WORLD_COORDINATE ! Geometry::is_rotation_ninety_degrees(volume->get_instance_rotation())) { // Manipulating an instance in the world coordinate system, rotation is not multiples of ninety degrees, therefore enforce uniform scaling. m_uniform_scale = true; m_lock_bnt->SetLock(true); } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE if (is_world_coordinates()) { @@ -674,32 +660,19 @@ void ObjectManipulation::update_settings_value(const Selection& selection) if (m_world_coordinates) { #endif // ENABLE_WORLD_COORDINATE m_new_rotate_label_string = L("Rotate"); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE - m_new_scale_label_string = L("Scale"); -#endif // ENABLE_WORLD_COORDINATE -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); m_new_size = selection.get_scaled_instance_bounding_box().size(); -#if ENABLE_WORLD_COORDINATE && !ENABLE_TRANSFORMATIONS_BY_MATRICES - m_new_scale = Vec3d(100.0, 100.0, 100.0); -#else m_new_scale = m_new_size.cwiseQuotient(selection.get_unscaled_instance_bounding_box().size()) * 100.0; -#endif // ENABLE_WORLD_COORDINATE && !ENABLE_TRANSFORMATIONS_BY_MATRICES } else { #if ENABLE_WORLD_COORDINATE m_new_move_label_string = L("Translate"); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotate_label_string = L("Rotate"); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_position = Vec3d::Zero(); -#endif // ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); #else m_new_rotation = volume->get_instance_rotation() * (180.0 / M_PI); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#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; } @@ -731,28 +704,15 @@ void ObjectManipulation::update_settings_value(const Selection& selection) m_new_position = offset; m_new_rotate_label_string = L("Rotate"); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - m_new_scale_label_string = L("Scale"); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); m_new_size = volume->transformed_convex_hull_bounding_box(trafo.get_matrix()).size(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES 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; -#else - m_new_scale = Vec3d(100.0, 100.0, 100.0); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else if (is_local_coordinates()) { m_new_move_label_string = L("Translate"); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotate_label_string = L("Rotate"); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_position = Vec3d::Zero(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_rotation = Vec3d::Zero(); -#else - m_new_rotation = volume->get_volume_rotation() * (180.0 / M_PI); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_new_scale = volume->get_volume_scaling_factor() * 100.0; m_new_size = volume->get_volume_scaling_factor().cwiseProduct(volume->bounding_box().size()); } @@ -760,19 +720,10 @@ void ObjectManipulation::update_settings_value(const Selection& selection) #endif // ENABLE_WORLD_COORDINATE m_new_position = volume->get_volume_offset(); m_new_rotate_label_string = L("Rotate"); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE - m_new_scale_label_string = L("Scale"); -#endif // ENABLE_WORLD_COORDINATE -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES 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(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES 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; -#else - m_new_scale = Vec3d(100.0, 100.0, 100.0); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } #else m_new_scale = volume->get_volume_scaling_factor() * 100.0; @@ -843,29 +794,20 @@ void ObjectManipulation::update_if_dirty() update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); } -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE - Selection::EUniformScaleRequiredReason reason; - if (selection.requires_uniform_scale(&reason)) { - wxString tooltip = _L("You cannot use non-uniform scaling mode for multiple objects/parts selection"); - m_lock_bnt->SetToolTip(tooltip); -#else +#if !ENABLE_WORLD_COORDINATE if (selection.requires_uniform_scale()) { m_lock_bnt->SetLock(true); m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); m_lock_bnt->disable(); -#endif // ENABLE_WORLD_COORDINATE } else { -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE m_lock_bnt->SetLock(m_uniform_scale); m_lock_bnt->SetToolTip(wxEmptyString); m_lock_bnt->enable(); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES - #if !ENABLE_WORLD_COORDINATE + } + { int new_selection = m_world_coordinates ? 0 : 1; if (m_word_local_combo->GetSelection() != new_selection) @@ -898,35 +840,18 @@ void ObjectManipulation::update_reset_buttons_visibility() bool show_rotation = false; bool show_scale = false; bool show_drop_to_bed = false; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - bool show_skew = false; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES + bool show_skew = false; + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { -#else - if ((m_coordinates_type == ECoordinatesType::World && selection.is_single_full_instance()) || - (m_coordinates_type == ECoordinatesType::Instance && selection.is_single_volume_or_modifier())) { -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() : get_volume_min_z(*selection.get_first_volume()); show_drop_to_bed = std::abs(min_z) > EPSILON; -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - } - - if (m_coordinates_type == ECoordinatesType::Local && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier())) { -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES const GLVolume* volume = selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES Transform3d rotation = Transform3d::Identity(); Transform3d scale = Transform3d::Identity(); Geometry::Transformation skew; -#else - Vec3d rotation = Vec3d::Zero(); - Vec3d scale = Vec3d::Ones(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { const GLVolume* volume = selection.get_first_volume(); @@ -936,7 +861,7 @@ void ObjectManipulation::update_reset_buttons_visibility() #endif // ENABLE_WORLD_COORDINATE if (selection.is_single_full_instance()) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& trafo = volume->get_instance_transformation(); rotation = trafo.get_rotation_matrix(); scale = trafo.get_scaling_factor_matrix(); @@ -951,13 +876,11 @@ void ObjectManipulation::update_reset_buttons_visibility() #else rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if !ENABLE_WORLD_COORDINATE min_z = selection.get_scaled_instance_bounding_box().min.z(); -#endif // !ENABLE_WORLD_COORDINATE +#endif // ENABLE_WORLD_COORDINATE } else { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& trafo = volume->get_volume_transformation(); rotation = trafo.get_rotation_matrix(); scale = trafo.get_scaling_factor_matrix(); @@ -967,29 +890,25 @@ void ObjectManipulation::update_reset_buttons_visibility() #else rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if !ENABLE_WORLD_COORDINATE min_z = get_volume_min_z(*volume); -#endif // !ENABLE_WORLD_COORDINATE +#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#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()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if !ENABLE_WORLD_COORDINATE show_drop_to_bed = std::abs(min_z) > SINKING_Z_THRESHOLD; -#endif // !ENABLE_WORLD_COORDINATE +#endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed, show_skew] { #else wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed] { -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // There is a case (under OSX), when this function is called after the Manipulation panel is hidden // So, let check if Manipulation panel is still shown for this moment if (!this->IsShown()) @@ -997,10 +916,10 @@ void ObjectManipulation::update_reset_buttons_visibility() m_reset_rotation_button->Show(show_rotation); m_reset_scale_button->Show(show_scale); m_drop_to_bed_button->Show(show_drop_to_bed); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE m_reset_skew_button->Show(show_skew); m_skew_label->Show(show_skew); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time Sidebar& panel = wxGetApp().sidebar(); @@ -1131,7 +1050,6 @@ void ObjectManipulation::change_position_value(int axis, double value) Selection& selection = canvas->get_selection(); selection.setup_cache(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES TransformationType trafo_type; trafo_type.set_relative(); switch (get_coordinates_type()) @@ -1141,9 +1059,6 @@ void ObjectManipulation::change_position_value(int axis, double value) default: { break; } } selection.translate(position - m_cache.position, trafo_type); -#else - selection.translate(position - m_cache.position, get_coordinates_type()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else selection.translate(position - m_cache.position, selection.requires_local_axes()); #endif // ENABLE_WORLD_COORDINATE @@ -1166,21 +1081,13 @@ void ObjectManipulation::change_rotation_value(int axis, double value) Selection& selection = canvas->get_selection(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES TransformationType transformation_type; transformation_type.set_relative(); -#else - TransformationType transformation_type(TransformationType::World_Relative_Joint); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (selection.is_single_full_instance()) transformation_type.set_independent(); - if (is_local_coordinates()) { + if (is_local_coordinates()) transformation_type.set_local(); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - transformation_type.set_absolute(); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES - } if (is_instance_coordinates()) transformation_type.set_instance(); @@ -1286,28 +1193,10 @@ void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const else if (is_instance_coordinates()) transformation_type.set_instance(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES if (!selection.is_single_full_instance() && !selection.is_single_volume_or_modifier()) transformation_type.set_relative(); const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; -#else - if (!is_local_coordinates()) - transformation_type.set_relative(); - - bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); - Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; - - if (!uniform_scale && is_world_coordinates()) { - if (selection.is_single_full_instance()) - scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); - else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()).inverse(); - scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); - } - } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else TransformationType transformation_type(TransformationType::World_Relative_Joint); if (selection.is_single_full_instance()) { @@ -1336,28 +1225,7 @@ void ObjectManipulation::do_size(int axis, const Vec3d& scale) const else if (is_instance_coordinates()) transformation_type.set_instance(); -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - if (!is_local_coordinates()) - transformation_type.set_relative(); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES - -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; -#else - bool uniform_scale = m_uniform_scale || selection.requires_uniform_scale(); - Vec3d scaling_factor = uniform_scale ? scale(axis) * Vec3d::Ones() : scale; - - if (!uniform_scale && is_world_coordinates()) { - if (selection.is_single_full_instance()) - scaling_factor = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse() * scaling_factor).cwiseAbs(); - else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()).inverse(); - scaling_factor = (mv * mi * scaling_factor).cwiseAbs(); - } - } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - + const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; selection.setup_cache(); selection.scale(scaling_factor, transformation_type); wxGetApp().plater()->canvas3D()->do_scale(L("Set Size")); @@ -1400,30 +1268,16 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE - if (!use_uniform_scale) { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - int res = selection.bake_transform_if_needed(); - if (res == -1) { - // Enforce uniform scaling. - m_lock_bnt->SetLock(true); - return; - } - else if (res == 0) -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES - // Recalculate cached values at this panel, refresh the screen. - this->UpdateAndShow(true); - } + if (!use_uniform_scale) + // Recalculate cached values at this panel, refresh the screen. + this->UpdateAndShow(true); m_uniform_scale = use_uniform_scale; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES set_dirty(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); if (selection.is_single_full_instance() && m_world_coordinates && !use_uniform_scale) { // Verify whether the instance rotation is multiples of 90 degrees, so that the scaling in world coordinates is possible. // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one @@ -1493,9 +1347,9 @@ void ObjectManipulation::msw_rescale() m_mirror_bitmap_hidden.msw_rescale(); m_reset_scale_button->msw_rescale(); m_reset_rotation_button->msw_rescale(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE m_reset_skew_button->msw_rescale(); -#endif /// ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif /// ENABLE_WORLD_COORDINATE m_drop_to_bed_button->msw_rescale(); m_lock_bnt->msw_rescale(); @@ -1535,9 +1389,9 @@ void ObjectManipulation::sys_color_changed() m_mirror_bitmap_hidden.msw_rescale(); m_reset_scale_button->msw_rescale(); m_reset_rotation_button->msw_rescale(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE m_reset_skew_button->msw_rescale(); -#endif /// ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE m_drop_to_bed_button->msw_rescale(); m_lock_bnt->msw_rescale(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index e25e05f60..cfa43b43a 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -122,9 +122,9 @@ private: // Non-owning pointers to the reset buttons, so we can hide and show them. ScalableButton* m_reset_scale_button{ nullptr }; ScalableButton* m_reset_rotation_button{ nullptr }; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE ScalableButton* m_reset_skew_button{ nullptr }; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE ScalableButton* m_drop_to_bed_button{ nullptr }; wxCheckBox* m_check_inch {nullptr}; @@ -179,9 +179,9 @@ private: wxFlexGridSizer* m_main_grid_sizer; wxFlexGridSizer* m_labels_grid_sizer; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE wxStaticText* m_skew_label{ nullptr }; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // sizers, used for msw_rescale wxBoxSizer* m_word_local_combo_sizer; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index a52c85d67..49e97ee1f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -341,11 +341,11 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) ++mesh_id; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d trafo_matrix = mi->get_matrix_no_offset() * mv->get_matrix_no_offset(); #else const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast().normalized(); Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast().normalized(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 9bac7f293..f854edb81 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -228,11 +228,11 @@ void GLGizmoFlatten::update_planes() } ch = ch.convex_hull_3d(); m_planes.clear(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d inst_matrix = mo->instances.front()->get_matrix_no_offset(); #else const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Following constants are used for discarding too small polygons. const float minimal_area = 5.f; // in square mm (world coordinates) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 8ef23d538..7272a5ef7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -121,11 +121,11 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) Geometry::Transformation trafo = vol->get_instance_transformation() * vol->get_volume_transformation(); #if ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_scaling_factor_matrix().inverse(); #else const Transform3d instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE const Transform3d instance_matrix = Geometry::translation_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * trafo.get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 04ccfb4c8..79aed06b9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -98,19 +98,11 @@ void GLGizmoMove3D::on_start_dragging() m_starting_drag_position = m_center + m_grabbers[m_hover_id].center; else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center; -#else - m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center; -#else - m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } m_starting_box_center = m_center; m_starting_box_bottom_center = m_center; @@ -141,7 +133,6 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data) Selection &selection = m_parent.get_selection(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES TransformationType trafo_type; trafo_type.set_relative(); switch (wxGetApp().obj_manipul()->get_coordinates_type()) @@ -151,9 +142,6 @@ void GLGizmoMove3D::on_dragging(const UpdateData& data) default: { break; } } selection.translate(m_displacement, trafo_type); -#else - selection.translate(m_displacement, wxGetApp().obj_manipul()->get_coordinates_type()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else selection.translate(m_displacement); #endif // ENABLE_WORLD_COORDINATE @@ -529,17 +517,9 @@ Transform3d GLGizmoMove3D::local_transform(const Selection& selection) const Transform3d ret = Geometry::assemble_transform(m_center); if (!wxGetApp().obj_manipul()->is_world_coordinates()) { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); -#else - Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix(); -#else - orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES ret = ret * orient_matrix; } return ret; @@ -569,12 +549,8 @@ void GLGizmoMove3D::calc_selection_box_and_center() } else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES 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()); -#else - m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_center = v.world_matrix() * m_bounding_box.center(); } else { @@ -584,14 +560,9 @@ void GLGizmoMove3D::calc_selection_box_and_center() const GLVolume& v = *selection.get_volume(id); m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES 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(); -#else - m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); - m_center = selection.get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } } #endif // ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index c7731f1ff..4e03abb33 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -291,11 +291,11 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const return; #endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_scaling_factor_matrix().inverse(); #else const Transform3d complete_scaling_matrix_inverse = Geometry::Transformation(trafo).get_matrix(true, true, false, true).inverse(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE const bool is_left_handed = Geometry::Transformation(trafo).is_left_handed(); #if !ENABLE_GL_SHADERS_ATTRIBUTES @@ -512,11 +512,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const Selection &selection = m_parent.get_selection(); const ModelObject *mo = m_c->selection_info()->model_object(); const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix_no_offset() * mo->volumes[m_rr.mesh_id]->get_matrix_no_offset(); #else const Transform3d trafo_matrix_not_translate = mi->get_transformation().get_matrix(true) * mo->volumes[m_rr.mesh_id]->get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE const Transform3d trafo_matrix = mi->get_transformation().get_matrix() * mo->volumes[m_rr.mesh_id]->get_matrix(); m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, this->get_clipping_plane_in_volume_coordinates(trafo_matrix), m_smart_fill_angle, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true); @@ -555,11 +555,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const ModelObject *mo = m_c->selection_info()->model_object(); const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; const Transform3d instance_trafo = mi->get_transformation().get_matrix(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix_no_offset(); #else const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Precalculate transformations of individual meshes. std::vector trafo_matrices; @@ -567,11 +567,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous for (const ModelVolume *mv : mo->volumes) if (mv->is_model_part()) { trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix_no_offset()); #else trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } std::vector> projected_mouse_positions_by_mesh = get_projected_mouse_positions(mouse_position, 1., trafo_matrices); @@ -653,11 +653,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const ModelObject *mo = m_c->selection_info()->model_object(); const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; const Transform3d instance_trafo = mi->get_transformation().get_matrix(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix_no_offset(); #else const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Precalculate transformations of individual meshes. std::vector trafo_matrices; @@ -665,11 +665,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous for (const ModelVolume *mv : mo->volumes) if (mv->is_model_part()) { trafo_matrices.emplace_back(instance_trafo * mv->get_matrix()); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix_no_offset()); #else trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } // Now "click" into all the prepared points and spill paint around them. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index b72c6761f..3a4f77837 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -287,27 +287,19 @@ void GLGizmoRotate::on_render_for_picking() #if ENABLE_WORLD_COORDINATE void GLGizmoRotate::init_data_from_selection(const Selection& selection) { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES ECoordinatesType coordinates_type; if (selection.is_wipe_tower()) coordinates_type = ECoordinatesType::Local; else coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); -#else - const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES 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(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES 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()); -#else - m_bounding_box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_center = v.world_matrix() * m_bounding_box.center(); } else { @@ -317,14 +309,9 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) const GLVolume& v = *selection.get_volume(id); m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES 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(); -#else - m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); - m_center = selection.get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } m_radius = Offset + m_bounding_box.radius(); @@ -335,25 +322,13 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection) if (coordinates_type == ECoordinatesType::World) m_orient_matrix = Transform3d::Identity(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES else if (coordinates_type == ECoordinatesType::Local && (selection.is_wipe_tower() || selection.is_single_volume_or_modifier())) { -#else - else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) { -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix(); -#else - m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_orient_matrix = v.get_instance_transformation().get_rotation_matrix(); -#else - m_orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } } #endif // ENABLE_WORLD_COORDINATE @@ -762,20 +737,20 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const { case X: { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE ret = Geometry::rotation_transform(0.5 * PI * Vec3d::UnitY()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()); #else ret = Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE break; } case Y: { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE ret = Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitY()); #else ret = Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitY()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE break; } default: @@ -889,25 +864,22 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) // Apply new temporary rotations #if ENABLE_WORLD_COORDINATE TransformationType transformation_type; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES if (m_parent.get_selection().is_wipe_tower()) transformation_type = TransformationType::Instance_Relative_Joint; else { -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES switch (wxGetApp().obj_manipul()->get_coordinates_type()) { default: - case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; } + case ECoordinatesType::World: { transformation_type = TransformationType::World_Relative_Joint; break; } case ECoordinatesType::Instance: { transformation_type = TransformationType::Instance_Relative_Joint; break; } - case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; } + case ECoordinatesType::Local: { transformation_type = TransformationType::Local_Relative_Joint; break; } } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else TransformationType transformation_type(TransformationType::World_Relative_Joint); #endif // ENABLE_WORLD_COORDINATE - if (mouse_event.AltDown()) transformation_type.set_independent(); + if (mouse_event.AltDown()) + transformation_type.set_independent(); m_parent.get_selection().rotate(get_rotation(), transformation_type); } return use_grabbers(mouse_event); @@ -915,26 +887,26 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event) void GLGizmoRotate3D::data_changed() { if (m_parent.get_selection().is_wipe_tower()) { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE const DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; const float wipe_tower_rotation_angle = dynamic_cast( config.option("wipe_tower_rotation_angle"))->value; set_rotation(Vec3d(0., 0., (M_PI / 180.) * wipe_tower_rotation_angle)); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE m_gizmos[0].disable_grabber(); m_gizmos[1].disable_grabber(); } else { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE set_rotation(Vec3d::Zero()); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE m_gizmos[0].enable_grabber(); m_gizmos[1].enable_grabber(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE set_rotation(Vec3d::Zero()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } bool GLGizmoRotate3D::on_init() diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 1d87d772a..f287a69f7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -41,7 +41,7 @@ GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filen std::string GLGizmoScale3D::get_tooltip() const { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Vec3d scale = 100.0 * m_scale; #else const Selection& selection = m_parent.get_selection(); @@ -49,13 +49,9 @@ std::string GLGizmoScale3D::get_tooltip() const Vec3d scale = 100.0 * Vec3d::Ones(); if (selection.is_single_full_instance()) scale = 100.0 * selection.get_first_volume()->get_instance_scaling_factor(); -#if ENABLE_WORLD_COORDINATE - else if (selection.is_single_volume_or_modifier()) -#else else if (selection.is_single_modifier() || selection.is_single_volume()) -#endif // ENABLE_WORLD_COORDINATE scale = 100.0 * selection.get_first_volume()->get_volume_scaling_factor(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE if (m_hover_id == 0 || m_hover_id == 1 || m_grabbers[0].dragging || m_grabbers[1].dragging) return "X: " + format(scale.x(), 4) + "%"; @@ -79,54 +75,29 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) { if (mouse_event.Dragging()) { if (m_dragging) { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE - int res = 1; - if (m_scale.x() != m_scale.y() || m_scale.x() != m_scale.z()) - res = m_parent.get_selection().bake_transform_if_needed(); - - if (res != 1) { - do_stop_dragging(true); - return true; - } - else { -#endif // ENABLE_WORLD_COORDINATE -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES // Apply new temporary scale factors #if ENABLE_WORLD_COORDINATE - TransformationType transformation_type; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - if (wxGetApp().obj_manipul()->is_local_coordinates()) - transformation_type.set_local(); - else if (wxGetApp().obj_manipul()->is_instance_coordinates()) - transformation_type.set_instance(); + 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(); + transformation_type.set_relative(); #else - if (!wxGetApp().obj_manipul()->is_world_coordinates()) - transformation_type.set_local(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#else - TransformationType transformation_type(TransformationType::Local_Absolute_Joint); + TransformationType transformation_type(TransformationType::Local_Absolute_Joint); #endif // ENABLE_WORLD_COORDINATE - if (mouse_event.AltDown()) transformation_type.set_independent(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES - m_parent.get_selection().scale_and_translate(m_scale, m_offset, transformation_type); -#else - Selection& selection = m_parent.get_selection(); - selection.scale(m_scale, transformation_type); + if (mouse_event.AltDown()) + transformation_type.set_independent(); + #if ENABLE_WORLD_COORDINATE - if (mouse_event.CmdDown()) selection.translate(m_offset, wxGetApp().obj_manipul()->get_coordinates_type()); + m_parent.get_selection().scale_and_translate(m_scale, m_offset, transformation_type); #else - if (mouse_event.CmdDown()) selection.translate(m_offset, true); + Selection& selection = m_parent.get_selection(); + selection.scale(m_scale, transformation_type); + if (mouse_event.CmdDown()) selection.translate(m_offset, true); #endif // ENABLE_WORLD_COORDINATE -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE - } -#endif // ENABLE_WORLD_COORDINATE -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES } } return use_grabbers(mouse_event); @@ -134,41 +105,29 @@ bool GLGizmoScale3D::on_mouse(const wxMouseEvent &mouse_event) void GLGizmoScale3D::data_changed() { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - const Selection &selection = m_parent.get_selection(); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #if ENABLE_WORLD_COORDINATE -#else - bool enable_scale_xyz = selection.is_single_full_instance() || - selection.is_single_volume() || - selection.is_single_modifier(); -#endif // ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES set_scale(Vec3d::Ones()); #else -#if ENABLE_WORLD_COORDINATE - if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { -#else + const Selection& selection = m_parent.get_selection(); + bool enable_scale_xyz = selection.is_single_full_instance() || + selection.is_single_volume() || + selection.is_single_modifier(); + for (unsigned int i = 0; i < 6; ++i) m_grabbers[i].enabled = enable_scale_xyz; if (enable_scale_xyz) { -#endif // ENABLE_WORLD_COORDINATE // all volumes in the selection belongs to the same instance, any of // them contains the needed data, so we take the first const GLVolume* volume = selection.get_first_volume(); if (selection.is_single_full_instance()) set_scale(volume->get_instance_scaling_factor()); -#if ENABLE_WORLD_COORDINATE - else if (selection.is_single_volume_or_modifier()) -#else else if (selection.is_single_volume() || selection.is_single_modifier()) -#endif // ENABLE_WORLD_COORDINATE set_scale(volume->get_volume_scaling_factor()); } else set_scale(Vec3d::Ones()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } bool GLGizmoScale3D::on_init() @@ -279,24 +238,15 @@ void GLGizmoScale3D::on_render() } #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_scaling_factor_matrix()); -#else - m_bounding_box = m_bounding_box.transformed(selection.get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #endif // ENABLE_WORLD_COORDINATE // gets transform from first selected volume const GLVolume& v = *selection.get_first_volume(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES 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(); -#else - m_grabbers_transform = v.get_instance_transformation().get_matrix(false, false, true) * Geometry::assemble_transform(m_bounding_box.center()); - m_center = selection.get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false) * m_bounding_box.center(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES m_instance_center = v.get_instance_offset(); } else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_instance_coordinates()) { @@ -313,14 +263,9 @@ void GLGizmoScale3D::on_render() #endif // ENABLE_WORLD_COORDINATE const GLVolume& v = *selection.get_first_volume(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES 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()); -#else - m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, false, false, true))); - Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES trafo.set_offset(v.world_matrix().translation()); m_grabbers_transform = trafo.get_matrix(); m_center = v.world_matrix() * m_bounding_box.center(); @@ -328,14 +273,9 @@ void GLGizmoScale3D::on_render() } else if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES 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()); -#else - m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true))); - Geometry::Transformation trafo(v.get_instance_transformation().get_matrix(true, false, true, true) * v.get_volume_transformation().get_matrix(true, false, true, true)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES trafo.set_offset(v.world_matrix().translation()); m_grabbers_transform = trafo.get_matrix(); m_center = v.world_matrix() * m_bounding_box.center(); @@ -736,7 +676,7 @@ void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int } #endif // ENABLE_LEGACY_OPENGL_REMOVAL -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { double ratio = calc_ratio(data); @@ -790,76 +730,16 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) #else void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) { -#if ENABLE_WORLD_COORDINATE - double ratio = calc_ratio(data); - if (ratio > 0.0) { - Vec3d curr_scale = m_scale; - Vec3d starting_scale = m_starting.scale; - const Selection& selection = m_parent.get_selection(); - const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type(); - if (coordinates_type == ECoordinatesType::World) { - if (selection.is_single_full_instance()) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()); - curr_scale = (m * curr_scale).cwiseAbs(); - starting_scale = (m * starting_scale).cwiseAbs(); - } - else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()); - const Transform3d m = mi * mv; - curr_scale = (m * curr_scale).cwiseAbs(); - starting_scale = (m * starting_scale).cwiseAbs(); - } - } - - curr_scale(axis) = starting_scale(axis) * ratio; - - if (coordinates_type == ECoordinatesType::World) { - if (selection.is_single_full_instance()) - m_scale = (Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse() * curr_scale).cwiseAbs(); - else if (selection.is_single_volume_or_modifier()) { - const Transform3d mi = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_instance_rotation()).inverse(); - const Transform3d mv = Geometry::assemble_transform(Vec3d::Zero(), selection.get_first_volume()->get_volume_rotation()).inverse(); - m_scale = (mv * mi * curr_scale).cwiseAbs(); - } - else - m_scale = curr_scale; - } - else - m_scale = curr_scale; -#else const double ratio = calc_ratio(data); if (ratio > 0.0) { m_scale(axis) = m_starting.scale(axis) * ratio; -#endif // ENABLE_WORLD_COORDINATE if (m_starting.ctrl_down) { -#if ENABLE_WORLD_COORDINATE - double local_offset = 0.5 * (ratio - 1.0) * m_starting.box.size()(axis); -#else double local_offset = 0.5 * (m_scale(axis) - m_starting.scale(axis)) * m_starting.box.size()(axis); -#endif // ENABLE_WORLD_COORDINATE if (m_hover_id == 2 * axis) local_offset *= -1.0; -#if ENABLE_WORLD_COORDINATE - Vec3d center_offset = m_starting.instance_center - m_starting.center; - if (selection.is_single_full_instance() && coordinates_type != ECoordinatesType::World) { - const Transform3d m = Geometry::rotation_transform(selection.get_first_volume()->get_instance_rotation()).inverse(); - center_offset = m * center_offset; - } - - local_offset += (ratio - 1.0) * center_offset(axis); - - switch (axis) - { - case X: { m_offset = local_offset * Vec3d::UnitX(); break; } - case Y: { m_offset = local_offset * Vec3d::UnitY(); break; } - case Z: { m_offset = local_offset * Vec3d::UnitZ(); break; } - default: { m_offset = Vec3d::Zero(); break; } - } -#else Vec3d local_offset_vec; switch (axis) { @@ -870,15 +750,14 @@ void GLGizmoScale3D::do_scale_along_axis(Axis axis, const UpdateData& data) } m_offset = m_offsets_transform * local_offset_vec; -#endif // ENABLE_WORLD_COORDINATE } else m_offset = Vec3d::Zero(); } } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void GLGizmoScale3D::do_scale_uniform(const UpdateData & data) { const double ratio = calc_ratio(data); @@ -926,32 +805,10 @@ void GLGizmoScale3D::do_scale_uniform(const UpdateData& data) const double ratio = calc_ratio(data); if (ratio > 0.0) { m_scale = m_starting.scale * ratio; - -#if ENABLE_WORLD_COORDINATE - if (m_starting.ctrl_down) { - m_offset = 0.5 * (ratio - 1.0) * m_starting.box.size(); - - if (m_hover_id == 6 || m_hover_id == 9) - m_offset.x() *= -1.0; - if (m_hover_id == 6 || m_hover_id == 7) - m_offset.y() *= -1.0; - - const Selection& selection = m_parent.get_selection(); - Vec3d center_offset = m_starting.instance_center - m_starting.center; - - if (selection.is_single_full_instance() && !wxGetApp().obj_manipul()->is_world_coordinates()) { - const Transform3d m = Geometry::rotation_transform(selection.get_first_volume()->get_instance_rotation()).inverse(); - center_offset = m * center_offset; - } - - m_offset += (ratio - 1.0) * center_offset; - } - else -#endif // ENABLE_WORLD_COORDINATE m_offset = Vec3d::Zero(); } } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE double GLGizmoScale3D::calc_ratio(const UpdateData& data) const { @@ -995,17 +852,9 @@ Transform3d GLGizmoScale3D::local_transform(const Selection& selection) const Transform3d ret = Geometry::assemble_transform(m_center); if (!wxGetApp().obj_manipul()->is_world_coordinates()) { const GLVolume& v = *selection.get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES Transform3d orient_matrix = v.get_instance_transformation().get_rotation_matrix(); -#else - Transform3d orient_matrix = v.get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES if (selection.is_single_volume_or_modifier() && wxGetApp().obj_manipul()->is_local_coordinates()) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = orient_matrix * v.get_volume_transformation().get_rotation_matrix(); -#else - orient_matrix = orient_matrix * v.get_volume_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES ret = ret * orient_matrix; } return ret; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 754b588b4..b2a9b7a23 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -67,11 +67,11 @@ public: void set_snap_step(double step) { m_snap_step = step; } const Vec3d& get_scale() const { return m_scale; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; m_offset = Vec3d::Zero(); } #else void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE_SCALE_REVISITED const Vec3d& get_starting_scale() const { return m_starting.scale; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 25b918e4b..987fd325f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -148,11 +148,11 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const GLVolume* vol = selection.get_first_volume(); Geometry::Transformation transformation(vol->get_instance_transformation().get_matrix() * vol->get_volume_transformation().get_matrix()); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d instance_scaling_matrix_inverse = transformation.get_scaling_factor_matrix().inverse(); #else const Transform3d& instance_scaling_matrix_inverse = transformation.get_matrix(true, true, false, true).inverse(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_GL_SHADERS_ATTRIBUTES const Transform3d instance_matrix = Geometry::assemble_transform(m_c->selection_info()->get_sla_shift() * Vec3d::UnitZ()) * transformation.get_matrix(); const Camera& camera = wxGetApp().plater()->get_camera(); diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 777a4d7ba..7782b8d75 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -112,11 +112,11 @@ void MeshClipper::render_cut() void MeshClipper::recalculate_triangles() { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3f instance_matrix_no_translation_no_scaling = m_trafo.get_rotation_matrix().cast(); #else const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Calculate clipping plane normal in mesh coordinates. const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast(); const Vec3d up = up_noscale.cast().cwiseProduct(m_trafo.get_scaling_factor()); @@ -307,11 +307,11 @@ std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo { std::vector out; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Transform3d instance_matrix_no_translation_no_scaling = trafo.get_rotation_matrix(); #else const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE Vec3d direction_to_camera = -camera.get_dir_forward(); Vec3d direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera).normalized().eval(); direction_to_camera_mesh = direction_to_camera_mesh.cwiseProduct(trafo.get_scaling_factor()); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c9cc7fc22..d23a3dd13 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2085,9 +2085,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); }); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE view3D_canvas->Bind(EVT_GLCANVAS_RESET_SKEW, [this](SimpleEvent&) { update(); }); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); }); view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event& evt) { this->sidebar->enable_buttons(evt.data); }); view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_GEOMETRY, &priv::on_update_geometry, this); @@ -3495,11 +3495,11 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); new_volume->set_transformation(old_volume->get_transformation()); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); #else new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); if (old_volume->source.is_converted_from_inches) new_volume->convert_from_imperial_units(); @@ -3854,7 +3854,7 @@ void Plater::priv::reload_from_disk() new_volume->config.apply(old_volume->config); new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE new_volume->set_transformation(Geometry::translation_transform(old_volume->source.transform.get_offset()) * old_volume->get_transformation().get_matrix_no_offset() * old_volume->source.transform.get_matrix_no_offset()); new_volume->translate(new_volume->get_transformation().get_matrix_no_offset() * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); @@ -3863,7 +3863,7 @@ void Plater::priv::reload_from_disk() old_volume->get_transformation().get_matrix(true) * old_volume->source.transform.get_matrix(true)); new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE new_volume->source.object_idx = old_volume->source.object_idx; new_volume->source.volume_idx = old_volume->source.volume_idx; assert(!old_volume->source.is_converted_from_inches || !old_volume->source.is_converted_from_meters); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 02edd9c31..61ffd4b19 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -41,18 +41,18 @@ Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transform , scaling_factor(transform.get_scaling_factor()) , mirror(transform.get_mirror()) , full_matrix(transform.get_matrix()) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE , transform(transform) , rotation_matrix(transform.get_rotation_matrix()) , scale_matrix(transform.get_scaling_factor_matrix()) , mirror_matrix(transform.get_mirror_matrix()) -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE { -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); - scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); - mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES + scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); + mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror); +#endif // !ENABLE_WORLD_COORDINATE } Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) @@ -599,76 +599,15 @@ bool Selection::matches(const std::vector& volume_idxs) const return count == (unsigned int)m_list.size(); } -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES -#if ENABLE_WORLD_COORDINATE -bool Selection::requires_uniform_scale(EUniformScaleRequiredReason* reason) const -#else +#if !ENABLE_WORLD_COORDINATE bool Selection::requires_uniform_scale() const -#endif // ENABLE_WORLD_COORDINATE { -#if ENABLE_WORLD_COORDINATE - if (is_empty()) - return false; - - ECoordinatesType coord_type = wxGetApp().obj_manipul()->get_coordinates_type(); - if (is_single_volume_or_modifier()) { - if (coord_type == ECoordinatesType::World) { - if (!Geometry::is_rotation_ninety_degrees(Geometry::Transformation(get_first_volume()->world_matrix()).get_rotation())) { - if (reason != nullptr) - *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_World; - return true; - } - } - else if (coord_type == ECoordinatesType::Instance) { - if (!Geometry::is_rotation_ninety_degrees(get_first_volume()->get_volume_rotation())) { - if (reason != nullptr) - *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; - return true; - } - } - return false; - } - else if (is_single_full_instance()) { - if (coord_type == ECoordinatesType::World) { - if (!Geometry::is_rotation_ninety_degrees(get_first_volume()->get_instance_rotation())) { - if (reason != nullptr) - *reason = EUniformScaleRequiredReason::InstanceNotAxisAligned_World; - return true; - } - else { - for (unsigned int i : m_list) { - if (!Geometry::is_rotation_ninety_degrees((*m_volumes)[i]->get_volume_rotation())) { - if (reason != nullptr) - *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; - return true; - } - } - } - } - else { - for (unsigned int i : m_list) { - if (!Geometry::is_rotation_ninety_degrees((*m_volumes)[i]->get_volume_rotation())) { - if (reason != nullptr) - *reason = EUniformScaleRequiredReason::VolumeNotAxisAligned_Instance; - return true; - } - } - } - return false; - } - - if (reason != nullptr) - *reason = EUniformScaleRequiredReason::MultipleSelection; - - return true; -#else if (is_single_full_instance() || is_single_modifier() || is_single_volume()) return false; return true; -#endif // ENABLE_WORLD_COORDINATE } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE int Selection::get_object_idx() const { @@ -723,11 +662,11 @@ const BoundingBoxf3& Selection::get_unscaled_instance_bounding_box() const const GLVolume& volume = *(*m_volumes)[i]; if (volume.is_modifier) continue; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Transform3d trafo = volume.get_instance_transformation().get_matrix_no_scaling_factor() * volume.get_volume_transformation().get_matrix(); #else Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE trafo.translation().z() += volume.get_sla_shift_z(); (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); } @@ -757,7 +696,7 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const return *m_scaled_instance_bounding_box; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const BoundingBoxf3& Selection::get_full_unscaled_instance_bounding_box() const { assert(is_single_full_instance()); @@ -814,7 +753,7 @@ const BoundingBoxf3& Selection::get_full_unscaled_instance_local_bounding_box() } return *m_full_unscaled_instance_local_bounding_box; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Selection::setup_cache() { @@ -824,7 +763,7 @@ void Selection::setup_cache() set_caches(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void Selection::translate(const Vec3d& displacement, TransformationType transformation_type) { if (!m_valid) @@ -865,11 +804,7 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } #else -#if ENABLE_WORLD_COORDINATE -void Selection::translate(const Vec3d& displacement, ECoordinatesType type) -#else void Selection::translate(const Vec3d& displacement, bool local) -#endif // ENABLE_WORLD_COORDINATE { if (!m_valid) return; @@ -879,45 +814,16 @@ void Selection::translate(const Vec3d& displacement, bool local) for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; if (m_mode == Volume || v.is_wipe_tower) { -#if ENABLE_WORLD_COORDINATE - if (type == ECoordinatesType::Instance) -#else if (local) -#endif // ENABLE_WORLD_COORDINATE v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); -#if ENABLE_WORLD_COORDINATE - else if (type == ECoordinatesType::Local) { - const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d local_displacement = volume_data.get_volume_rotation_matrix() * displacement; - v.set_volume_offset(volume_data.get_volume_position() + local_displacement); - } -#endif // ENABLE_WORLD_COORDINATE else { -#if ENABLE_WORLD_COORDINATE - const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d local_displacement = (volume_data.get_instance_rotation_matrix() * volume_data.get_instance_scale_matrix() * volume_data.get_instance_mirror_matrix()).inverse() * displacement; - v.set_volume_offset(volume_data.get_volume_position() + local_displacement); -#else const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); -#endif // ENABLE_WORLD_COORDINATE } } else if (m_mode == Instance) { -#if ENABLE_WORLD_COORDINATE - if (is_from_fully_selected_instance(i)) { - if (type == ECoordinatesType::Local) { - const VolumeCache& volume_data = m_cache.volumes_data[i]; - const Vec3d world_displacement = volume_data.get_instance_rotation_matrix() * displacement; - v.set_instance_offset(volume_data.get_instance_position() + world_displacement); - } - else - v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); - } -#else if (is_from_fully_selected_instance(i)) v.set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); -#endif // ENABLE_WORLD_COORDINATE else { const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; v.set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); @@ -937,10 +843,10 @@ void Selection::translate(const Vec3d& displacement, bool local) set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Rotate an object around one of the axes. Only one rotation component is expected to be changing. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) { if (!m_valid) @@ -1052,18 +958,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } else { // extracts rotations from the composed transformation -#if ENABLE_WORLD_COORDINATE - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - const Vec3d new_rotation = transformation_type.world() ? - Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()) : - transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_instance_rotation_matrix() * m); - const Vec3d relative_instance_offset = m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center; - if (rot_axis_max == 2 && transformation_type.joint() && !relative_instance_offset.isApprox(Vec3d::Zero())) { - // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. - const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); - volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * relative_instance_offset); - } -#else const Vec3d new_rotation = transformation_type.world() ? Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); @@ -1072,9 +966,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); } -#endif // ENABLE_WORLD_COORDINATE - else if (!(m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center).isApprox(Vec3d::Zero())) - volume.set_instance_offset(m_cache.dragging_center + Geometry::assemble_transform(Vec3d::Zero(), new_rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix().inverse() * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); volume.set_instance_rotation(new_rotation); object_instance_first[volume.object_idx()] = i; @@ -1085,25 +976,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ GLVolume &v = *(*m_volumes)[i]; if (is_single_full_instance()) rotate_instance(v, i); -#if ENABLE_WORLD_COORDINATE - else if (is_single_volume_or_modifier()) { - if (transformation_type.local()) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - const Vec3d new_rotation = transformation_type.absolute() ? rotation : Geometry::extract_euler_angles(m_cache.volumes_data[i].get_volume_rotation_matrix() * m); - v.set_volume_rotation(new_rotation); - } - else if (transformation_type.instance()) { - const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - v.set_volume_rotation(Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix())); - } - else { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - m = m * m_cache.volumes_data[i].get_instance_rotation_matrix(); - m = m * m_cache.volumes_data[i].get_volume_rotation_matrix(); - m = m_cache.volumes_data[i].get_instance_rotation_matrix().inverse() * m; - v.set_volume_rotation(Geometry::extract_euler_angles(m)); - } -#else else if (is_single_volume() || is_single_modifier()) { if (transformation_type.independent()) v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation); @@ -1112,7 +984,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); v.set_volume_rotation(new_rotation); } -#endif // ENABLE_WORLD_COORDINATE } else { if (m_mode == Instance) @@ -1133,21 +1004,8 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } #if !DISABLE_INSTANCES_SYNCH -#if ENABLE_WORLD_COORDINATE - if (m_mode == Instance) { - SyncRotationType synch; - if (transformation_type.world() && rot_axis_max == 2) - synch = SyncRotationType::NONE; - else if (transformation_type.local()) - synch = SyncRotationType::FULL; - else - synch = SyncRotationType::GENERAL; - synchronize_unselected_instances(synch); - } -#else if (m_mode == Instance) synchronize_unselected_instances((rot_axis_max == 2) ? SyncRotationType::NONE : SyncRotationType::GENERAL); -#endif // ENABLE_WORLD_COORDINATE else if (m_mode == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -1166,7 +1024,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Selection::flattening_rotate(const Vec3d& normal) { @@ -1181,7 +1039,7 @@ void Selection::flattening_rotate(const Vec3d& normal) for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; // Normal transformed from the object coordinate space to the world coordinate space. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& old_inst_trafo = v.get_instance_transformation(); const Vec3d tnormal = old_inst_trafo.get_matrix().matrix().block(0, 0, 3, 3).inverse().transpose() * normal; // Additional rotation to align tnormal with the down vector in the world coordinate space. @@ -1195,7 +1053,7 @@ void Selection::flattening_rotate(const Vec3d& normal) // Additional rotation to align tnormal with the down vector in the world coordinate space. auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, -Vec3d::UnitZ()); v.set_instance_rotation(Geometry::extract_euler_angles(extra_rotation.toRotationMatrix() * m_cache.volumes_data[i].get_instance_rotation_matrix())); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } #if !DISABLE_INSTANCES_SYNCH @@ -1208,7 +1066,7 @@ void Selection::flattening_rotate(const Vec3d& normal) this->set_bounding_boxes_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void Selection::scale(const Vec3d& scale, TransformationType transformation_type) { scale_and_translate(scale, Vec3d::Zero(), transformation_type); @@ -1233,9 +1091,6 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type v.set_instance_scaling_factor(new_scale); } else { -#if ENABLE_WORLD_COORDINATE - v.set_instance_scaling_factor(scale); -#else if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { // Non-uniform scaling. Transform the scaling factors into the local coordinate system. // This is only possible, if the instance rotation is mulitples of ninety degrees. @@ -1244,14 +1099,9 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type } else v.set_instance_scaling_factor(scale); -#endif // ENABLE_WORLD_COORDINATE } } -#if ENABLE_WORLD_COORDINATE - else if (is_single_volume_or_modifier()) -#else else if (is_single_volume() || is_single_modifier()) -#endif // ENABLE_WORLD_COORDINATE v.set_volume_scaling_factor(scale); else { const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); @@ -1288,7 +1138,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type set_bounding_boxes_dirty(); wxGetApp().plater()->canvas3D()->requires_check_outside_state(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Selection::scale_to_fit_print_volume(const BuildVolume& volume) { @@ -1311,13 +1161,13 @@ void Selection::scale_to_fit_print_volume(const BuildVolume& volume) // center selection on print bed setup_cache(); offset.z() = -get_bounding_box().min.z(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE TransformationType trafo_type; trafo_type.set_relative(); translate(offset, trafo_type); #else translate(offset); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot wxGetApp().obj_manipul()->set_dirty(); @@ -1407,7 +1257,7 @@ void Selection::mirror(Axis axis) set_bounding_boxes_dirty(); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void Selection::scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type) { if (!m_valid) @@ -1586,7 +1436,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) this->set_bounding_boxes_dirty(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void Selection::translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement) { @@ -1596,11 +1446,11 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co for (unsigned int i : m_list) { GLVolume& v = *(*m_volumes)[i]; if (v.object_idx() == (int)object_idx && v.instance_idx() == (int)instance_idx) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE v.set_instance_transformation(Geometry::translation_transform(displacement) * v.get_instance_transformation().get_matrix()); #else v.set_instance_offset(v.get_instance_offset() + displacement); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } std::set done; // prevent processing volumes twice @@ -1633,11 +1483,11 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co if (v.object_idx() != object_idx || v.instance_idx() != (int)instance_idx) continue; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE v.set_instance_transformation(Geometry::translation_transform(displacement) * v.get_instance_transformation().get_matrix()); #else v.set_instance_offset(v.get_instance_offset() + displacement); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE done.insert(j); } } @@ -1821,13 +1671,8 @@ void Selection::render(float scale_factor) } else if (coordinates_type == ECoordinatesType::Local && is_single_volume_or_modifier()) { const GLVolume& v = *get_first_volume(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES box = v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_scaling_factor_matrix()); trafo = v.get_instance_transformation().get_matrix() * v.get_volume_transformation().get_matrix_no_scaling_factor(); -#else - box = v.transformed_convex_hull_bounding_box(v.get_instance_transformation().get_matrix(true, true, false, true) * v.get_volume_transformation().get_matrix(true, true, false, true)); - trafo = v.get_instance_transformation().get_matrix(false, false, true, false) * v.get_volume_transformation().get_matrix(false, false, true, false); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } else { const Selection::IndicesList& ids = get_volume_idxs(); @@ -1835,14 +1680,9 @@ void Selection::render(float scale_factor) const GLVolume& v = *get_volume(id); box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES const Geometry::Transformation inst_trafo = get_first_volume()->get_instance_transformation(); box = box.transformed(inst_trafo.get_scaling_factor_matrix()); trafo = inst_trafo.get_matrix_no_scaling_factor(); -#else - box = box.transformed(get_first_volume()->get_instance_transformation().get_matrix(true, true, false, true)); - trafo = get_first_volume()->get_instance_transformation().get_matrix(false, false, true, false); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES } render_bounding_box(box, trafo, ColorRGB::WHITE()); @@ -1929,12 +1769,10 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) #if ENABLE_GL_SHADERS_ATTRIBUTES const Transform3d base_matrix = Geometry::assemble_transform(get_bounding_box().center()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES -#if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE Transform3d orient_matrix = Transform3d::Identity(); #else glsafe(::glPushMatrix()); -#endif // ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE +#endif // ENABLE_GL_SHADERS_ATTRIBUTES #if ENABLE_WORLD_COORDINATE const Vec3d center = get_bounding_box().center(); @@ -1957,11 +1795,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) glsafe(::glTranslated(center.x(), center.y(), center.z())); #endif // !ENABLE_GL_SHADERS_ATTRIBUTES && !ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); -#else - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); #else if (!boost::starts_with(sidebar_field, "position")) { @@ -1999,19 +1833,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) if (!wxGetApp().obj_manipul()->is_world_coordinates()) { if (wxGetApp().obj_manipul()->is_local_coordinates()) { const GLVolume* v = (*m_volumes)[*m_list.begin()]; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = v->get_instance_transformation().get_rotation_matrix() * v->get_volume_transformation().get_rotation_matrix(); -#else - orient_matrix = v->get_instance_transformation().get_matrix(true, false, true, true) * v->get_volume_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES axes_center = (*m_volumes)[*m_list.begin()]->world_matrix().translation(); } else { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); -#else - orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES axes_center = (*m_volumes)[*m_list.begin()]->get_instance_offset(); } } @@ -2031,11 +1857,11 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) else { #if ENABLE_GL_SHADERS_ATTRIBUTES || ENABLE_WORLD_COORDINATE if (requires_local_axes()) -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation_matrix(); #else orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE #else glsafe(::glTranslated(center.x(), center.y(), center.z())); if (requires_local_axes()) { @@ -2917,11 +2743,11 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, GLS void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) #endif // ENABLE_GL_SHADERS_ATTRIBUTES { -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const bool uniform_scale = wxGetApp().obj_manipul()->get_uniform_scaling(); #else const bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_GL_SHADERS_ATTRIBUTES auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis, GLShaderProgram& shader, const Transform3d& matrix) { @@ -3190,7 +3016,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL const int instance_idx = volume_i->instance_idx(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& curr_inst_trafo_i = volume_i->get_instance_transformation(); const Vec3d curr_inst_rotation_i = curr_inst_trafo_i.get_rotation(); const Vec3d& curr_inst_scaling_factor_i = curr_inst_trafo_i.get_scaling_factor(); @@ -3200,7 +3026,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ const Vec3d& rotation = volume_i->get_instance_rotation(); const Vec3d& scaling_factor = volume_i->get_instance_scaling_factor(); const Vec3d& mirror = volume_i->get_instance_mirror(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Process unselected instances. for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { @@ -3214,7 +3040,7 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ if (volume_j->object_idx() != object_idx || volume_j->instance_idx() == instance_idx) continue; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Vec3d old_inst_rotation_j = m_cache.volumes_data[j].get_instance_transform().get_rotation(); assert(is_rotation_xy_synchronized(old_inst_rotation_i, old_inst_rotation_j)); const Geometry::Transformation& curr_inst_trafo_j = volume_j->get_instance_transformation(); @@ -3223,12 +3049,12 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ Vec3d new_inst_rotation_j = curr_inst_rotation_j; #else assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE switch (sync_rotation_type) { case SyncRotationType::NONE: { // z only rotation -> synch instance z // The X,Y rotations should be synchronized from start to end of the rotation. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE assert(is_rotation_xy_synchronized(curr_inst_rotation_i, curr_inst_rotation_j)); if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) new_inst_offset_j.z() = curr_inst_trafo_i.get_offset().z(); @@ -3236,51 +3062,41 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ assert(is_rotation_xy_synchronized(rotation, volume_j->get_instance_rotation())); if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) volume_j->set_instance_offset(Z, volume_i->get_instance_offset().z()); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE break; } case SyncRotationType::GENERAL: { // generic rotation -> update instance z with the delta of the rotation. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const double z_diff = Geometry::rotation_diff_z(old_inst_rotation_i, old_inst_rotation_j); new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); #else const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); volume_j->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE break; } #if ENABLE_WORLD_COORDINATE case SyncRotationType::FULL: { // generic rotation -> update instance z with the delta of the rotation. -#if ENABLE_TRANSFORMATIONS_BY_MATRICES const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(curr_inst_rotation_i, old_inst_rotation_j)); -#else - const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, m_cache.volumes_data[j].get_instance_rotation())); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES const Vec3d& axis = angle_axis.axis(); const double z_diff = (std::abs(axis.x()) > EPSILON || std::abs(axis.y()) > EPSILON) ? -#if ENABLE_TRANSFORMATIONS_BY_MATRICES angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(curr_inst_rotation_i, old_inst_rotation_j); new_inst_rotation_j = curr_inst_rotation_i + z_diff * Vec3d::UnitZ(); -#else - angle_axis.angle()* axis.z() : Geometry::rotation_diff_z(rotation, m_cache.volumes_data[j].get_instance_rotation()); - - volume_j->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES break; } #endif // ENABLE_WORLD_COORDINATE } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE volume_j->set_instance_transformation(Geometry::assemble_transform(new_inst_offset_j, new_inst_rotation_j, curr_inst_scaling_factor_i, curr_inst_mirror_i)); #else volume_j->set_instance_scaling_factor(scaling_factor); volume_j->set_instance_mirror(mirror); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE done.insert(j); } @@ -3307,14 +3123,14 @@ void Selection::synchronize_unselected_volumes() #endif // ENABLE_WIPETOWER_OBJECTID_1000_REMOVAL const int volume_idx = volume->volume_idx(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& trafo = volume->get_volume_transformation(); #else const Vec3d& offset = volume->get_volume_offset(); const Vec3d& rotation = volume->get_volume_rotation(); const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); const Vec3d& mirror = volume->get_volume_mirror(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE // Process unselected volumes. for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { @@ -3325,14 +3141,14 @@ void Selection::synchronize_unselected_volumes() if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) continue; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE v->set_volume_transformation(trafo); #else v->set_volume_offset(offset); v->set_volume_rotation(rotation); v->set_volume_scaling_factor(scaling_factor); v->set_volume_mirror(mirror); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } } } @@ -3447,13 +3263,13 @@ void Selection::paste_volumes_from_clipboard() { ModelInstance* dst_instance = dst_object->instances[dst_inst_idx]; BoundingBoxf3 dst_instance_bb = dst_object->instance_bounding_box(dst_inst_idx); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix_no_offset(); Transform3d dst_matrix = dst_instance->get_transformation().get_matrix_no_offset(); #else Transform3d src_matrix = src_object->instances[0]->get_transformation().get_matrix(true); Transform3d dst_matrix = dst_instance->get_transformation().get_matrix(true); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE bool from_same_object = (src_object->input_file == dst_object->input_file) && src_matrix.isApprox(dst_matrix); // used to keep relative position of multivolume selections when pasting from another object @@ -3531,7 +3347,7 @@ void Selection::paste_objects_from_clipboard() #endif /* _DEBUG */ } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, const Transform3d& transform) { @@ -3551,7 +3367,7 @@ void Selection::transform_volume_relative(GLVolume& volume, const VolumeCache& v else assert(false); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index a20e952f3..0e6922c63 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -125,9 +125,9 @@ private: Transform3d scale_matrix{ Transform3d::Identity() }; Transform3d mirror_matrix{ Transform3d::Identity() }; Transform3d full_matrix{ Transform3d::Identity() }; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE Geometry::Transformation transform; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE TransformCache() = default; explicit TransformCache(const Geometry::Transformation& transform); @@ -141,18 +141,18 @@ private: VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform); const Vec3d& get_volume_position() const { return m_volume.position; } -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#if !ENABLE_WORLD_COORDINATE const Vec3d& get_volume_rotation() const { return m_volume.rotation; } const Vec3d& get_volume_scaling_factor() const { return m_volume.scaling_factor; } const Vec3d& get_volume_mirror() const { return m_volume.mirror; } -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // !ENABLE_WORLD_COORDINATE const Transform3d& get_volume_rotation_matrix() const { return m_volume.rotation_matrix; } const Transform3d& get_volume_scale_matrix() const { return m_volume.scale_matrix; } const Transform3d& get_volume_mirror_matrix() const { return m_volume.mirror_matrix; } const Transform3d& get_volume_full_matrix() const { return m_volume.full_matrix; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& get_volume_transform() const { return m_volume.transform; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE const Vec3d& get_instance_position() const { return m_instance.position; } const Vec3d& get_instance_rotation() const { return m_instance.rotation; } @@ -162,9 +162,9 @@ private: const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; } const Transform3d& get_instance_mirror_matrix() const { return m_instance.mirror_matrix; } const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; } -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE const Geometry::Transformation& get_instance_transform() const { return m_instance.transform; } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE }; public: @@ -232,7 +232,7 @@ private: // Bounding box of a single full instance selection, in world coordinates. // Modifiers are NOT taken in account std::optional m_scaled_instance_bounding_box; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. // Modifiers are taken in account std::optional m_full_unscaled_instance_bounding_box; @@ -242,7 +242,7 @@ private: // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied. // Modifiers are taken in account std::optional m_full_unscaled_instance_local_bounding_box; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE #if ENABLE_RENDER_SELECTION_CENTER GLModel m_vbo_sphere; @@ -346,9 +346,6 @@ public: VolumeNotAxisAligned_Instance, MultipleSelection, }; -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES - bool requires_uniform_scale(EUniformScaleRequiredReason* reason = nullptr) const; -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES #else bool requires_uniform_scale() const; #endif // ENABLE_WORLD_COORDINATE @@ -376,7 +373,7 @@ public: // Bounding box of a single full instance selection, in world coordinates. // Modifiers are NOT taken in account const BoundingBoxf3& get_scaled_instance_bounding_box() const; -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. // Modifiers are taken in account const BoundingBoxf3& get_full_unscaled_instance_bounding_box() const; @@ -387,16 +384,12 @@ public: // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied. // Modifiers are taken in account const BoundingBoxf3& get_full_unscaled_instance_local_bounding_box() const; -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void setup_cache(); #if ENABLE_WORLD_COORDINATE -#if ENABLE_TRANSFORMATIONS_BY_MATRICES void translate(const Vec3d& displacement, TransformationType transformation_type); -#else - void translate(const Vec3d& displacement, ECoordinatesType type = ECoordinatesType::World); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES #else void translate(const Vec3d& displacement, bool local = false); #endif // ENABLE_WORLD_COORDINATE @@ -405,14 +398,12 @@ public: void scale(const Vec3d& scale, TransformationType transformation_type); void scale_to_fit_print_volume(const BuildVolume& volume); void mirror(Axis axis); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void scale_and_translate(const Vec3d& scale, const Vec3d& translation, TransformationType transformation_type); void reset_skew(); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES - -#if !ENABLE_TRANSFORMATIONS_BY_MATRICES +#else void translate(unsigned int object_idx, const Vec3d& displacement); -#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void translate(unsigned int object_idx, unsigned int instance_idx, const Vec3d& displacement); #if ENABLE_WORLD_COORDINATE @@ -458,7 +449,7 @@ private: void do_remove_volume(unsigned int volume_idx); void do_remove_instance(unsigned int object_idx, unsigned int instance_idx); void do_remove_object(unsigned int object_idx); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); @@ -467,7 +458,7 @@ private: } #else void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); } -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE void render_synchronized_volumes(); #if ENABLE_LEGACY_OPENGL_REMOVAL #if ENABLE_WORLD_COORDINATE @@ -513,10 +504,10 @@ private: void paste_volumes_from_clipboard(); void paste_objects_from_clipboard(); -#if ENABLE_TRANSFORMATIONS_BY_MATRICES +#if ENABLE_WORLD_COORDINATE void transform_volume_relative(GLVolume& volume, const VolumeCache& volume_data, TransformationType transformation_type, const Transform3d& transform); -#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES +#endif // ENABLE_WORLD_COORDINATE }; } // namespace GUI From c99e93c3578f0ccb92a9de2a546a56a88662129e Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 6 Jun 2022 14:55:38 +0200 Subject: [PATCH 144/169] Fixed differences after rebase with master --- src/libslic3r/Geometry.hpp | 8 ++-- src/slic3r/GUI/GLCanvas3D.cpp | 11 +++--- src/slic3r/GUI/Gizmos/GLGizmoMove.hpp | 23 +++++++----- src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp | 2 +- src/slic3r/GUI/Gizmos/GLGizmoScale.cpp | 46 +++++++++++------------ src/slic3r/GUI/Gizmos/GLGizmoScale.hpp | 8 +--- src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 20 ++++++---- src/slic3r/GUI/Selection.cpp | 35 ++++++++--------- 9 files changed, 80 insertions(+), 77 deletions(-) diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 6ff52016e..aa09a0d3e 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -408,14 +408,14 @@ class Transformation public: #if ENABLE_WORLD_COORDINATE Transformation() = default; - explicit Transformation(const Transform3d & transform) : m_matrix(transform) {} + explicit Transformation(const Transform3d& transform) : m_matrix(transform) {} Vec3d get_offset() const { return m_matrix.translation(); } double get_offset(Axis axis) const { return get_offset()[axis]; } Transform3d get_offset_matrix() const; - void set_offset(const Vec3d & offset) { m_matrix.translation() = offset; } + void set_offset(const Vec3d& offset) { m_matrix.translation() = offset; } void set_offset(Axis axis, double offset) { m_matrix.translation()[axis] = offset; } Vec3d get_rotation() const; @@ -424,12 +424,12 @@ public: Transform3d get_rotation_matrix() const; #else Transformation(); - explicit Transformation(const Transform3d & transform); + explicit Transformation(const Transform3d& transform); const Vec3d& get_offset() const { return m_offset; } double get_offset(Axis axis) const { return m_offset(axis); } - void set_offset(const Vec3d & offset); + void set_offset(const Vec3d& offset); void set_offset(Axis axis, double offset); const Vec3d& get_rotation() const { return m_rotation; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 026734711..c44ba66a0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7433,11 +7433,13 @@ bool GLCanvas3D::_is_any_volume_outside() const void GLCanvas3D::_update_selection_from_hover() { bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); - if (m_hover_volume_idxs.empty()) { - if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) - m_selection.remove_all(); + bool selection_changed = false; - return; + if (m_hover_volume_idxs.empty()) { + if (!ctrl_pressed && m_rectangle_selection.get_state() == GLSelectionRectangle::EState::Select) { + selection_changed = ! m_selection.is_empty(); + m_selection.remove_all(); + } } GLSelectionRectangle::EState state = m_rectangle_selection.get_state(); @@ -7450,7 +7452,6 @@ void GLCanvas3D::_update_selection_from_hover() } } - bool selection_changed = false; #if ENABLE_NEW_RECTANGLE_SELECTION if (!m_rectangle_selection.is_empty()) { #endif // ENABLE_NEW_RECTANGLE_SELECTION diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 85f3c4785..dc5618cc4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -57,16 +57,15 @@ public: /// Detect reduction of move for wipetover on selection change /// void data_changed() override; - protected: - virtual bool on_init() override; - virtual std::string on_get_name() const override; - virtual bool on_is_activable() const override; - virtual void on_start_dragging() override; - virtual void on_stop_dragging() override; - virtual void on_dragging(const UpdateData& data) override; - virtual void on_render() override; - virtual void on_render_for_picking() override; + bool on_init() override; + std::string on_get_name() const override; + bool on_is_activable() const override; + void on_start_dragging() override; + void on_stop_dragging() override; + void on_dragging(const UpdateData& data) override; + void on_render() override; + void on_render_for_picking() override; private: double calc_projection(const UpdateData& data) const; @@ -79,10 +78,16 @@ private: void calc_selection_box_and_center(); #endif // ENABLE_WORLD_COORDINATE #if !ENABLE_GIZMO_GRABBER_REFACTOR +#if ENABLE_WORLD_COORDINATE && ENABLE_GL_SHADERS_ATTRIBUTES + void render_grabber_extension(Axis axis, const Transform3d& base_matrix, const BoundingBoxf3& box, bool picking); +#else void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking); +#endif // ENABLE_WORLD_COORDINATE && ENABLE_GL_SHADERS_ATTRIBUTES #endif // !ENABLE_GIZMO_GRABBER_REFACTOR }; + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 3a4f77837..e877fa9f3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -393,7 +393,7 @@ void GLGizmoRotate::render_circle() const #else ::glBegin(GL_LINE_LOOP); for (unsigned int i = 0; i < ScaleStepsCount; ++i) { - const float angle = (float)i * ScaleStepRad; + const float angle = float(i) * ScaleStepRad; const float x = ::cos(angle) * m_radius; const float y = ::sin(angle) * m_radius; const float z = 0.0f; @@ -595,7 +595,7 @@ void GLGizmoRotate::render_angle() const #else ::glBegin(GL_LINE_STRIP); for (unsigned int i = 0; i <= AngleResolution; ++i) { - const float angle = (float)i * step_angle; + const float angle = float(i) * step_angle; const float x = ::cos(angle) * ex_radius; const float y = ::sin(angle) * ex_radius; const float z = 0.0f; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 5fc24ed90..9b0417aaf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -137,7 +137,7 @@ public: GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } - void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } + void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation.x()); m_gizmos[Y].set_angle(rotation.y()); m_gizmos[Z].set_angle(rotation.z()); } std::string get_tooltip() const override { std::string tooltip = m_gizmos[X].get_tooltip(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index f287a69f7..98c9ffeef 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -11,7 +11,7 @@ #include -#include +#include namespace Slic3r { namespace GUI { @@ -166,26 +166,25 @@ bool GLGizmoScale3D::on_is_activable() const void GLGizmoScale3D::on_start_dragging() { - if (m_hover_id != -1) { - m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); + assert(m_hover_id != -1); + m_starting.ctrl_down = wxGetKeyState(WXK_CONTROL); #if ENABLE_WORLD_COORDINATE - m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center; - m_starting.box = m_bounding_box; - m_starting.center = m_center; - m_starting.instance_center = m_instance_center; + m_starting.drag_position = m_grabbers_transform * m_grabbers[m_hover_id].center; + m_starting.box = m_bounding_box; + m_starting.center = m_center; + m_starting.instance_center = m_instance_center; #else - m_starting.drag_position = m_grabbers[m_hover_id].center; - m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_bounding_box : m_parent.get_selection().get_bounding_box(); + m_starting.drag_position = m_grabbers[m_hover_id].center; + m_starting.box = (m_starting.ctrl_down && m_hover_id < 6) ? m_bounding_box : m_parent.get_selection().get_bounding_box(); - const Vec3d center = m_starting.box.center(); - m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); - m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z()); - m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z()); - m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z()); - m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z()); - m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z()); + const Vec3d& center = m_starting.box.center(); + m_starting.pivots[0] = m_transform * Vec3d(m_starting.box.max.x(), center.y(), center.z()); + m_starting.pivots[1] = m_transform * Vec3d(m_starting.box.min.x(), center.y(), center.z()); + m_starting.pivots[2] = m_transform * Vec3d(center.x(), m_starting.box.max.y(), center.z()); + m_starting.pivots[3] = m_transform * Vec3d(center.x(), m_starting.box.min.y(), center.z()); + m_starting.pivots[4] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.max.z()); + m_starting.pivots[5] = m_transform * Vec3d(center.x(), center.y(), m_starting.box.min.z()); #endif // ENABLE_WORLD_COORDINATE - } } void GLGizmoScale3D::on_stop_dragging() { @@ -230,11 +229,7 @@ void GLGizmoScale3D::on_render() const Selection::IndicesList& idxs = selection.get_volume_idxs(); for (unsigned int idx : idxs) { const GLVolume& v = *selection.get_volume(idx); -#if ENABLE_WORLD_COORDINATE m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); -#else - m_bounding_box.merge(v.transformed_convex_hull_bounding_box(v.get_volume_transformation().get_matrix())); -#endif // ENABLE_WORLD_COORDINATE } #if ENABLE_WORLD_COORDINATE @@ -303,7 +298,7 @@ void GLGizmoScale3D::on_render() 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); - bool ctrl_down = m_dragging && m_starting.ctrl_down || !m_dragging && wxGetKeyState(WXK_CONTROL); + const bool ctrl_down = (m_dragging && m_starting.ctrl_down) || (!m_dragging && wxGetKeyState(WXK_CONTROL)); #endif // ENABLE_WORLD_COORDINATE #if ENABLE_WORLD_COORDINATE @@ -361,6 +356,7 @@ void GLGizmoScale3D::on_render() 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; } @@ -383,7 +379,7 @@ void GLGizmoScale3D::on_render() transform_to_local(selection); #endif // ENABLE_GL_SHADERS_ATTRIBUTES - float grabber_mean_size = (float)((m_bounding_box.size().x() + m_bounding_box.size().y() + m_bounding_box.size().z()) / 3.0); + 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); @@ -597,7 +593,9 @@ void GLGizmoScale3D::on_render() } #if ENABLE_WORLD_COORDINATE +#if !ENABLE_GL_SHADERS_ATTRIBUTES glsafe(::glPopMatrix()); +#endif // !ENABLE_GL_SHADERS_ATTRIBUTES #endif // ENABLE_WORLD_COORDINATE } @@ -621,7 +619,7 @@ void GLGizmoScale3D::on_render_for_picking() #else render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); #endif // ENABLE_WORLD_COORDINATE - } +} #if ENABLE_LEGACY_OPENGL_REMOVAL void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index b2a9b7a23..fb4ff09b6 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -3,7 +3,9 @@ #include "GLGizmoBase.hpp" +#if !ENABLE_WORLD_COORDINATE #include "libslic3r/BoundingBox.hpp" +#endif // !ENABLE_WORLD_COORDINATE namespace Slic3r { namespace GUI { @@ -73,12 +75,6 @@ public: void set_scale(const Vec3d& scale) { m_starting.scale = scale; m_scale = scale; } #endif // ENABLE_WORLD_COORDINATE -#if ENABLE_WORLD_COORDINATE_SCALE_REVISITED - const Vec3d& get_starting_scale() const { return m_starting.scale; } -#endif // ENABLE_WORLD_COORDINATE_SCALE_REVISITED - - const Vec3d& get_offset() const { return m_offset; } - std::string get_tooltip() const override; /// diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 21b55ea69..39a1cba8e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -408,7 +408,8 @@ bool GLGizmosManager::gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event) { // at this moment is enebled to process mouse move under gizmo // tools bar e.g. Do not interupt dragging. return false; - } else if (mc.exist_tooltip) { + } + else if (mc.exist_tooltip) { // first move out of gizmo tool bar - unselect tooltip mc.exist_tooltip = false; update_hover_state(Undefined); @@ -423,10 +424,12 @@ bool GLGizmosManager::gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event) { mc.left = true; open_gizmo(gizmo); return true; - } else if (mouse_event.RightDown()) { + } + else if (mouse_event.RightDown()) { mc.right = true; return true; - } else if (mouse_event.MiddleDown()) { + } + else if (mouse_event.MiddleDown()) { mc.middle = true; return true; } @@ -441,14 +444,17 @@ bool GLGizmosManager::gizmos_toolbar_on_mouse(const wxMouseEvent &mouse_event) { update_hover_state(Undefined); } // draging start on toolbar so no propagation into scene - return true; - } else if (mc.left && mouse_event.LeftUp()) { + return true; + } + else if (mc.left && mouse_event.LeftUp()) { mc.left = false; return true; - } else if (mc.right && mouse_event.RightUp()) { + } + else if (mc.right && mouse_event.RightUp()) { mc.right = false; return true; - } else if (mc.middle && mouse_event.MiddleUp()) { + } + else if (mc.middle && mouse_event.MiddleUp()) { mc.middle = false; return true; } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 61ffd4b19..271435c99 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -9,7 +9,6 @@ #include "GUI_ObjectList.hpp" #include "Camera.hpp" #include "Plater.hpp" - #if ENABLE_WORLD_COORDINATE #include "MsgDialog.hpp" #endif // ENABLE_WORLD_COORDINATE @@ -786,7 +785,7 @@ void Selection::translate(const Vec3d& displacement, TransformationType transfor assert(false); } else { - const Vec3d offset = transformation_type.local() ? + const Vec3d offset = transformation_type.local() ? (Vec3d)(volume_data.get_volume_transform().get_rotation_matrix() * displacement) : displacement; transform_volume_relative(v, volume_data, transformation_type, Geometry::translation_transform(offset)); } @@ -966,7 +965,6 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); } - volume.set_instance_rotation(new_rotation); object_instance_first[volume.object_idx()] = i; } @@ -1048,7 +1046,7 @@ void Selection::flattening_rotate(const Vec3d& normal) #else const auto& voldata = m_cache.volumes_data[i]; Vec3d tnormal = (Geometry::assemble_transform( - Vec3d::Zero(), voldata.get_instance_rotation(), + Vec3d::Zero(), voldata.get_instance_rotation(), voldata.get_instance_scaling_factor().cwiseInverse(), voldata.get_instance_mirror()) * normal).normalized(); // Additional rotation to align tnormal with the down vector in the world coordinate space. auto extra_rotation = Eigen::Quaterniond().setFromTwoVectors(tnormal, -Vec3d::UnitZ()); @@ -1897,6 +1895,7 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) render_sidebar_scale_hints(sidebar_field, *shader, base_matrix * orient_matrix); else if (boost::starts_with(sidebar_field, "layer")) render_sidebar_layers_hints(sidebar_field, *shader); + #if ENABLE_WORLD_COORDINATE if (!boost::starts_with(sidebar_field, "layer")) { if (!wxGetApp().obj_manipul()->is_world_coordinates()) @@ -2014,8 +2013,7 @@ std::vector Selection::get_volume_idxs_from_object(unsigned int ob { std::vector idxs; - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { if ((*m_volumes)[i]->object_idx() == (int)object_idx) idxs.push_back(i); } @@ -2027,10 +2025,9 @@ std::vector Selection::get_volume_idxs_from_instance(unsigned int { std::vector idxs; - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { const GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == (int)object_idx) && (v->instance_idx() == (int)instance_idx)) + if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx) idxs.push_back(i); } @@ -2044,9 +2041,8 @@ std::vector Selection::get_volume_idxs_from_volume(unsigned int ob for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { const GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == (int)object_idx) && (v->volume_idx() == (int)volume_idx)) - { - if (((int)instance_idx != -1) && (v->instance_idx() == (int)instance_idx)) + if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) { + if ((int)instance_idx != -1 && v->instance_idx() == (int)instance_idx) idxs.push_back(i); } } @@ -2058,8 +2054,7 @@ std::vector Selection::get_missing_volume_idxs_from(const std::vec { std::vector idxs; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { std::vector::const_iterator it = std::find(volume_idxs.begin(), volume_idxs.end(), i); if (it == volume_idxs.end()) idxs.push_back(i); @@ -2105,7 +2100,8 @@ void Selection::update_type() if (!m_valid) m_type = Invalid; - else { + else + { if (m_list.empty()) m_type = Empty; else if (m_list.size() == 1) { @@ -2203,8 +2199,7 @@ void Selection::update_type() int object_idx = get_object_idx(); int instance_idx = get_instance_idx(); - for (GLVolume* v : *m_volumes) - { + for (GLVolume* v : *m_volumes) { v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false; } @@ -2393,7 +2388,7 @@ void Selection::render_synchronized_volumes() if (coordinates_type == ECoordinatesType::World) { box = v.transformed_convex_hull_bounding_box(); trafo = Transform3d::Identity(); - } + } else if (coordinates_type == ECoordinatesType::Local) { box = v.bounding_box(); trafo = v.world_matrix(); @@ -2437,6 +2432,7 @@ void Selection::render_bounding_box(const BoundingBoxf3 & box, float* color) con #if ENABLE_LEGACY_OPENGL_REMOVAL const BoundingBoxf3& curr_box = m_box.get_bounding_box(); + if (!m_box.is_initialized() || !is_approx(box.min, curr_box.min) || !is_approx(box.max, curr_box.max)) { m_box.reset(); @@ -3046,10 +3042,11 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ const Geometry::Transformation& curr_inst_trafo_j = volume_j->get_instance_transformation(); const Vec3d curr_inst_rotation_j = curr_inst_trafo_j.get_rotation(); Vec3d new_inst_offset_j = curr_inst_trafo_j.get_offset(); - Vec3d new_inst_rotation_j = curr_inst_rotation_j; + Vec3d new_inst_rotation_j = curr_inst_rotation_j; #else assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); #endif // ENABLE_WORLD_COORDINATE + switch (sync_rotation_type) { case SyncRotationType::NONE: { // z only rotation -> synch instance z From 4f8535d0d5b2c5a0e2577c5e88578bd2ce18c8d7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 7 Jun 2022 16:05:16 +0200 Subject: [PATCH 145/169] Move out invalid_id constant from MutablePriotityQueue template class Enclose it into Slic3r namespace --- src/libslic3r/MutablePriorityQueue.hpp | 10 ++++++++-- tests/libslic3r/test_mutable_priority_queue.cpp | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/MutablePriorityQueue.hpp b/src/libslic3r/MutablePriorityQueue.hpp index b45b8cfff..fd3e7ac2d 100644 --- a/src/libslic3r/MutablePriorityQueue.hpp +++ b/src/libslic3r/MutablePriorityQueue.hpp @@ -7,6 +7,10 @@ #include #include // adds size_t (without std::) +namespace Slic3r { + +constexpr auto InvalidQueueID = std::numeric_limits::max(); + template class MutablePriorityQueue { @@ -41,7 +45,7 @@ public: bool empty() const { return m_heap.empty(); } T& operator[](std::size_t idx) noexcept { return m_heap[idx]; } const T& operator[](std::size_t idx) const noexcept { return m_heap[idx]; } - static constexpr size_t invalid_id() { return std::numeric_limits::max(); } + static constexpr size_t invalid_id() { return InvalidQueueID; } using iterator = typename std::vector::iterator; using const_iterator = typename std::vector::const_iterator; @@ -291,7 +295,7 @@ public: bool empty() const { return m_heap.empty(); } T& operator[](std::size_t idx) noexcept { assert(! address::is_padding(idx)); return m_heap[idx]; } const T& operator[](std::size_t idx) const noexcept { assert(! address::is_padding(idx)); return m_heap[idx]; } - static constexpr size_t invalid_id() { return std::numeric_limits::max(); } + static constexpr size_t invalid_id() { return InvalidQueueID; } protected: void update_heap_up(size_t top, size_t bottom); @@ -450,4 +454,6 @@ inline void MutableSkipHeapPriorityQueue Date: Tue, 7 Jun 2022 16:05:37 +0200 Subject: [PATCH 146/169] Make AStar use InvalidQueueID constant --- src/libslic3r/AStar.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/AStar.hpp b/src/libslic3r/AStar.hpp index 61c82157b..b35b6a4af 100644 --- a/src/libslic3r/AStar.hpp +++ b/src/libslic3r/AStar.hpp @@ -53,7 +53,7 @@ template struct TracerTraits_ template using TracerNodeT = typename TracerTraits_>::Node; -constexpr size_t Unassigned = size_t(-1); +constexpr auto Unassigned = std::numeric_limits::max(); template struct QNode // Queue node. Keeps track of scores g, and h @@ -69,7 +69,11 @@ struct QNode // Queue node. Keeps track of scores g, and h size_t p = Unassigned, float gval = std::numeric_limits::infinity(), float hval = 0.f) - : node{std::move(n)}, parent{p}, queue_id{Unassigned}, g{gval}, h{hval} + : node{std::move(n)} + , parent{p} + , queue_id{InvalidQueueID} + , g{gval} + , h{hval} {} }; @@ -154,7 +158,7 @@ bool search_route(const Tracer &tracer, // The cache needs to be updated either way prev_nd = qsucc_nd; - if (queue_id == decltype(qopen)::invalid_id()) + if (queue_id == InvalidQueueID) // was in closed or unqueued, rescheduling qopen.push(succ_id); else // was in open, updating From b42e4b29272762b925e78c19fb5b0fd834eecf61 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 8 Jun 2022 13:12:29 +0200 Subject: [PATCH 147/169] GLTexture - Use BitmapCache::nsvgParseFromFileWithReplace() in place of nsvgParseFromFile() to load images from files --- src/slic3r/GUI/GLTexture.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 137a0a109..6065f22a5 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -7,6 +7,7 @@ #include "GUI_App.hpp" #include "GLModel.hpp" #endif // ENABLE_LEGACY_OPENGL_REMOVAL +#include "BitmapCache.hpp" #include @@ -204,7 +205,7 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector Date: Thu, 9 Jun 2022 09:22:40 +0200 Subject: [PATCH 148/169] Backported a fix from Clipper 6.4.2 for the issue that Clipper Z coordinated has incorrect value because ZFillFunction wasn't called in all cases (https://sourceforge.net/p/polyclipping/bugs/160/). Also, this issue led to duplicity vertices with the same XY coordinates but differ in Z coordinates. --- src/clipper/clipper.cpp | 9 +++++++ tests/fff_print/CMakeLists.txt | 1 + tests/fff_print/test_clipper.cpp | 43 ++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 tests/fff_print/test_clipper.cpp diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 84d68b1e6..84109398a 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -2290,6 +2290,12 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) if (horzEdge->OutIdx >= 0 && !IsOpen) //note: may be done multiple times { +#ifdef CLIPPERLIB_USE_XYZ + if (dir == dLeftToRight) + SetZ(e->Curr, *horzEdge, *e); + else + SetZ(e->Curr, *e, *horzEdge); +#endif op1 = AddOutPt(horzEdge, e->Curr); TEdge* eNextHorz = m_SortedEdges; while (eNextHorz) @@ -2614,6 +2620,9 @@ void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) { e->Curr.x() = TopX( *e, topY ); e->Curr.y() = topY; +#ifdef CLIPPERLIB_USE_XYZ + e->Curr.z() = topY == e->Top.y() ? e->Top.z() : (topY == e->Bot.y() ? e->Bot.z() : 0); +#endif } //When StrictlySimple and 'e' is being touched by another edge, then diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 4e8821287..0b3b920b9 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable(${_TEST_NAME}_tests test_avoid_crossing_perimeters.cpp test_bridges.cpp test_cooling.cpp + test_clipper.cpp test_custom_gcode.cpp test_data.cpp test_data.hpp diff --git a/tests/fff_print/test_clipper.cpp b/tests/fff_print/test_clipper.cpp new file mode 100644 index 000000000..dc49ce0f1 --- /dev/null +++ b/tests/fff_print/test_clipper.cpp @@ -0,0 +1,43 @@ +#include + +#include "test_data.hpp" +#include "clipper/clipper_z.hpp" + +using namespace Slic3r; + +// Test case for an issue with duplicity vertices (same XY coordinates but differ in Z coordinates) in Clipper 6.2.9, +// (related to https://sourceforge.net/p/polyclipping/bugs/160/) that was fixed in Clipper 6.4.2. +SCENARIO("Clipper Z", "[ClipperZ]") +{ + ClipperLib_Z::Path subject; + + subject.emplace_back(-2000, -1000, 10); + subject.emplace_back(-2000, 1000, 10); + subject.emplace_back( 2000, 1000, 10); + subject.emplace_back( 2000, -1000, 10); + + ClipperLib_Z::Path clip; + clip.emplace_back(-1000, -2000, -5); + clip.emplace_back(-1000, 2000, -5); + clip.emplace_back( 1000, 2000, -5); + clip.emplace_back( 1000, -2000, -5); + + 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) { + pt.z() = 1; + }); + + clipper.AddPath(subject, ClipperLib_Z::ptSubject, false); + clipper.AddPath(clip, ClipperLib_Z::ptClip, true); + + ClipperLib_Z::PolyTree polytree; + ClipperLib_Z::Paths paths; + clipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); + ClipperLib_Z::PolyTreeToPaths(polytree, paths); + + REQUIRE(paths.size() == 1); + REQUIRE(paths.front().size() == 2); + for (const ClipperLib_Z::IntPoint &pt : paths.front()) + REQUIRE(pt.z() == 1); +} \ No newline at end of file From 89f467a4885e559753778c369f30a42e09101a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 9 Jun 2022 10:57:30 +0200 Subject: [PATCH 149/169] Fixed undefined symbols when mold linker was used for linking libnest2d_tests and sla_print_tests. --- tests/libnest2d/CMakeLists.txt | 4 +++- tests/sla_print/CMakeLists.txt | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/libnest2d/CMakeLists.txt b/tests/libnest2d/CMakeLists.txt index 9bafe84a0..ea4f4255e 100644 --- a/tests/libnest2d/CMakeLists.txt +++ b/tests/libnest2d/CMakeLists.txt @@ -1,6 +1,8 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp printer_parts.cpp printer_parts.hpp) -target_link_libraries(${_TEST_NAME}_tests test_common libnest2d ) + +# mold linker for successful linking needs also to link TBB library and link it before libslic3r. +target_link_libraries(${_TEST_NAME}_tests test_common TBB::tbb libnest2d ) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") # catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ") diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index a26d5f480..24e9552c5 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -5,7 +5,9 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp sla_supptgen_tests.cpp sla_raycast_tests.cpp sla_archive_readwrite_tests.cpp) -target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) + +# mold linker for successful linking needs also to link TBB library and link it before libslic3r. +target_link_libraries(${_TEST_NAME}_tests test_common TBB::tbb libslic3r) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") if (WIN32) From 5d82c1601b8897b9113e428bf6b36eaaabd2a101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 9 Jun 2022 14:25:06 +0200 Subject: [PATCH 150/169] Fixed an unintentional transformation of ExPolygon to a single vector containing all points from contour and all holes in the Lightning infill. This was causing most of the issues with randomly generated infill hanging in the air without any support. --- src/libslic3r/Fill/Lightning/Generator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index 4aba7202d..75ca5583e 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -62,7 +62,7 @@ void Generator::generateInitialInternalOverhangs(const PrintObject &print_object for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions()) for (const Surface& surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) - infill_area_here.emplace_back(surface.expolygon); + append(infill_area_here, to_polygons(surface.expolygon)); //Remove the part of the infill area that is already supported by the walls. Polygons overhang = diff(offset(infill_area_here, -float(m_wall_supporting_radius)), infill_area_above); @@ -90,7 +90,7 @@ void Generator::generateTrees(const PrintObject &print_object, const std::functi for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions()) for (const Surface &surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) - infill_outlines[layer_id].emplace_back(surface.expolygon); + append(infill_outlines[layer_id], to_polygons(surface.expolygon)); } // For various operations its beneficial to quickly locate nearby features on the polygon: From dba1179708a88c425be5374666e6d68ec64ed7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 10 Jun 2022 15:19:26 +0200 Subject: [PATCH 151/169] Fixed an issue when the Lightning infill stuck in an infinite loop on some models. Cased by two sampled points closer than chosen spacing. --- src/libslic3r/Fill/FillRectilinear.cpp | 20 ++++++----- src/libslic3r/Fill/FillRectilinear.hpp | 6 ++-- .../Fill/Lightning/DistanceField.cpp | 33 ++++++++++++++++++- .../Fill/Lightning/DistanceField.hpp | 6 ++++ 4 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 264ae8a59..ba7461c5f 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -3043,14 +3043,18 @@ Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams return polylines_out; } -Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing) +// Lightning infill assumes that the distance between any two sampled points is always +// at least equal to the value of spacing. To meet this assumption, we need to use +// BoundingBox for whole layers instead of bounding box just around processing ExPolygon. +// Using just BoundingBox around processing ExPolygon could produce two points closer +// than spacing (in cases where two ExPolygon are closer than spacing). +Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing, const BoundingBox &global_bounding_box) { ExPolygonWithOffset poly_with_offset(expolygon, 0, 0, 0); - BoundingBox bounding_box = poly_with_offset.bounding_box_src(); std::vector segs = slice_region_by_vertical_lines( poly_with_offset, - (bounding_box.max.x() - bounding_box.min.x() + spacing - 1) / spacing, - bounding_box.min.x(), + (global_bounding_box.max.x() - global_bounding_box.min.x() + spacing - 1) / spacing, + global_bounding_box.min.x(), spacing); Points out; @@ -3066,17 +3070,17 @@ Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing) return out; } -Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing) +Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing, const BoundingBox &global_bounding_box) { Points out; for (const ExPolygon &expoly : expolygons) - append(out, sample_grid_pattern(expoly, spacing)); + append(out, sample_grid_pattern(expoly, spacing, global_bounding_box)); return out; } -Points sample_grid_pattern(const Polygons &polygons, coord_t spacing) +Points sample_grid_pattern(const Polygons &polygons, coord_t spacing, const BoundingBox &global_bounding_box) { - return sample_grid_pattern(union_ex(polygons), spacing); + return sample_grid_pattern(union_ex(polygons), spacing, global_bounding_box); } } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index ba735dd02..0a6c976ad 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -109,9 +109,9 @@ protected: float _layer_angle(size_t idx) const override { return 0.f; } }; -Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing); -Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing); -Points sample_grid_pattern(const Polygons &polygons, coord_t spacing); +Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing, const BoundingBox &global_bounding_box); +Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing, const BoundingBox &global_bounding_box); +Points sample_grid_pattern(const Polygons &polygons, coord_t spacing, const BoundingBox &global_bounding_box); } // namespace Slic3r diff --git a/src/libslic3r/Fill/Lightning/DistanceField.cpp b/src/libslic3r/Fill/Lightning/DistanceField.cpp index ef407664c..0cbfa9af5 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.cpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.cpp @@ -7,11 +7,34 @@ #include +#ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT +#include "../../SVG.hpp" +#endif + namespace Slic3r::FillLightning { constexpr coord_t radius_per_cell_size = 6; // The cell-size should be small compared to the radius, but not so small as to be inefficient. +#ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT +void export_distance_field_to_svg(const std::string &path, const Polygons &outline, const Polygons &overhang, const std::list &unsupported_points, const Points &points = {}) +{ + coordf_t stroke_width = scaled(0.01); + BoundingBox bbox = get_extents(outline); + + bbox.offset(SCALED_EPSILON); + SVG svg(path, bbox); + svg.draw_outline(outline, "green", stroke_width); + svg.draw_outline(overhang, "blue", stroke_width); + + for (const DistanceField::UnsupportedCell &cell : unsupported_points) + svg.draw(cell.loc, "cyan", coord_t(stroke_width)); + + for (const Point &pt : points) + svg.draw(pt, "red", coord_t(stroke_width)); +} +#endif + DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outline, const BoundingBox& current_outlines_bbox, const Polygons& current_overhang) : m_cell_size(radius / radius_per_cell_size), m_supporting_radius(radius), @@ -19,8 +42,9 @@ DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outl { m_supporting_radius2 = Slic3r::sqr(int64_t(radius)); // Sample source polygons with a regular grid sampling pattern. + const BoundingBox overhang_bbox = get_extents(current_overhang); for (const ExPolygon &expoly : union_ex(current_overhang)) { - const Points sampled_points = sample_grid_pattern(expoly, m_cell_size); + const Points sampled_points = sample_grid_pattern(expoly, m_cell_size, overhang_bbox); const size_t unsupported_points_prev_size = m_unsupported_points.size(); m_unsupported_points.resize(unsupported_points_prev_size + sampled_points.size()); @@ -59,6 +83,13 @@ DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outl // Because the distance between two points is at least one axis equal to m_cell_size, every cell // in m_unsupported_points_grid contains exactly one point. assert(m_unsupported_points.size() == m_unsupported_points_grid.size()); + +#ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT + { + static int iRun = 0; + export_distance_field_to_svg(debug_out_path("FillLightning-DistanceField-%d.svg", iRun++), current_outline, current_overhang, m_unsupported_points); + } +#endif } void DistanceField::update(const Point& to_node, const Point& added_leaf) diff --git a/src/libslic3r/Fill/Lightning/DistanceField.hpp b/src/libslic3r/Fill/Lightning/DistanceField.hpp index 1a47ee6ca..007ac235e 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.hpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.hpp @@ -8,6 +8,8 @@ #include "../../Point.hpp" #include "../../Polygon.hpp" +//#define LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT + namespace Slic3r::FillLightning { @@ -196,6 +198,10 @@ protected: Point from_grid_point(const Point &point) const { return point * m_cell_size + m_unsupported_points_bbox.min; } + +#ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT + friend void export_distance_field_to_svg(const std::string &path, const Polygons &outline, const Polygons &overhang, const std::list &unsupported_points, const Points &points); +#endif }; } // namespace Slic3r::FillLightning From 91a5ceb1dd61d39e9228e5e4702a8fe75bbd9f40 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 13 Jun 2022 15:44:43 +0200 Subject: [PATCH 152/169] OSX specific: Fixing opening of G-codes by drag & dropping of G-code on slicer icon in case slicer is not yet running. --- src/slic3r/GUI/GUI_App.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b688e11f5..9a4921a84 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -738,7 +738,7 @@ void GUI_App::post_init() if (! this->initialized()) throw Slic3r::RuntimeError("Calling post_init() while not yet initialized"); - if (this->init_params->start_as_gcodeviewer) { + if (this->is_gcode_viewer()) { if (! this->init_params->input_files.empty()) this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str())); } From 040a8467bd0809bed7dad64cdf79ca6e5c340c20 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 14 Jun 2022 12:17:03 +0200 Subject: [PATCH 153/169] #8401 - Show error message when trying to import invalid 3mf --- src/libslic3r/Format/3mf.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index abe705dc4..9a71c8978 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -3200,7 +3200,8 @@ bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionCo bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version); importer.log_errors(); handle_legacy_project_loaded(importer.version(), config); - return res; + + return !model->objects.empty() || !config.empty(); } bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64) From 9fc47b9c3a1e85a5b798b32b8a040edbfa056089 Mon Sep 17 00:00:00 2001 From: rtyr <36745189+rtyr@users.noreply.github.com> Date: Tue, 14 Jun 2022 12:44:25 +0200 Subject: [PATCH 154/169] Added Infinity3D bundle. https://github.com/prusa3d/PrusaSlicer/pull/8396 --- resources/profiles/Infinity3D.idx | 2 + resources/profiles/Infinity3D.ini | 812 ++++++++++++++++++ .../profiles/Infinity3D/DEV-200_thumbnail.png | Bin 0 -> 57206 bytes .../profiles/Infinity3D/DEV-350_thumbnail.png | Bin 0 -> 57124 bytes resources/profiles/Infinity3D/DEV_200_bed.stl | Bin 0 -> 684 bytes .../profiles/Infinity3D/DEV_200_texture.svg | 487 +++++++++++ resources/profiles/Infinity3D/DEV_350_bed.stl | Bin 0 -> 684 bytes .../profiles/Infinity3D/DEV_350_texture.svg | 589 +++++++++++++ 8 files changed, 1890 insertions(+) create mode 100644 resources/profiles/Infinity3D.idx create mode 100644 resources/profiles/Infinity3D.ini create mode 100644 resources/profiles/Infinity3D/DEV-200_thumbnail.png create mode 100644 resources/profiles/Infinity3D/DEV-350_thumbnail.png create mode 100644 resources/profiles/Infinity3D/DEV_200_bed.stl create mode 100644 resources/profiles/Infinity3D/DEV_200_texture.svg create mode 100644 resources/profiles/Infinity3D/DEV_350_bed.stl create mode 100644 resources/profiles/Infinity3D/DEV_350_texture.svg diff --git a/resources/profiles/Infinity3D.idx b/resources/profiles/Infinity3D.idx new file mode 100644 index 000000000..0af9cc73b --- /dev/null +++ b/resources/profiles/Infinity3D.idx @@ -0,0 +1,2 @@ +min_slic3r_version = 2.5.0-alpha0 +1.0.0 Initial Infinity3D bundle diff --git a/resources/profiles/Infinity3D.ini b/resources/profiles/Infinity3D.ini new file mode 100644 index 000000000..73703b43d --- /dev/null +++ b/resources/profiles/Infinity3D.ini @@ -0,0 +1,812 @@ +# Infinity3D profiles + +[vendor] +# Vendor name will be shown by the Config Wizard. +name = Infinity3D +# Configuration version of this file. Config file will only be installed, if the config_version differs. +# This means, the server may force the Slic3r configuration to be downgraded. +config_version = 1.0.0 +# Where to get the updates from? +config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Infinity3D/ +# The printer models will be shown by the Configuration Wizard in this order, + +[printer_model:DEV-200] +name = Infinity3D DEV-200 +variants = 0.4 +technology = FFF +bed_model = DEV_200_bed.stl +bed_texture = DEV_200_texture.svg +default_materials = Generic PLA @Infinity3D; Generic PETG @Infinity3D + +[printer_model:DEV-350] +name = Infinity3D DEV-350 +variants = 0.4 +technology = FFF +bed_model = DEV_350_bed.stl +bed_texture = DEV_350_texture.svg +default_materials = Generic PLA @Infinity3D; Generic PETG @Infinity3D + +[print:*common*] +avoid_crossing_perimeters = 1 +avoid_crossing_perimeters_max_detour = 0 +bottom_fill_pattern = monotonic +bottom_solid_layers = 4 +bottom_solid_min_thickness = 0 +bridge_acceleration = 0 +bridge_angle = 0 +bridge_flow_ratio = 1 +bridge_speed = 60 +brim_separation = 0 +brim_type = outer_only +brim_width = 0 +clip_multipart_objects = 1 +complete_objects = 0 +default_acceleration = 0 +dont_support_bridges = 1 +draft_shield = disabled +elefant_foot_compensation = 0.1 +ensure_vertical_shell_thickness = 1 +external_perimeter_extrusion_width = 0.45 +external_perimeter_speed = 80% +external_perimeters_first = 0 +extra_perimeters = 0 +extruder_clearance_height = 25 +extruder_clearance_radius = 75 +extrusion_width = 0.45 +fill_angle = 45 +fill_density = 20% +fill_pattern = gyroid +first_layer_acceleration = 0 +first_layer_acceleration_over_raft = 0 +first_layer_extrusion_width = 0.45 +first_layer_height = 0.2 +first_layer_speed = 45 +first_layer_speed_over_raft = 45 +fuzzy_skin = none +fuzzy_skin_point_dist = 0.8 +fuzzy_skin_thickness = 0.3 +gap_fill_enabled = 1 +gap_fill_speed = 20 +gcode_comments = 0 +gcode_label_objects = 0 +gcode_resolution = 0.0125 +gcode_substitutions = +infill_acceleration = 0 +infill_anchor = 600% +infill_anchor_max = 50 +infill_every_layers = 1 +infill_extruder = 1 +infill_extrusion_width = 0.45 +infill_first = 0 +infill_only_where_needed = 0 +infill_overlap = 25% +infill_speed = 60 +inherits = +interface_shells = 0 +ironing = 0 +ironing_flowrate = 15% +ironing_spacing = 0.25 +ironing_speed = 30 +ironing_type = top +layer_height = 0.2 +max_print_speed = 100 +max_volumetric_speed = 0 +min_skirt_length = 4 +mmu_segmented_region_max_width = 0 +notes = +only_retract_when_crossing_perimeters = 0 +ooze_prevention = 0 +output_filename_format = {input_filename_base}_{layer_height}mm_{filament_type[0]}_{printer_model}_{print_time}.gcode +overhangs = 0 +perimeter_acceleration = 0 +perimeter_extruder = 1 +perimeter_extrusion_width = 0.45 +perimeter_speed = 60 +perimeters = 2 +post_process = +print_settings_id = +raft_contact_distance = 0.1 +raft_expansion = 1.5 +raft_first_layer_density = 90% +raft_first_layer_expansion = 3 +raft_layers = 0 +resolution = 0 +seam_position = nearest +single_extruder_multi_material_priming = 1 +skirt_distance = 5 +skirt_height = 1 +skirts = 3 +slice_closing_radius = 0.049 +slicing_mode = regular +small_perimeter_speed = 70% +solid_infill_below_area = 0 +solid_infill_every_layers = 0 +solid_infill_extruder = 1 +solid_infill_extrusion_width= 0.45 +solid_infill_speed = 80% +spiral_vase = 0 +standby_temperature_delta = -5 +support_material = 0 +support_material_angle = 0 +support_material_auto = 1 +support_material_bottom_contact_distance = 0 +support_material_bottom_interface_layers = -1 +support_material_buildplate_only = 0 +support_material_closing_radius = 2 +support_material_contact_distance = 0.15 +support_material_enforce_layers = 0 +support_material_extruder = 0 +support_material_extrusion_width = 0.38 +support_material_interface_contact_loops = 0 +support_material_interface_extruder = 0 +support_material_interface_layers = 2 +support_material_interface_pattern = rectilinear +support_material_interface_spacing = 0.2 +support_material_interface_speed = 100% +support_material_pattern = rectilinear +support_material_spacing = 2 +support_material_speed = 60 +support_material_style = grid +support_material_synchronize_layers = 0 +support_material_threshold = 45 +support_material_with_sheath = 0 +support_material_xy_spacing = 60% +thick_bridges = 1 +thin_walls = 0 +threads = 8 +top_fill_pattern = monotonic +top_infill_extrusion_width = 0.45 +top_solid_infill_speed = 60% +top_solid_layers = 4 +top_solid_min_thickness = 0 +travel_speed = 70 +travel_speed_z = 0 +wipe_tower = 0 +wipe_tower_bridging = 10 +wipe_tower_brim_width = 2 +wipe_tower_no_sparse_layers = 0 +wipe_tower_rotation_angle = 0 +wipe_tower_width = 60 +wipe_tower_x = 170 +wipe_tower_y = 140 +xy_size_compensation = 0 +compatible_printers_condition = nozzle_diameter[0]==0.4 + +[print:0.06mm SUPERFINE @Infinity3D_DEV_200] +inherits = *common* +layer_height = 0.06 +bottom_solid_layers = 12 +top_solid_layers = 12 +top_solid_min_thickness = 0.72 +bottom_solid_min_thickness = 0.72 +bridge_speed = 45 +infill_speed = 60 +perimeter_speed = 50 +support_material_speed = 50 +max_print_speed = 60 +skirt_distance = 10 +first_layer_speed = 80% +first_layer_extrusion_width = 0.45 +perimeter_extrusion_width = 0.4 +external_perimeter_extrusion_width = 0.4 +infill_extrusion_width = 0.4 +solid_infill_extrusion_width = 0.4 +top_infill_extrusion_width = 0.4 +compatible_printers_condition = nozzle_diameter[0]==0.4 and printer_model=="DEV-200" + +[print:0.10mm Fine @Infinity3D_DEV_200] +inherits = *common* +layer_height = 0.10 +top_solid_layers = 8 +bottom_solid_layers = 8 +bridge_speed = 45 +infill_speed = 60 +perimeter_speed = 50 +support_material_speed = 50 +max_print_speed = 60 +skirt_distance = 10 +first_layer_speed = 80% +first_layer_extrusion_width = 0.45 +perimeter_extrusion_width = 0.4 +external_perimeter_extrusion_width = 0.4 +infill_extrusion_width = 0.4 +solid_infill_extrusion_width = 0.4 +top_infill_extrusion_width = 0.4 +compatible_printers_condition = nozzle_diameter[0]==0.4 and printer_model=="DEV-200" + +[print:0.20mm GOOD @Infinity3D_DEV_200] +inherits = *common* +layer_height = 0.20 +top_solid_layers = 5 +bottom_solid_layers = 5 +bridge_speed = 45 +infill_speed = 60 +perimeter_speed = 50 +support_material_speed = 50 +max_print_speed = 60 +skirt_distance = 10 +first_layer_speed = 80% +first_layer_extrusion_width = 0.45 +perimeter_extrusion_width = 0.4 +external_perimeter_extrusion_width = 0.4 +infill_extrusion_width = 0.4 +solid_infill_extrusion_width = 0.4 +top_infill_extrusion_width = 0.4 +compatible_printers_condition = nozzle_diameter[0]==0.4 and printer_model=="DEV-200" + +[print:0.30mm RAPID @Infinity3D_DEV_200] +inherits = *common* +layer_height = 0.30 +top_solid_layers = 3 +bottom_solid_layers = 3 +bridge_speed = 45 +infill_speed = 60 +perimeter_speed = 50 +support_material_speed = 50 +max_print_speed = 60 +skirt_distance = 10 +first_layer_speed = 80% +compatible_printers_condition = nozzle_diameter[0]==0.4 and printer_model=="DEV-200" + +[print:0.40mm FAST @Infinity3D_DEV_200] +inherits = *common* +layer_height = 0.40 +top_solid_layers = 3 +bottom_solid_layers = 3 +bridge_speed = 45 +infill_speed = 60 +perimeter_speed = 50 +support_material_speed = 50 +max_print_speed = 60 +skirt_distance = 10 +first_layer_speed = 80% +support_material_extrusion_width = 0.45 +compatible_printers_condition = nozzle_diameter[0]==0.4 and printer_model=="DEV-200" + + +[print:0.06mm SUPERFINE @Infinity3D_DEV_350] +inherits = *common* +layer_height = 0.06 +bottom_solid_layers = 12 +top_solid_layers = 12 +top_solid_min_thickness = 0.72 +bottom_solid_min_thickness = 0.72 +bridge_speed = 45 +infill_speed = 60 +perimeter_speed = 50 +support_material_speed = 50 +max_print_speed = 60 +skirt_distance = 10 +first_layer_speed = 80% +first_layer_extrusion_width = 0.45 +perimeter_extrusion_width = 0.4 +external_perimeter_extrusion_width = 0.4 +infill_extrusion_width = 0.4 +solid_infill_extrusion_width = 0.4 +top_infill_extrusion_width = 0.4 +compatible_printers_condition = nozzle_diameter[0]==0.4 and printer_model=="DEV-350" + +[print:0.10mm Fine @Infinity3D_DEV_350] +inherits = *common* +layer_height = 0.10 +top_solid_layers = 8 +bottom_solid_layers = 8 +bridge_speed = 45 +infill_speed = 60 +perimeter_speed = 50 +support_material_speed = 50 +max_print_speed = 60 +skirt_distance = 10 +first_layer_speed = 80% +first_layer_extrusion_width = 0.45 +perimeter_extrusion_width = 0.4 +external_perimeter_extrusion_width = 0.4 +infill_extrusion_width = 0.4 +solid_infill_extrusion_width = 0.4 +top_infill_extrusion_width = 0.4 +compatible_printers_condition = nozzle_diameter[0]==0.4 and printer_model=="DEV-350" + +[print:0.20mm GOOD @Infinity3D_DEV_350] +inherits = *common* +layer_height = 0.20 +top_solid_layers = 5 +bottom_solid_layers = 5 +bridge_speed = 45 +infill_speed = 60 +perimeter_speed = 50 +support_material_speed = 50 +max_print_speed = 60 +skirt_distance = 10 +first_layer_speed = 80% +first_layer_extrusion_width = 0.45 +perimeter_extrusion_width = 0.4 +external_perimeter_extrusion_width = 0.4 +infill_extrusion_width = 0.4 +solid_infill_extrusion_width = 0.4 +top_infill_extrusion_width = 0.4 +compatible_printers_condition = nozzle_diameter[0]==0.4 and printer_model=="DEV-350" + +[print:0.30mm RAPID @Infinity3D_DEV_350] +inherits = *common* +layer_height = 0.30 +top_solid_layers = 3 +bottom_solid_layers = 3 +bridge_speed = 45 +infill_speed = 60 +perimeter_speed = 50 +support_material_speed = 50 +max_print_speed = 60 +skirt_distance = 10 +first_layer_speed = 80% +compatible_printers_condition = nozzle_diameter[0]==0.4 and printer_model=="DEV-350" + +[print:0.40mm FAST @Infinity3D_DEV_350] +inherits = *common* +layer_height = 0.40 +top_solid_layers = 3 +bottom_solid_layers = 3 +bridge_speed = 45 +infill_speed = 60 +perimeter_speed = 50 +support_material_speed = 50 +max_print_speed = 60 +skirt_distance = 10 +first_layer_speed = 80% +support_material_extrusion_width = 0.45 +compatible_printers_condition = nozzle_diameter[0]==0.4 and printer_model=="DEV-350" + + +[filament:*common*] +bed_temperature = 60 +bridge_fan_speed = 100 +compatible_printers = +compatible_printers_condition = +compatible_prints = +compatible_prints_condition = +cooling = 1 +disable_fan_first_layers = 2 +end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n" +extrusion_multiplier = 1 +fan_always_on = 1 +fan_below_layer_time = 60 +filament_colour = #29B2B2 +filament_cooling_final_speed = 3.4 +filament_cooling_initial_speed = 2.2 +filament_cooling_moves = 4 +filament_cost = 0 +filament_density = 0 +filament_deretract_speed = nil +filament_diameter = 1.75 +filament_load_time = 0 +filament_loading_speed = 28 +filament_loading_speed_start = 3 +filament_max_volumetric_speed = 0 +filament_minimal_purge_on_wipe_tower = 15 +filament_notes = "" +filament_ramming_parameters = "120 100 6.6 6.8 7.2 7.6 7.9 8.2 8.7 9.4 9.9 10.0| 0.05 6.6 0.45 6.8 0.95 7.8 1.45 8.3 1.95 9.7 2.45 10 2.95 7.6 3.45 7.6 3.95 7.6 4.45 7.6 4.95 7.6" +filament_retract_before_travel = nil +filament_retract_before_wipe = nil +filament_retract_layer_change = nil +filament_retract_length = nil +filament_retract_lift = nil +filament_retract_lift_above = nil +filament_retract_lift_below = nil +filament_retract_restart_extra = nil +filament_retract_speed = nil +filament_settings_id = "" +filament_soluble = 0 +filament_spool_weight = 0 +filament_toolchange_delay = 0 +filament_type = PLA +filament_unload_time = 0 +filament_unloading_speed = 90 +filament_unloading_speed_start = 100 +filament_wipe = nil +first_layer_bed_temperature = 60 +first_layer_temperature = 210 +full_fan_speed_layer = 0 +inherits = +max_fan_speed = 100 +min_fan_speed = 35 +min_print_speed = 10 +slowdown_below_layer_time = 5 +start_filament_gcode = "; Filament gcode\n" +temperature = 210 + +[filament:*PLA*] +inherits = *common* +bed_temperature = 60 +fan_below_layer_time = 100 +filament_colour = #DDDDDD +filament_max_volumetric_speed = 15 +filament_type = PLA +filament_density = 1.24 +filament_cost = 20 +first_layer_bed_temperature = 60 +first_layer_temperature = 210 +fan_always_on = 1 +cooling = 1 +max_fan_speed = 100 +min_fan_speed = 100 +bridge_fan_speed = 100 +disable_fan_first_layers = 1 +temperature = 205 + +[filament:*PET*] +inherits = *common* +bed_temperature = 70 +cooling = 1 +disable_fan_first_layers = 3 +fan_below_layer_time = 20 +filament_colour = #DDDDDD +filament_max_volumetric_speed = 8 +filament_type = PETG +filament_density = 1.27 +filament_cost = 30 +first_layer_bed_temperature = 70 +first_layer_temperature = 240 +fan_always_on = 1 +max_fan_speed = 50 +min_fan_speed = 20 +bridge_fan_speed = 100 +temperature = 240 + +[filament:*ABS*] +inherits = *common* +bed_temperature = 100 +cooling = 0 +disable_fan_first_layers = 3 +fan_below_layer_time = 20 +filament_colour = #DDDDDD +filament_max_volumetric_speed = 11 +filament_type = ABS +filament_density = 1.04 +filament_cost = 20 +first_layer_bed_temperature = 100 +first_layer_temperature = 245 +fan_always_on = 0 +max_fan_speed = 0 +min_fan_speed = 0 +bridge_fan_speed = 30 +top_fan_speed = 0 +temperature = 245 + +[filament:Generic PLA @Infinity3D] +inherits = *PLA* +renamed_from = "Generic PLA @Infinity3D" +filament_vendor = Generic + +[filament:Generic PETG @Infinity3D] +inherits = *PET* +renamed_from = "Generic PETG @Infinity3D" +filament_vendor = Generic + +[filament:Generic ABS @Infinity3D] +inherits = *ABS* +renamed_from = "Generic ABS @Infinity3D" +first_layer_bed_temperature = 90 +bed_temperature = 90 +filament_vendor = Generic + +[filament:Infinity3D PLA @Infinity3D] +inherits = *PLA* +renamed_from = "Infinity3D PLA @Infinity3D" +filament_vendor = Infinity3D +temperature = 200 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +filament_colour = #42BDD8 + +[filament:Infinity3D PETG @Infinity3D] +inherits = *PET* +renamed_from = "Infinity3D PETG @Infinity3D" +filament_vendor = Infinity3D +temperature = 240 +bed_temperature = 70 +first_layer_temperature = 240 +first_layer_bed_temperature = 70 +max_fan_speed = 40 +min_fan_speed = 20 +filament_colour = #42BDD8 + +[filament:Infinity3D ABS @Infinity3D] +inherits = *ABS* +renamed_from = "Infinity3D ABS @Infinity3D" +filament_vendor = Infinity3D +temperature = 240 +bed_temperature = 90 +first_layer_temperature = 240 +first_layer_bed_temperature = 90 +filament_colour = #42BDD8 + +[filament:Prusament PLA @Infinity3D] +inherits = *PLA* +renamed_from = "Prusament PLA @Infinity3D" +filament_vendor = Prusa Polymers +temperature = 210 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +filament_cost = 24.99 +filament_density = 1.24 +filament_colour = #F94D0C + +[filament:Prusament PETG @Infinity3D] +inherits = *PET* +renamed_from = "Prusament PETG @Infinity3D" +filament_vendor = Prusa Polymers +temperature = 245 +bed_temperature = 70 +first_layer_temperature = 245 +first_layer_bed_temperature = 70 +filament_cost = 24.99 +filament_density = 1.27 +filament_colour = #F94D0C + +[filament:AzureFilm PLA @Infinity3D] +inherits = *PLA* +filament_vendor = AzureFilm +temperature = 210 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +filament_cost = 19.97 +filament_density = 1.24 +filament_colour = #006AA6 + +[filament:Devil Design PLA @Infinity3D] +inherits = *PLA* +filament_vendor = Devil Design +temperature = 215 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +filament_cost = 19.00 +filament_density = 1.24 +filament_colour = #FF0000 +filament_spool_weight = 256 + +[filament:Devil Design PLA Matt @Infinity3D] +inherits = *PLA* +filament_vendor = Devil Design +temperature = 205 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +filament_cost = 20.00 +filament_density = 1.38 +filament_colour = #FF0000 +filament_spool_weight = 256 + +[filament:Devil Design PLA Galaxy @Infinity3D] +inherits = *PLA* +renamed_from = "Devil Design PLA (Galaxy) @Infinity3D" +filament_vendor = Devil Design +temperature = 225 +bed_temperature = 65 +first_layer_temperature = 225 +first_layer_bed_temperature = 65 +filament_cost = 19.00 +filament_density = 1.24 +filament_colour = #FF0000 +filament_spool_weight = 256 + +[filament:Extrudr PLA NX2 @Infinity3D] +inherits = *PLA* +filament_vendor = Extrudr +temperature = 200 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +filament_cost = 23.63 +filament_density = 1.3 +filament_colour = #3C4547 +filament_spool_weight = 256 + +[filament:Extrudr GreenTEC Pro @Infinity3D] +inherits = *PLA* +filament_vendor = Extrudr +temperature = 210 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +filament_cost = 56.24 +filament_density = 1.35 +filament_colour = #3C4547 + +[filament:Real Filament PLA @Infinity3D] +inherits = *PLA* +filament_vendor = Real Filament +temperature = 195 +bed_temperature = 60 +first_layer_temperature = 200 +first_layer_bed_temperature = 60 +filament_cost = 24.99 +filament_density = 1.24 +filament_colour = #007ABF + +[filament:Velleman PLA @Infinity3D] +inherits = *PLA* +filament_vendor = Velleman +temperature = 200 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +filament_cost = 27.99 +filament_density = 1.24 +filament_colour = #7EA60D + +[filament:3DJAKE ecoPLA @Infinity3D] +inherits = *PLA* +filament_vendor = 3DJAKE +temperature = 200 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +filament_cost = 21.99 +filament_density = 1.24 +filament_colour = #125467 +filament_spool_weight = 238 + +[filament:3DJAKE ecoPLA Matt @Infinity3D] +inherits = *PLA* +filament_vendor = 3DJAKE +temperature = 195 +bed_temperature = 60 +first_layer_temperature = 195 +first_layer_bed_temperature = 60 +filament_cost = 24.99 +filament_density = 1.38 +filament_colour = #125467 +filament_spool_weight = 238 + +[filament:3DJAKE ecoPLA Tough @Infinity3D] +inherits = *PLA* +filament_vendor = 3DJAKE +temperature = 215 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +filament_cost = 29.99 +filament_density = 1.21 +filament_colour = #125467 + +[filament:FormFutura Tough PLA @Infinity3D] +inherits = *PLA* +filament_vendor = FormFutura +temperature = 215 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +filament_cost = 46.65 +filament_density = 1.21 +filament_colour = #ed000e + +[filament:123-3D Jupiter PLA @Infinity3D] +inherits = *PLA* +filament_vendor = 123-3D +temperature = 200 +bed_temperature = 60 +first_layer_temperature = 205 +first_layer_bed_temperature = 60 +filament_cost = 19.50 +filament_density = 1.24 +filament_colour = #FFE200 + +[filament:Das Filament PLA @Infinity3D] +inherits = *PLA* +filament_vendor = Das Filament +temperature = 210 +bed_temperature = 60 +first_layer_temperature = 215 +first_layer_bed_temperature = 60 +filament_cost = 20.56 +filament_density = 1.24 +filament_colour = #C7F935 + +[filament:Das Filament PETG @Infinity3D] +inherits = *PET* +filament_vendor = Das Filament +temperature = 240 +bed_temperature = 70 +first_layer_temperature = 240 +first_layer_bed_temperature = 70 +filament_cost = 27.44 +filament_density = 1.29 +filament_colour = #C7F935 + +[filament:Verbatim PLA @Infinity3D] +inherits = *PLA* +filament_vendor = Verbatim +temperature = 205 +bed_temperature = 60 +first_layer_temperature = 210 +first_layer_bed_temperature = 60 +filament_cost = 22.99 +filament_density = 1.24 +filament_colour = #001ca8 + +# Common printer preset +[printer:*common*] +bed_shape = 0x0,350x0,350x350,0x350 +color_change_gcode = M600 +cooling_tube_length = 5 +cooling_tube_retraction = 91.5 +default_filament_profile = "" +default_print_profile = +end_gcode = ;End GCode begin\nM140 S0 ;Heated bed heater off\nM104 S0 ;Extruder heater off\nG90 ;absolute positioning\nG92 E0 ;Retract the filament\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z350 E-1 F3000 ;move Z up a bit and retract filament even more\nG1 X0 F3000 ;move X to min endstops, so the head is out of the way\nG1 Y350 F3000 ;so the head is out of the way and Plate is moved forward\nM84 ;stepper off\nM107 ; fan off\nM82 ; absolute extrusion\n;End GCode end +extra_loading_move = -2 +extruder_colour = "" +extruder_offset = 0x0 +gcode_flavor = marlin +high_current_on_filament_swap = 0 +machine_limits_usage = time_estimate_only +machine_max_acceleration_e = 10000 +machine_max_acceleration_extruding = 1000 +machine_max_acceleration_retracting = 1000 +machine_max_acceleration_travel = 1500 +machine_max_acceleration_x = 3000 +machine_max_acceleration_y = 3000 +machine_max_acceleration_z = 100 +machine_max_feedrate_e = 25 +machine_max_feedrate_x = 150 +machine_max_feedrate_y = 150 +machine_max_feedrate_z = 50 +machine_max_jerk_e = 2.5 +machine_max_jerk_x = 10 +machine_max_jerk_y = 10 +machine_max_jerk_z = 0.2 +machine_min_extruding_rate = 0 +machine_min_travel_rate = 0 +max_layer_height = 0.4 +max_print_height = 350 +min_layer_height = 0.08 +nozzle_diameter = 0.4 +parking_pos_retraction = 92 +pause_print_gcode = +printer_technology = FFF +remaining_times = 0 +retract_before_travel = 2 +retract_before_wipe = 0% +retract_layer_change = 1 +retract_length = 2 +retract_length_toolchange = 10 +retract_lift = 0 +retract_lift_above = 0 +retract_lift_below = 328 +retract_restart_extra = 0 +retract_restart_extra_toolchange = 0 +retract_speed = 60 +deretract_speed = 40 +silent_mode = 0 +single_extruder_multi_material = 0 +start_gcode = Start GCode begin\nM140 S[first_layer_bed_temperature] ;Start Warming Bed\nM104 S[first_layer_temperature] ;Preheat\nG28 ;home\nG29 ;Auto Bed-level\nG90 ;absolute positioning\nG1 X-10 Y-10 F3000 ;Move to corner\nM190 S[first_layer_bed_temperature] ;Wait For Bed Temperature\nM109 S[first_layer_temperature] ;Wait for Hotend Temperature\nG92 E0 ;Zero set extruder position\nG1 E3 F200 ;Feed filament to clear nozzle\nG92 E0 ;Zero set extruder position +thumbnails = 16x16,220x124 +use_firmware_retraction = 0 +use_relative_e_distances = 0 +use_volumetric_e = 0 +variable_layer_height = 1 +wipe = 0 +z_offset = 0 + +[printer:Infinity3D DEV-350] +inherits = *common* +printer_model = DEV-350 +printer_variant = 0.4 +default_filament_profile = Generic PLA @Infinity3D +default_print_profile = 0.20mm GOOD @Infinity3D_DEV_350 + +[printer:Infinity3D DEV-200] +inherits = *common* +printer_model = DEV-200 +printer_variant = 0.4 +bed_shape = 0x0,200x0,200x200,0x200 +thumbnails = +variable_layer_height = 0 +retract_lift_below = 0 +max_print_height = 235 +start_gcode = Start GCode begin\nM140 S[first_layer_bed_temperature] ;Start Warming Bed\nM104 S[first_layer_temperature] ;Preheat\nG28 ;home\nG29 ;Auto Bed-level\nG90 ;absolute positioning\nG1 X-10 Y-10 F3000 ;Move to corner\nM190 S[first_layer_bed_temperature] ;Wait For Bed Temperature\nM109 S[first_layer_temperature] ;Wait for Hotend Temperature\nG92 E0 ;Zero set extruder position\nG1 E3 F200 ;Feed filament to clear nozzle\nG92 E0 ;Zero set extruder position +end_gcode = ;End GCode begin\nM140 S0 ;Heated bed heater off\nM104 S0 ;Extruder heater off\nG90 ;absolute positioning\nG92 E0 ;Retract the filament\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z240 E-1 F3000 ;move Z up a bit and retract filament even more\nG1 X0 F3000 ;move X to min endstops, so the head is out of the way\nG1 Y200 F3000 ;so the head is out of the way and Plate is moved forward\nM84 ;stepper off\nM107 ; fan off\nM82 ; absolute extrusion\n;End GCode end +default_filament_profile = Generic PLA @Infinity3D +default_print_profile = 0.20mm GOOD @Infinity3D_DEV_200 diff --git a/resources/profiles/Infinity3D/DEV-200_thumbnail.png b/resources/profiles/Infinity3D/DEV-200_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..9379447734428ced2b3acb4e55ff6f347eec4b91 GIT binary patch literal 57206 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 I<*Y*g2g&|j1ONa4 literal 0 HcmV?d00001 diff --git a/resources/profiles/Infinity3D/DEV-350_thumbnail.png b/resources/profiles/Infinity3D/DEV-350_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..02ae09fa1d11459ec1b4b0dc648194557a3c41f1 GIT binary patch literal 57124 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 literal 0 HcmV?d00001 diff --git a/resources/profiles/Infinity3D/DEV_200_bed.stl b/resources/profiles/Infinity3D/DEV_200_bed.stl new file mode 100644 index 0000000000000000000000000000000000000000..b079051500a823e3b89ef35d47d4ae1845b78cb5 GIT binary patch literal 684 zcmb_ZK?;B{4C}$8e;W9*{!H07^dX*{rdvuU3LYFdLrvPGiu`-VuAQb*)_hzK$`_sj z&Vy_d={gfpv6r+EH}yva`$7$jPS#!R7M + + + + + + + diff --git a/resources/profiles/Infinity3D/DEV_350_bed.stl b/resources/profiles/Infinity3D/DEV_350_bed.stl new file mode 100644 index 0000000000000000000000000000000000000000..7250f3867ea7a2ce1cb7330701c0017ecf799ab9 GIT binary patch literal 684 zcmb`DI|{@w3`7-3xwlDr7rvNo=7n5pi-5z(Jdi_*6zd`x?0KV)*?-Sxiu1DAew=+f zblv+_(Yjx4uh_kzAb!1k(RzDNkPD%tNcZ@fH+3Rcu7T!$c+I>>L1g_z3^y)KD;IRu z^61P2z2{w_MF%1(_Qih4&Gpzcg#O;{1k*m&h{^L7(aK^b>3qE8mpd_a<;+2K< + + + + + + + From db31995310c1743faa0c1ce9d3135af6cb7e2c71 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Thu, 16 Jun 2022 11:12:32 +0200 Subject: [PATCH 155/169] Allow drag and drop on files into PrusaSlicer no matter which is the current selected tab --- src/slic3r/GUI/Plater.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d23a3dd13..b78a6db95 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1586,12 +1586,15 @@ std::string& Sidebar::get_search_line() class PlaterDropTarget : public wxFileDropTarget { public: - PlaterDropTarget(Plater* plater) : m_plater(plater) { this->SetDefaultAction(wxDragCopy); } + PlaterDropTarget(MainFrame& mainframe, Plater& plater) : m_mainframe(mainframe), m_plater(plater) { + this->SetDefaultAction(wxDragCopy); + } virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames); private: - Plater* m_plater; + MainFrame& m_mainframe; + Plater& m_plater; }; bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames) @@ -1601,8 +1604,11 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi this->MSWUpdateDragImageOnLeave(); #endif // WIN32 - bool res = (m_plater != nullptr) ? m_plater->load_files(filenames) : false; - wxGetApp().mainframe->update_title(); + m_mainframe.Raise(); + m_mainframe.select_tab(size_t(0)); + m_plater.select_view_3D("3D"); + bool res = m_plater.load_files(filenames); + m_mainframe.update_title(); return res; } @@ -2144,7 +2150,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) } // Drop target: - q->SetDropTarget(new PlaterDropTarget(q)); // if my understanding is right, wxWindow takes the owenership + main_frame->SetDropTarget(new PlaterDropTarget(*main_frame, *q)); // if my understanding is right, wxWindow takes the owenership q->Layout(); set_current_panel(wxGetApp().is_editor() ? static_cast(view3D) : static_cast(preview)); From 84ec233734840a8a4bedc486a189895839456240 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 16 Jun 2022 12:59:33 +0200 Subject: [PATCH 156/169] Follow-up https://github.com/prusa3d/PrusaSlicer/commit/d0b4a4a87da65ba079456863092a335f192bf6dd - Preferences Dialog:Set flag m_settings_layout_changed to false, when CANCEL button was pushed. --- src/slic3r/GUI/Preferences.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index a4317d8f4..d301f0d9a 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -714,14 +714,17 @@ void PreferencesDialog::revert(wxEvent&) } if (key == "old_settings_layout_mode") { m_rb_old_settings_layout_mode->SetValue(app_config->get(key) == "1"); + m_settings_layout_changed = false; continue; } if (key == "new_settings_layout_mode") { m_rb_new_settings_layout_mode->SetValue(app_config->get(key) == "1"); + m_settings_layout_changed = false; continue; } if (key == "dlg_settings_layout_mode") { m_rb_dlg_settings_layout_mode->SetValue(app_config->get(key) == "1"); + m_settings_layout_changed = false; continue; } From 4079eac29a734c3b095cf654cd027da2d4b9c605 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 16 Jun 2022 16:35:06 +0200 Subject: [PATCH 157/169] Fixed a failing assert in BuildVolume.cpp --- src/libslic3r/BuildVolume.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/BuildVolume.cpp b/src/libslic3r/BuildVolume.cpp index c580e6f87..899055355 100644 --- a/src/libslic3r/BuildVolume.cpp +++ b/src/libslic3r/BuildVolume.cpp @@ -238,7 +238,7 @@ BuildVolume::ObjectState object_state_templ(const indexed_triangle_set &its, con const stl_vertex p2 = trafo * its.vertices[tri(iedge)]; assert(sign(p1) == s[iprev]); assert(sign(p2) == s[iedge]); - assert(p1.z() * p2.z() < 0); + assert((p1.z() - world_min_z) * (p2.z() - world_min_z) < 0); // Edge crosses the z plane. Calculate intersection point with the plane. const float t = (world_min_z - p1.z()) / (p2.z() - p1.z()); (is_inside(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z)) ? inside : outside) = true; From 570b43941e1bcf797840bd2ecc7bd21dfe0995db Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 17 Jun 2022 12:40:13 +0200 Subject: [PATCH 158/169] Fixed title label when run as G-Code Viewer: Builds that were not tagged and contained number of commits since last tag showed "-UNKNOWN" in the title bar, even when they went through the build server --- src/libslic3r/libslic3r.h | 1 - src/slic3r/GUI/MainFrame.cpp | 4 +++- src/slic3r/GUI/SysInfoDialog.cpp | 7 ++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 06cad840e..3ee8acfc3 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -4,7 +4,6 @@ #include "libslic3r_version.h" #define GCODEVIEWER_APP_NAME "PrusaSlicer G-code Viewer" #define GCODEVIEWER_APP_KEY "PrusaSlicerGcodeViewer" -#define GCODEVIEWER_BUILD_ID std::string("PrusaSlicer G-code Viewer-") + std::string(SLIC3R_VERSION) + std::string("-UNKNOWN") // this needs to be included early for MSVC (listing it in Build.PL is not enough) #include diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index cde7a05a2..ab96c69fc 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -637,7 +637,9 @@ void MainFrame::update_title() } } - std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID; + std::string build_id = SLIC3R_BUILD_ID; + if (! wxGetApp().is_editor()) + boost::replace_first(build_id, SLIC3R_APP_NAME, GCODEVIEWER_APP_NAME); size_t idx_plus = build_id.find('+'); if (idx_plus != build_id.npos) { // Parse what is behind the '+'. If there is a number, then it is a build number after the label, and full build ID is shown. diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 06cf2b73d..92868cd4e 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -40,7 +40,12 @@ std::string get_main_info(bool format_as_html) if (!format_as_html) out << b_start << (wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME) << b_end << line_end; out << b_start << "Version: " << b_end << SLIC3R_VERSION << line_end; - out << b_start << "Build: " << b_end << (wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID) << line_end; + + std::string build_id = SLIC3R_BUILD_ID; + if (! wxGetApp().is_editor()) + boost::replace_first(build_id, SLIC3R_APP_NAME, GCODEVIEWER_APP_NAME); + out << b_start << "Build: " << b_end << build_id << line_end; + out << line_end; out << b_start << "Operating System: " << b_end << wxPlatformInfo::Get().GetOperatingSystemFamilyName() << line_end; out << b_start << "System Architecture: " << b_end << wxPlatformInfo::Get().GetArchName() << line_end; From 63890b5f8d352d3ef1228fa00b0d3932717f933d Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Mon, 20 Jun 2022 13:58:06 +0200 Subject: [PATCH 159/169] Do know allow picking while dragging gizmos --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.hpp | 1 - src/slic3r/GUI/Gizmos/GLGizmoBase.cpp | 3 --- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index c44ba66a0..eb5883f19 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5420,7 +5420,7 @@ void GLCanvas3D::_refresh_if_shown_on_screen() void GLCanvas3D::_picking_pass() { - if (m_picking_enabled && !m_mouse.dragging && m_mouse.position != Vec2d(DBL_MAX, DBL_MAX)) { + if (m_picking_enabled && !m_mouse.dragging && m_mouse.position != Vec2d(DBL_MAX, DBL_MAX) && !m_gizmos.is_dragging()) { m_hover_volume_idxs.clear(); // Render the object for picking. diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 18ad42ae1..a5b2acb32 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -854,7 +854,6 @@ public: // Returns the view ray line, in world coordinate, at the given mouse position. Linef3 mouse_ray(const Point& mouse_pos); - void set_mouse_as_dragging() { m_mouse.dragging = true; } bool is_mouse_dragging() const { return m_mouse.dragging; } double get_size_proportional_to_max_bed_size(double factor) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 0810ddc97..0ef4d3774 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -310,8 +310,6 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size())) m_grabbers[m_hover_id].dragging = true; - // prevent change of hover_id during dragging - m_parent.set_mouse_as_dragging(); on_start_dragging(); // Let the plater know that the dragging started @@ -323,7 +321,6 @@ bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) { // when mouse cursor leave window than finish actual dragging operation bool is_leaving = mouse_event.Leaving(); if (mouse_event.Dragging()) { - m_parent.set_mouse_as_dragging(); Point mouse_coord(mouse_event.GetX(), mouse_event.GetY()); auto ray = m_parent.mouse_ray(mouse_coord); UpdateData data(ray, mouse_coord); From 27a18065b3c7af5d78fad8890d591f83d1a22194 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 20 Jun 2022 14:47:55 +0200 Subject: [PATCH 160/169] Added possibly missing includes --- src/slic3r/GUI/MainFrame.cpp | 1 + src/slic3r/GUI/SysInfoDialog.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ab96c69fc..3d80954bd 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include "libslic3r/Print.hpp" diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 92868cd4e..53e7d637d 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -7,6 +7,8 @@ #include +#include + #include #include From d81009c6ce1c823570d511905455e5a0e6416638 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 8 Jun 2022 18:00:29 +0200 Subject: [PATCH 161/169] Follow-up https://github.com/prusa3d/PrusaSlicer/commit/56466abe99121d0bb40eaa9bb578593a6e3254a5# - Fixed a Search in the Preferences options + Preferences dialog: Deleted unused variable 'reverted' --- src/slic3r/GUI/Preferences.cpp | 1 - src/slic3r/GUI/Search.cpp | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index d301f0d9a..27cd113fb 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -697,7 +697,6 @@ void PreferencesDialog::revert(wxEvent&) for (auto value : m_values) { - bool reverted = false; const std::string& key = value.first; if (key == "default_action_on_dirty_project") { diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index d8c4622ec..6b5edc30e 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -311,6 +311,9 @@ void OptionsSearcher::check_and_update(PrinterTechnology pt_in, ConfigOptionMode for (auto i : input_values) append_options(i.config, i.type); + + options.insert(options.end(), preferences_options.begin(), preferences_options.end()); + sort_options(); search(search_line, true); From 76ea74c28921a4831ed5ae14b22f5b3f17c1184f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 8 Jun 2022 12:25:01 +0200 Subject: [PATCH 162/169] Update wxWidgets to 3.1.7 added handling for nanosvg with cmake --- deps/CMakeLists.txt | 1 + deps/NanoSVG/NanoSVG.cmake | 4 + deps/wxWidgets/wxWidgets.cmake | 14 +- src/CMakeLists.txt | 5 +- src/nanosvg/README-prusa.txt | 1 - src/nanosvg/nanosvg.h | 2979 ------------------------------- src/nanosvg/nanosvgrast.h | 1452 --------------- src/slic3r/CMakeLists.txt | 4 +- src/slic3r/GUI/BitmapCache.cpp | 6 +- src/slic3r/GUI/GLTexture.cpp | 4 +- src/slic3r/GUI/ImGuiWrapper.cpp | 5 +- 11 files changed, 24 insertions(+), 4451 deletions(-) create mode 100644 deps/NanoSVG/NanoSVG.cmake delete mode 100644 src/nanosvg/README-prusa.txt delete mode 100644 src/nanosvg/nanosvg.h delete mode 100644 src/nanosvg/nanosvgrast.h diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index d129ff1c2..eb0c420fa 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -188,6 +188,7 @@ endif () include(JPEG/JPEG.cmake) include(TIFF/TIFF.cmake) +include(NanoSVG/NanoSVG.cmake) include(wxWidgets/wxWidgets.cmake) set(_dep_list diff --git a/deps/NanoSVG/NanoSVG.cmake b/deps/NanoSVG/NanoSVG.cmake new file mode 100644 index 000000000..9623d3226 --- /dev/null +++ b/deps/NanoSVG/NanoSVG.cmake @@ -0,0 +1,4 @@ +prusaslicer_add_cmake_project(NanoSVG + URL https://github.com/memononen/nanosvg/archive/4c8f0139b62c6e7faa3b67ce1fbe6e63590ed148.zip + URL_HASH SHA256=584e084af1a75bf633f79753ce2f6f6ec8686002ca27f35f1037c25675fecfb6 +) \ No newline at end of file diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index bf5fd6289..4a0875d62 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -1,5 +1,3 @@ -set(_wx_git_tag v3.1.4-patched) - set(_wx_toolkit "") if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(_gtk_ver 2) @@ -15,11 +13,9 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for endif() prusaslicer_add_cmake_project(wxWidgets - # GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets" - # GIT_TAG tm_cross_compile #${_wx_git_tag} - URL https://github.com/prusa3d/wxWidgets/archive/489f6118256853cf5b299d595868641938566cdb.zip - URL_HASH SHA256=5b22d465377cedd8044bba69bea958b248953fd3628c1de4913a84d4e6f6175b - DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG + URL https://github.com/prusa3d/wxWidgets/archive/5412ac15586da3ecb6952fcc875d2a23366c998f.zip + URL_HASH SHA256=85a6e13152289fbf1ea51f221fbe1452e7914bbaa665b89536780810e93948a6 + DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG CMAKE_ARGS -DwxBUILD_PRECOMP=ON ${_wx_toolkit} @@ -32,7 +28,9 @@ prusaslicer_add_cmake_project(wxWidgets -DwxUSE_OPENGL=ON -DwxUSE_LIBPNG=sys -DwxUSE_ZLIB=sys - -DwxUSE_REGEX=builtin + -DwxUSE_NANOSVG=sys + -DwxUSE_NANOSVG_EXTERNAL=ON + -DwxUSE_REGEX=OFF -DwxUSE_LIBXPM=builtin -DwxUSE_LIBJPEG=sys -DwxUSE_LIBTIFF=sys diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 801760b8c..8a093f639 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -105,7 +105,10 @@ if (SLIC3R_GUI) # wrong libs for opengl in the link line and it does not link to it by himself. # libslic3r_gui will link to opengl anyway, so lets override wx list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX OpenGL) - + + if (UNIX AND NOT APPLE) + list(APPEND wxWidgets_LIBRARIES X11 wayland-client wayland-egl EGL) + endif () # list(REMOVE_ITEM wxWidgets_LIBRARIES oleacc) message(STATUS "wx libs: ${wxWidgets_LIBRARIES}") diff --git a/src/nanosvg/README-prusa.txt b/src/nanosvg/README-prusa.txt deleted file mode 100644 index 8388aa8ef..000000000 --- a/src/nanosvg/README-prusa.txt +++ /dev/null @@ -1 +0,0 @@ -Upstream source: https://github.com/memononen/nanosvg/tree/c1f6e209c16b18b46aa9f45d7e619acf42c29726 \ No newline at end of file diff --git a/src/nanosvg/nanosvg.h b/src/nanosvg/nanosvg.h deleted file mode 100644 index 57bcb7c2c..000000000 --- a/src/nanosvg/nanosvg.h +++ /dev/null @@ -1,2979 +0,0 @@ -/* - * Copyright (c) 2013-14 Mikko Mononen memon@inside.org - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - * - * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example - * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/) - * - * Arc calculation code based on canvg (https://code.google.com/p/canvg/) - * - * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html - * - */ - -#ifndef NANOSVG_H -#define NANOSVG_H - -#ifndef NANOSVG_CPLUSPLUS -#ifdef __cplusplus -extern "C" { -#endif -#endif - -// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes. -// -// The library suits well for anything from rendering scalable icons in your editor application to prototyping a game. -// -// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request! -// -// The shapes in the SVG images are transformed by the viewBox and converted to specified units. -// That is, you should get the same looking data as your designed in your favorite app. -// -// NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose -// to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters. -// -// The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. -// DPI (dots-per-inch) controls how the unit conversion is done. -// -// If you don't know or care about the units stuff, "px" and 96 should get you going. - - -/* Example Usage: - // Load SVG - NSVGimage* image; - image = nsvgParseFromFile("test.svg", "px", 96); - printf("size: %f x %f\n", image->width, image->height); - // Use... - for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { - for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { - for (int i = 0; i < path->npts-1; i += 3) { - float* p = &path->pts[i*2]; - drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]); - } - } - } - // Delete - nsvgDelete(image); -*/ - -enum NSVGpaintType { - NSVG_PAINT_NONE = 0, - NSVG_PAINT_COLOR = 1, - NSVG_PAINT_LINEAR_GRADIENT = 2, - NSVG_PAINT_RADIAL_GRADIENT = 3 -}; - -enum NSVGspreadType { - NSVG_SPREAD_PAD = 0, - NSVG_SPREAD_REFLECT = 1, - NSVG_SPREAD_REPEAT = 2 -}; - -enum NSVGlineJoin { - NSVG_JOIN_MITER = 0, - NSVG_JOIN_ROUND = 1, - NSVG_JOIN_BEVEL = 2 -}; - -enum NSVGlineCap { - NSVG_CAP_BUTT = 0, - NSVG_CAP_ROUND = 1, - NSVG_CAP_SQUARE = 2 -}; - -enum NSVGfillRule { - NSVG_FILLRULE_NONZERO = 0, - NSVG_FILLRULE_EVENODD = 1 -}; - -enum NSVGflags { - NSVG_FLAGS_VISIBLE = 0x01 -}; - -typedef struct NSVGgradientStop { - unsigned int color; - float offset; -} NSVGgradientStop; - -typedef struct NSVGgradient { - float xform[6]; - char spread; - float fx, fy; - int nstops; - NSVGgradientStop stops[1]; -} NSVGgradient; - -typedef struct NSVGpaint { - char type; - union { - unsigned int color; - NSVGgradient* gradient; - }; -} NSVGpaint; - -typedef struct NSVGpath -{ - float* pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... - int npts; // Total number of bezier points. - char closed; // Flag indicating if shapes should be treated as closed. - float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. - struct NSVGpath* next; // Pointer to next path, or NULL if last element. -} NSVGpath; - -typedef struct NSVGshape -{ - char id[64]; // Optional 'id' attr of the shape or its group - NSVGpaint fill; // Fill paint - NSVGpaint stroke; // Stroke paint - float opacity; // Opacity of the shape. - float strokeWidth; // Stroke width (scaled). - float strokeDashOffset; // Stroke dash offset (scaled). - float strokeDashArray[8]; // Stroke dash array (scaled). - char strokeDashCount; // Number of dash values in dash array. - char strokeLineJoin; // Stroke join type. - char strokeLineCap; // Stroke cap type. - float miterLimit; // Miter limit - char fillRule; // Fill rule, see NSVGfillRule. - unsigned char flags; // Logical or of NSVG_FLAGS_* flags - float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. - NSVGpath* paths; // Linked list of paths in the image. - struct NSVGshape* next; // Pointer to next shape, or NULL if last element. -} NSVGshape; - -typedef struct NSVGimage -{ - float width; // Width of the image. - float height; // Height of the image. - NSVGshape* shapes; // Linked list of shapes in the image. -} NSVGimage; - -// Parses SVG file from a file, returns SVG image as paths. -NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi); - -// Parses SVG file from a null terminated string, returns SVG image as paths. -// Important note: changes the string. -NSVGimage* nsvgParse(char* input, const char* units, float dpi); - -// Duplicates a path. -NSVGpath* nsvgDuplicatePath(NSVGpath* p); - -// Deletes an image. -void nsvgDelete(NSVGimage* image); - -#ifndef NANOSVG_CPLUSPLUS -#ifdef __cplusplus -} -#endif -#endif - -#endif // NANOSVG_H - -#ifdef NANOSVG_IMPLEMENTATION - -#include -#include -#include - -#include - -#define NSVG_PI (3.14159265358979323846264338327f) -#define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. - -#define NSVG_ALIGN_MIN 0 -#define NSVG_ALIGN_MID 1 -#define NSVG_ALIGN_MAX 2 -#define NSVG_ALIGN_NONE 0 -#define NSVG_ALIGN_MEET 1 -#define NSVG_ALIGN_SLICE 2 - -#define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) -#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) - -#ifdef _MSC_VER - #pragma warning (disable: 4996) // Switch off security warnings - #pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings - #ifdef __cplusplus - #define NSVG_INLINE inline - #else - #define NSVG_INLINE - #endif -#else - #define NSVG_INLINE inline -#endif - - -static int nsvg__isspace(char c) -{ - return strchr(" \t\n\v\f\r", c) != 0; -} - -static int nsvg__isdigit(char c) -{ - return c >= '0' && c <= '9'; -} - -static int nsvg__isnum(char c) -{ - return strchr("0123456789+-.eE", c) != 0; -} - -static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; } -static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; } - - -// Simple XML parser - -#define NSVG_XML_TAG 1 -#define NSVG_XML_CONTENT 2 -#define NSVG_XML_MAX_ATTRIBS 256 - -static void nsvg__parseContent(char* s, - void (*contentCb)(void* ud, const char* s), - void* ud) -{ - // Trim start white spaces - while (*s && nsvg__isspace(*s)) s++; - if (!*s) return; - - if (contentCb) - (*contentCb)(ud, s); -} - -static void nsvg__parseElement(char* s, - void (*startelCb)(void* ud, const char* el, const char** attr), - void (*endelCb)(void* ud, const char* el), - void* ud) -{ - const char* attr[NSVG_XML_MAX_ATTRIBS]; - int nattr = 0; - char* name; - int start = 0; - int end = 0; - char quote; - - // Skip white space after the '<' - while (*s && nsvg__isspace(*s)) s++; - - // Check if the tag is end tag - if (*s == '/') { - s++; - end = 1; - } else { - start = 1; - } - - // Skip comments, data and preprocessor stuff. - if (!*s || *s == '?' || *s == '!') - return; - - // Get tag name - name = s; - while (*s && !nsvg__isspace(*s)) s++; - if (*s) { *s++ = '\0'; } - - // Get attribs - while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) { - char* name = NULL; - char* value = NULL; - - // Skip white space before the attrib name - while (*s && nsvg__isspace(*s)) s++; - if (!*s) break; - if (*s == '/') { - end = 1; - break; - } - name = s; - // Find end of the attrib name. - while (*s && !nsvg__isspace(*s) && *s != '=') s++; - if (*s) { *s++ = '\0'; } - // Skip until the beginning of the value. - while (*s && *s != '\"' && *s != '\'') s++; - if (!*s) break; - quote = *s; - s++; - // Store value and find the end of it. - value = s; - while (*s && *s != quote) s++; - if (*s) { *s++ = '\0'; } - - // Store only well formed attributes - if (name && value) { - attr[nattr++] = name; - attr[nattr++] = value; - } - } - - // List terminator - attr[nattr++] = 0; - attr[nattr++] = 0; - - // Call callbacks. - if (start && startelCb) - (*startelCb)(ud, name, attr); - if (end && endelCb) - (*endelCb)(ud, name); -} - -int nsvg__parseXML(char* input, - void (*startelCb)(void* ud, const char* el, const char** attr), - void (*endelCb)(void* ud, const char* el), - void (*contentCb)(void* ud, const char* s), - void* ud) -{ - char* s = input; - char* mark = s; - int state = NSVG_XML_CONTENT; - while (*s) { - if (*s == '<' && state == NSVG_XML_CONTENT) { - // Start of a tag - *s++ = '\0'; - nsvg__parseContent(mark, contentCb, ud); - mark = s; - state = NSVG_XML_TAG; - } else if (*s == '>' && state == NSVG_XML_TAG) { - // Start of a content or new tag. - *s++ = '\0'; - nsvg__parseElement(mark, startelCb, endelCb, ud); - mark = s; - state = NSVG_XML_CONTENT; - } else { - s++; - } - } - - return 1; -} - - -/* Simple SVG parser. */ - -#define NSVG_MAX_ATTR 128 - -enum NSVGgradientUnits { - NSVG_USER_SPACE = 0, - NSVG_OBJECT_SPACE = 1 -}; - -#define NSVG_MAX_DASHES 8 - -enum NSVGunits { - NSVG_UNITS_USER, - NSVG_UNITS_PX, - NSVG_UNITS_PT, - NSVG_UNITS_PC, - NSVG_UNITS_MM, - NSVG_UNITS_CM, - NSVG_UNITS_IN, - NSVG_UNITS_PERCENT, - NSVG_UNITS_EM, - NSVG_UNITS_EX -}; - -typedef struct NSVGcoordinate { - float value; - int units; -} NSVGcoordinate; - -typedef struct NSVGlinearData { - NSVGcoordinate x1, y1, x2, y2; -} NSVGlinearData; - -typedef struct NSVGradialData { - NSVGcoordinate cx, cy, r, fx, fy; -} NSVGradialData; - -typedef struct NSVGgradientData -{ - char id[64]; - char ref[64]; - char type; - union { - NSVGlinearData linear; - NSVGradialData radial; - }; - char spread; - char units; - float xform[6]; - int nstops; - NSVGgradientStop* stops; - struct NSVGgradientData* next; -} NSVGgradientData; - -typedef struct NSVGattrib -{ - char id[64]; - float xform[6]; - unsigned int fillColor; - unsigned int strokeColor; - float opacity; - float fillOpacity; - float strokeOpacity; - char fillGradient[64]; - char strokeGradient[64]; - float strokeWidth; - float strokeDashOffset; - float strokeDashArray[NSVG_MAX_DASHES]; - int strokeDashCount; - char strokeLineJoin; - char strokeLineCap; - float miterLimit; - char fillRule; - float fontSize; - unsigned int stopColor; - float stopOpacity; - float stopOffset; - char hasFill; - char hasStroke; - char visible; -} NSVGattrib; - -typedef struct NSVGparser -{ - NSVGattrib attr[NSVG_MAX_ATTR]; - int attrHead; - float* pts; - int npts; - int cpts; - NSVGpath* plist; - NSVGimage* image; - NSVGgradientData* gradients; - NSVGshape* shapesTail; - float viewMinx, viewMiny, viewWidth, viewHeight; - int alignX, alignY, alignType; - float dpi; - char pathFlag; - char defsFlag; -} NSVGparser; - -static void nsvg__xformIdentity(float* t) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = 0.0f; t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetTranslation(float* t, float tx, float ty) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = 0.0f; t[3] = 1.0f; - t[4] = tx; t[5] = ty; -} - -static void nsvg__xformSetScale(float* t, float sx, float sy) -{ - t[0] = sx; t[1] = 0.0f; - t[2] = 0.0f; t[3] = sy; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetSkewX(float* t, float a) -{ - t[0] = 1.0f; t[1] = 0.0f; - t[2] = tanf(a); t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetSkewY(float* t, float a) -{ - t[0] = 1.0f; t[1] = tanf(a); - t[2] = 0.0f; t[3] = 1.0f; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformSetRotation(float* t, float a) -{ - float cs = cosf(a), sn = sinf(a); - t[0] = cs; t[1] = sn; - t[2] = -sn; t[3] = cs; - t[4] = 0.0f; t[5] = 0.0f; -} - -static void nsvg__xformMultiply(float* t, float* s) -{ - float t0 = t[0] * s[0] + t[1] * s[2]; - float t2 = t[2] * s[0] + t[3] * s[2]; - float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; - t[1] = t[0] * s[1] + t[1] * s[3]; - t[3] = t[2] * s[1] + t[3] * s[3]; - t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; - t[0] = t0; - t[2] = t2; - t[4] = t4; -} - -static void nsvg__xformInverse(float* inv, float* t) -{ - double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; - if (det > -1e-6 && det < 1e-6) { - nsvg__xformIdentity(t); - return; - } - invdet = 1.0 / det; - inv[0] = (float)(t[3] * invdet); - inv[2] = (float)(-t[2] * invdet); - inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); - inv[1] = (float)(-t[1] * invdet); - inv[3] = (float)(t[0] * invdet); - inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); -} - -static void nsvg__xformPremultiply(float* t, float* s) -{ - float s2[6]; - memcpy(s2, s, sizeof(float)*6); - nsvg__xformMultiply(s2, t); - memcpy(t, s2, sizeof(float)*6); -} - -static void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t) -{ - *dx = x*t[0] + y*t[2] + t[4]; - *dy = x*t[1] + y*t[3] + t[5]; -} - -static void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t) -{ - *dx = x*t[0] + y*t[2]; - *dy = x*t[1] + y*t[3]; -} - -#define NSVG_EPSILON (1e-12) - -static int nsvg__ptInBounds(float* pt, float* bounds) -{ - return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; -} - - -static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3) -{ - double it = 1.0-t; - return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3; -} - -static void nsvg__curveBounds(float* bounds, float* curve) -{ - int i, j, count; - double roots[2], a, b, c, b2ac, t, v; - float* v0 = &curve[0]; - float* v1 = &curve[2]; - float* v2 = &curve[4]; - float* v3 = &curve[6]; - - // Start the bounding box by end points - bounds[0] = nsvg__minf(v0[0], v3[0]); - bounds[1] = nsvg__minf(v0[1], v3[1]); - bounds[2] = nsvg__maxf(v0[0], v3[0]); - bounds[3] = nsvg__maxf(v0[1], v3[1]); - - // Bezier curve fits inside the convex hull of it's control points. - // If control points are inside the bounds, we're done. - if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) - return; - - // Add bezier curve inflection points in X and Y. - for (i = 0; i < 2; i++) { - a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; - b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; - c = 3.0 * v1[i] - 3.0 * v0[i]; - count = 0; - if (fabs(a) < NSVG_EPSILON) { - if (fabs(b) > NSVG_EPSILON) { - t = -c / b; - if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) - roots[count++] = t; - } - } else { - b2ac = b*b - 4.0*c*a; - if (b2ac > NSVG_EPSILON) { - t = (-b + sqrt(b2ac)) / (2.0 * a); - if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) - roots[count++] = t; - t = (-b - sqrt(b2ac)) / (2.0 * a); - if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) - roots[count++] = t; - } - } - for (j = 0; j < count; j++) { - v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]); - bounds[0+i] = nsvg__minf(bounds[0+i], (float)v); - bounds[2+i] = nsvg__maxf(bounds[2+i], (float)v); - } - } -} - -static NSVGparser* nsvg__createParser() -{ - NSVGparser* p; - p = (NSVGparser*)malloc(sizeof(NSVGparser)); - if (p == NULL) goto error; - memset(p, 0, sizeof(NSVGparser)); - - p->image = (NSVGimage*)malloc(sizeof(NSVGimage)); - if (p->image == NULL) goto error; - memset(p->image, 0, sizeof(NSVGimage)); - - // Init style - nsvg__xformIdentity(p->attr[0].xform); - memset(p->attr[0].id, 0, sizeof p->attr[0].id); - p->attr[0].fillColor = NSVG_RGB(0,0,0); - p->attr[0].strokeColor = NSVG_RGB(0,0,0); - p->attr[0].opacity = 1; - p->attr[0].fillOpacity = 1; - p->attr[0].strokeOpacity = 1; - p->attr[0].stopOpacity = 1; - p->attr[0].strokeWidth = 1; - p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; - p->attr[0].strokeLineCap = NSVG_CAP_BUTT; - p->attr[0].miterLimit = 4; - p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; - p->attr[0].hasFill = 1; - p->attr[0].visible = 1; - - return p; - -error: - if (p) { - if (p->image) free(p->image); - free(p); - } - return NULL; -} - -static void nsvg__deletePaths(NSVGpath* path) -{ - while (path) { - NSVGpath *next = path->next; - if (path->pts != NULL) - free(path->pts); - free(path); - path = next; - } -} - -static void nsvg__deletePaint(NSVGpaint* paint) -{ - if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT) - free(paint->gradient); -} - -static void nsvg__deleteGradientData(NSVGgradientData* grad) -{ - NSVGgradientData* next; - while (grad != NULL) { - next = grad->next; - free(grad->stops); - free(grad); - grad = next; - } -} - -static void nsvg__deleteParser(NSVGparser* p) -{ - if (p != NULL) { - nsvg__deletePaths(p->plist); - nsvg__deleteGradientData(p->gradients); - nsvgDelete(p->image); - free(p->pts); - free(p); - } -} - -static void nsvg__resetPath(NSVGparser* p) -{ - p->npts = 0; -} - -static void nsvg__addPoint(NSVGparser* p, float x, float y) -{ - if (p->npts+1 > p->cpts) { - p->cpts = p->cpts ? p->cpts*2 : 8; - p->pts = (float*)realloc(p->pts, p->cpts*2*sizeof(float)); - if (!p->pts) return; - } - p->pts[p->npts*2+0] = x; - p->pts[p->npts*2+1] = y; - p->npts++; -} - -static void nsvg__moveTo(NSVGparser* p, float x, float y) -{ - if (p->npts > 0) { - p->pts[(p->npts-1)*2+0] = x; - p->pts[(p->npts-1)*2+1] = y; - } else { - nsvg__addPoint(p, x, y); - } -} - -static void nsvg__lineTo(NSVGparser* p, float x, float y) -{ - float px,py, dx,dy; - if (p->npts > 0) { - px = p->pts[(p->npts-1)*2+0]; - py = p->pts[(p->npts-1)*2+1]; - dx = x - px; - dy = y - py; - nsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f); - nsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f); - nsvg__addPoint(p, x, y); - } -} - -static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) -{ - nsvg__addPoint(p, cpx1, cpy1); - nsvg__addPoint(p, cpx2, cpy2); - nsvg__addPoint(p, x, y); -} - -static NSVGattrib* nsvg__getAttr(NSVGparser* p) -{ - return &p->attr[p->attrHead]; -} - -static void nsvg__pushAttr(NSVGparser* p) -{ - if (p->attrHead < NSVG_MAX_ATTR-1) { - p->attrHead++; - memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib)); - } -} - -static void nsvg__popAttr(NSVGparser* p) -{ - if (p->attrHead > 0) - p->attrHead--; -} - -static float nsvg__actualOrigX(NSVGparser* p) -{ - return p->viewMinx; -} - -static float nsvg__actualOrigY(NSVGparser* p) -{ - return p->viewMiny; -} - -static float nsvg__actualWidth(NSVGparser* p) -{ - return p->viewWidth; -} - -static float nsvg__actualHeight(NSVGparser* p) -{ - return p->viewHeight; -} - -static float nsvg__actualLength(NSVGparser* p) -{ - float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p); - return sqrtf(w*w + h*h) / sqrtf(2.0f); -} - -static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, float length) -{ - NSVGattrib* attr = nsvg__getAttr(p); - switch (c.units) { - case NSVG_UNITS_USER: return c.value; - case NSVG_UNITS_PX: return c.value; - case NSVG_UNITS_PT: return c.value / 72.0f * p->dpi; - case NSVG_UNITS_PC: return c.value / 6.0f * p->dpi; - case NSVG_UNITS_MM: return c.value / 25.4f * p->dpi; - case NSVG_UNITS_CM: return c.value / 2.54f * p->dpi; - case NSVG_UNITS_IN: return c.value * p->dpi; - case NSVG_UNITS_EM: return c.value * attr->fontSize; - case NSVG_UNITS_EX: return c.value * attr->fontSize * 0.52f; // x-height of Helvetica. - case NSVG_UNITS_PERCENT: return orig + c.value / 100.0f * length; - default: return c.value; - } - return c.value; -} - -static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) -{ - NSVGgradientData* grad = p->gradients; - while (grad) { - if (strcmp(grad->id, id) == 0) - return grad; - grad = grad->next; - } - return NULL; -} - -static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, char* paintType) -{ - NSVGattrib* attr = nsvg__getAttr(p); - NSVGgradientData* data = NULL; - NSVGgradientData* ref = NULL; - NSVGgradientStop* stops = NULL; - NSVGgradient* grad; - float ox, oy, sw, sh, sl; - int nstops = 0; - - data = nsvg__findGradientData(p, id); - if (data == NULL) return NULL; - - // TODO: use ref to fill in all unset values too. - ref = data; - while (ref != NULL) { - if (stops == NULL && ref->stops != NULL) { - stops = ref->stops; - nstops = ref->nstops; - break; - } - ref = nsvg__findGradientData(p, ref->ref); - } - if (stops == NULL) return NULL; - - grad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1)); - if (grad == NULL) return NULL; - - // The shape width and height. - if (data->units == NSVG_OBJECT_SPACE) { - ox = localBounds[0]; - oy = localBounds[1]; - sw = localBounds[2] - localBounds[0]; - sh = localBounds[3] - localBounds[1]; - } else { - ox = nsvg__actualOrigX(p); - oy = nsvg__actualOrigY(p); - sw = nsvg__actualWidth(p); - sh = nsvg__actualHeight(p); - } - sl = sqrtf(sw*sw + sh*sh) / sqrtf(2.0f); - - if (data->type == NSVG_PAINT_LINEAR_GRADIENT) { - float x1, y1, x2, y2, dx, dy; - x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw); - y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh); - x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw); - y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh); - // Calculate transform aligned to the line - dx = x2 - x1; - dy = y2 - y1; - grad->xform[0] = dy; grad->xform[1] = -dx; - grad->xform[2] = dx; grad->xform[3] = dy; - grad->xform[4] = x1; grad->xform[5] = y1; - } else { - float cx, cy, fx, fy, r; - cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw); - cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh); - fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw); - fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh); - r = nsvg__convertToPixels(p, data->radial.r, 0, sl); - // Calculate transform aligned to the circle - grad->xform[0] = r; grad->xform[1] = 0; - grad->xform[2] = 0; grad->xform[3] = r; - grad->xform[4] = cx; grad->xform[5] = cy; - grad->fx = fx / r; - grad->fy = fy / r; - } - - nsvg__xformMultiply(grad->xform, data->xform); - nsvg__xformMultiply(grad->xform, attr->xform); - - grad->spread = data->spread; - memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)); - grad->nstops = nstops; - - *paintType = data->type; - - return grad; -} - -static float nsvg__getAverageScale(float* t) -{ - float sx = sqrtf(t[0]*t[0] + t[2]*t[2]); - float sy = sqrtf(t[1]*t[1] + t[3]*t[3]); - return (sx + sy) * 0.5f; -} - -static void nsvg__getLocalBounds(float* bounds, NSVGshape *shape, float* xform) -{ - NSVGpath* path; - float curve[4*2], curveBounds[4]; - int i, first = 1; - for (path = shape->paths; path != NULL; path = path->next) { - nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform); - for (i = 0; i < path->npts-1; i += 3) { - nsvg__xformPoint(&curve[2], &curve[3], path->pts[(i+1)*2], path->pts[(i+1)*2+1], xform); - nsvg__xformPoint(&curve[4], &curve[5], path->pts[(i+2)*2], path->pts[(i+2)*2+1], xform); - nsvg__xformPoint(&curve[6], &curve[7], path->pts[(i+3)*2], path->pts[(i+3)*2+1], xform); - nsvg__curveBounds(curveBounds, curve); - if (first) { - bounds[0] = curveBounds[0]; - bounds[1] = curveBounds[1]; - bounds[2] = curveBounds[2]; - bounds[3] = curveBounds[3]; - first = 0; - } else { - bounds[0] = nsvg__minf(bounds[0], curveBounds[0]); - bounds[1] = nsvg__minf(bounds[1], curveBounds[1]); - bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]); - bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]); - } - curve[0] = curve[6]; - curve[1] = curve[7]; - } - } -} - -static void nsvg__addShape(NSVGparser* p) -{ - NSVGattrib* attr = nsvg__getAttr(p); - float scale = 1.0f; - NSVGshape* shape; - NSVGpath* path; - int i; - - if (p->plist == NULL) - return; - - shape = (NSVGshape*)malloc(sizeof(NSVGshape)); - if (shape == NULL) goto error; - memset(shape, 0, sizeof(NSVGshape)); - - memcpy(shape->id, attr->id, sizeof shape->id); - scale = nsvg__getAverageScale(attr->xform); - shape->strokeWidth = attr->strokeWidth * scale; - shape->strokeDashOffset = attr->strokeDashOffset * scale; - shape->strokeDashCount = (char)attr->strokeDashCount; - for (i = 0; i < attr->strokeDashCount; i++) - shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale; - shape->strokeLineJoin = attr->strokeLineJoin; - shape->strokeLineCap = attr->strokeLineCap; - shape->miterLimit = attr->miterLimit; - shape->fillRule = attr->fillRule; - shape->opacity = attr->opacity; - - shape->paths = p->plist; - p->plist = NULL; - - // Calculate shape bounds - shape->bounds[0] = shape->paths->bounds[0]; - shape->bounds[1] = shape->paths->bounds[1]; - shape->bounds[2] = shape->paths->bounds[2]; - shape->bounds[3] = shape->paths->bounds[3]; - for (path = shape->paths->next; path != NULL; path = path->next) { - shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]); - shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]); - shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]); - shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]); - } - - // Set fill - if (attr->hasFill == 0) { - shape->fill.type = NSVG_PAINT_NONE; - } else if (attr->hasFill == 1) { - shape->fill.type = NSVG_PAINT_COLOR; - shape->fill.color = attr->fillColor; - shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24; - } else if (attr->hasFill == 2) { - float inv[6], localBounds[4]; - nsvg__xformInverse(inv, attr->xform); - nsvg__getLocalBounds(localBounds, shape, inv); - shape->fill.gradient = nsvg__createGradient(p, attr->fillGradient, localBounds, &shape->fill.type); - if (shape->fill.gradient == NULL) { - shape->fill.type = NSVG_PAINT_NONE; - } - } - - // Set stroke - if (attr->hasStroke == 0) { - shape->stroke.type = NSVG_PAINT_NONE; - } else if (attr->hasStroke == 1) { - shape->stroke.type = NSVG_PAINT_COLOR; - shape->stroke.color = attr->strokeColor; - shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24; - } else if (attr->hasStroke == 2) { - float inv[6], localBounds[4]; - nsvg__xformInverse(inv, attr->xform); - nsvg__getLocalBounds(localBounds, shape, inv); - shape->stroke.gradient = nsvg__createGradient(p, attr->strokeGradient, localBounds, &shape->stroke.type); - if (shape->stroke.gradient == NULL) - shape->stroke.type = NSVG_PAINT_NONE; - } - - // Set flags - shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00); - - // Add to tail - if (p->image->shapes == NULL) - p->image->shapes = shape; - else - p->shapesTail->next = shape; - p->shapesTail = shape; - - return; - -error: - if (shape) free(shape); -} - -static void nsvg__addPath(NSVGparser* p, char closed) -{ - NSVGattrib* attr = nsvg__getAttr(p); - NSVGpath* path = NULL; - float bounds[4]; - float* curve; - int i; - - if (p->npts < 4) - return; - - if (closed) - nsvg__lineTo(p, p->pts[0], p->pts[1]); - - path = (NSVGpath*)malloc(sizeof(NSVGpath)); - if (path == NULL) goto error; - memset(path, 0, sizeof(NSVGpath)); - - path->pts = (float*)malloc(p->npts*2*sizeof(float)); - if (path->pts == NULL) goto error; - path->closed = closed; - path->npts = p->npts; - - // Transform path. - for (i = 0; i < p->npts; ++i) - nsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform); - - // Find bounds - for (i = 0; i < path->npts-1; i += 3) { - curve = &path->pts[i*2]; - nsvg__curveBounds(bounds, curve); - if (i == 0) { - path->bounds[0] = bounds[0]; - path->bounds[1] = bounds[1]; - path->bounds[2] = bounds[2]; - path->bounds[3] = bounds[3]; - } else { - path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]); - path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]); - path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]); - path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]); - } - } - - path->next = p->plist; - p->plist = path; - - return; - -error: - if (path != NULL) { - if (path->pts != NULL) free(path->pts); - free(path); - } -} - -// We roll our own string to float because the std library one uses locale and messes things up. -static double nsvg__atof(const char* s) -{ - char* cur = (char*)s; - char* end = NULL; - double res = 0.0, sign = 1.0; - long long intPart = 0, fracPart = 0; - char hasIntPart = 0, hasFracPart = 0; - - // Parse optional sign - if (*cur == '+') { - cur++; - } else if (*cur == '-') { - sign = -1; - cur++; - } - - // Parse integer part - if (nsvg__isdigit(*cur)) { - // Parse digit sequence - intPart = (double)strtoll(cur, &end, 10); - if (cur != end) { - res = (double)intPart; - hasIntPart = 1; - cur = end; - } - } - - // Parse fractional part. - if (*cur == '.') { - cur++; // Skip '.' - if (nsvg__isdigit(*cur)) { - // Parse digit sequence - fracPart = strtoll(cur, &end, 10); - if (cur != end) { - res += (double)fracPart / pow(10.0, (double)(end - cur)); - hasFracPart = 1; - cur = end; - } - } - } - - // A valid number should have integer or fractional part. - if (!hasIntPart && !hasFracPart) - return 0.0; - - // Parse optional exponent - if (*cur == 'e' || *cur == 'E') { - int expPart = 0; - cur++; // skip 'E' - expPart = strtol(cur, &end, 10); // Parse digit sequence with sign - if (cur != end) { - res *= pow(10.0, (double)expPart); - } - } - - return res * sign; -} - - -static const char* nsvg__parseNumber(const char* s, char* it, const int size) -{ - const int last = size-1; - int i = 0; - - // sign - if (*s == '-' || *s == '+') { - if (i < last) it[i++] = *s; - s++; - } - // integer part - while (*s && nsvg__isdigit(*s)) { - if (i < last) it[i++] = *s; - s++; - } - if (*s == '.') { - // decimal point - if (i < last) it[i++] = *s; - s++; - // fraction part - while (*s && nsvg__isdigit(*s)) { - if (i < last) it[i++] = *s; - s++; - } - } - // exponent - if (*s == 'e' || *s == 'E') { - if (i < last) it[i++] = *s; - s++; - if (*s == '-' || *s == '+') { - if (i < last) it[i++] = *s; - s++; - } - while (*s && nsvg__isdigit(*s)) { - if (i < last) it[i++] = *s; - s++; - } - } - it[i] = '\0'; - - return s; -} - -static const char* nsvg__getNextPathItem(const char* s, char* it) -{ - it[0] = '\0'; - // Skip white spaces and commas - while (*s && (nsvg__isspace(*s) || *s == ',')) s++; - if (!*s) return s; - if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) { - s = nsvg__parseNumber(s, it, 64); - } else { - // Parse command - it[0] = *s++; - it[1] = '\0'; - return s; - } - - return s; -} - -static unsigned int nsvg__parseColorHex(const char* str) -{ - unsigned int c = 0, r = 0, g = 0, b = 0; - int n = 0; - str++; // skip # - // Calculate number of characters. - while(str[n] && !nsvg__isspace(str[n])) - n++; - if (n == 6) { - sscanf(str, "%x", &c); - } else if (n == 3) { - sscanf(str, "%x", &c); - c = (c&0xf) | ((c&0xf0) << 4) | ((c&0xf00) << 8); - c |= c<<4; - } - r = (c >> 16) & 0xff; - g = (c >> 8) & 0xff; - b = c & 0xff; - return NSVG_RGB(r,g,b); -} - -static unsigned int nsvg__parseColorRGB(const char* str) -{ - int r = -1, g = -1, b = -1; - char s1[32]="", s2[32]=""; - sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b); - if (strchr(s1, '%')) { - return NSVG_RGB((r*255)/100,(g*255)/100,(b*255)/100); - } else { - return NSVG_RGB(r,g,b); - } -} - -typedef struct NSVGNamedColor { - const char* name; - unsigned int color; -} NSVGNamedColor; - -NSVGNamedColor nsvg__colors[] = { - - { "red", NSVG_RGB(255, 0, 0) }, - { "green", NSVG_RGB( 0, 128, 0) }, - { "blue", NSVG_RGB( 0, 0, 255) }, - { "yellow", NSVG_RGB(255, 255, 0) }, - { "cyan", NSVG_RGB( 0, 255, 255) }, - { "magenta", NSVG_RGB(255, 0, 255) }, - { "black", NSVG_RGB( 0, 0, 0) }, - { "grey", NSVG_RGB(128, 128, 128) }, - { "gray", NSVG_RGB(128, 128, 128) }, - { "white", NSVG_RGB(255, 255, 255) }, - -#ifdef NANOSVG_ALL_COLOR_KEYWORDS - { "aliceblue", NSVG_RGB(240, 248, 255) }, - { "antiquewhite", NSVG_RGB(250, 235, 215) }, - { "aqua", NSVG_RGB( 0, 255, 255) }, - { "aquamarine", NSVG_RGB(127, 255, 212) }, - { "azure", NSVG_RGB(240, 255, 255) }, - { "beige", NSVG_RGB(245, 245, 220) }, - { "bisque", NSVG_RGB(255, 228, 196) }, - { "blanchedalmond", NSVG_RGB(255, 235, 205) }, - { "blueviolet", NSVG_RGB(138, 43, 226) }, - { "brown", NSVG_RGB(165, 42, 42) }, - { "burlywood", NSVG_RGB(222, 184, 135) }, - { "cadetblue", NSVG_RGB( 95, 158, 160) }, - { "chartreuse", NSVG_RGB(127, 255, 0) }, - { "chocolate", NSVG_RGB(210, 105, 30) }, - { "coral", NSVG_RGB(255, 127, 80) }, - { "cornflowerblue", NSVG_RGB(100, 149, 237) }, - { "cornsilk", NSVG_RGB(255, 248, 220) }, - { "crimson", NSVG_RGB(220, 20, 60) }, - { "darkblue", NSVG_RGB( 0, 0, 139) }, - { "darkcyan", NSVG_RGB( 0, 139, 139) }, - { "darkgoldenrod", NSVG_RGB(184, 134, 11) }, - { "darkgray", NSVG_RGB(169, 169, 169) }, - { "darkgreen", NSVG_RGB( 0, 100, 0) }, - { "darkgrey", NSVG_RGB(169, 169, 169) }, - { "darkkhaki", NSVG_RGB(189, 183, 107) }, - { "darkmagenta", NSVG_RGB(139, 0, 139) }, - { "darkolivegreen", NSVG_RGB( 85, 107, 47) }, - { "darkorange", NSVG_RGB(255, 140, 0) }, - { "darkorchid", NSVG_RGB(153, 50, 204) }, - { "darkred", NSVG_RGB(139, 0, 0) }, - { "darksalmon", NSVG_RGB(233, 150, 122) }, - { "darkseagreen", NSVG_RGB(143, 188, 143) }, - { "darkslateblue", NSVG_RGB( 72, 61, 139) }, - { "darkslategray", NSVG_RGB( 47, 79, 79) }, - { "darkslategrey", NSVG_RGB( 47, 79, 79) }, - { "darkturquoise", NSVG_RGB( 0, 206, 209) }, - { "darkviolet", NSVG_RGB(148, 0, 211) }, - { "deeppink", NSVG_RGB(255, 20, 147) }, - { "deepskyblue", NSVG_RGB( 0, 191, 255) }, - { "dimgray", NSVG_RGB(105, 105, 105) }, - { "dimgrey", NSVG_RGB(105, 105, 105) }, - { "dodgerblue", NSVG_RGB( 30, 144, 255) }, - { "firebrick", NSVG_RGB(178, 34, 34) }, - { "floralwhite", NSVG_RGB(255, 250, 240) }, - { "forestgreen", NSVG_RGB( 34, 139, 34) }, - { "fuchsia", NSVG_RGB(255, 0, 255) }, - { "gainsboro", NSVG_RGB(220, 220, 220) }, - { "ghostwhite", NSVG_RGB(248, 248, 255) }, - { "gold", NSVG_RGB(255, 215, 0) }, - { "goldenrod", NSVG_RGB(218, 165, 32) }, - { "greenyellow", NSVG_RGB(173, 255, 47) }, - { "honeydew", NSVG_RGB(240, 255, 240) }, - { "hotpink", NSVG_RGB(255, 105, 180) }, - { "indianred", NSVG_RGB(205, 92, 92) }, - { "indigo", NSVG_RGB( 75, 0, 130) }, - { "ivory", NSVG_RGB(255, 255, 240) }, - { "khaki", NSVG_RGB(240, 230, 140) }, - { "lavender", NSVG_RGB(230, 230, 250) }, - { "lavenderblush", NSVG_RGB(255, 240, 245) }, - { "lawngreen", NSVG_RGB(124, 252, 0) }, - { "lemonchiffon", NSVG_RGB(255, 250, 205) }, - { "lightblue", NSVG_RGB(173, 216, 230) }, - { "lightcoral", NSVG_RGB(240, 128, 128) }, - { "lightcyan", NSVG_RGB(224, 255, 255) }, - { "lightgoldenrodyellow", NSVG_RGB(250, 250, 210) }, - { "lightgray", NSVG_RGB(211, 211, 211) }, - { "lightgreen", NSVG_RGB(144, 238, 144) }, - { "lightgrey", NSVG_RGB(211, 211, 211) }, - { "lightpink", NSVG_RGB(255, 182, 193) }, - { "lightsalmon", NSVG_RGB(255, 160, 122) }, - { "lightseagreen", NSVG_RGB( 32, 178, 170) }, - { "lightskyblue", NSVG_RGB(135, 206, 250) }, - { "lightslategray", NSVG_RGB(119, 136, 153) }, - { "lightslategrey", NSVG_RGB(119, 136, 153) }, - { "lightsteelblue", NSVG_RGB(176, 196, 222) }, - { "lightyellow", NSVG_RGB(255, 255, 224) }, - { "lime", NSVG_RGB( 0, 255, 0) }, - { "limegreen", NSVG_RGB( 50, 205, 50) }, - { "linen", NSVG_RGB(250, 240, 230) }, - { "maroon", NSVG_RGB(128, 0, 0) }, - { "mediumaquamarine", NSVG_RGB(102, 205, 170) }, - { "mediumblue", NSVG_RGB( 0, 0, 205) }, - { "mediumorchid", NSVG_RGB(186, 85, 211) }, - { "mediumpurple", NSVG_RGB(147, 112, 219) }, - { "mediumseagreen", NSVG_RGB( 60, 179, 113) }, - { "mediumslateblue", NSVG_RGB(123, 104, 238) }, - { "mediumspringgreen", NSVG_RGB( 0, 250, 154) }, - { "mediumturquoise", NSVG_RGB( 72, 209, 204) }, - { "mediumvioletred", NSVG_RGB(199, 21, 133) }, - { "midnightblue", NSVG_RGB( 25, 25, 112) }, - { "mintcream", NSVG_RGB(245, 255, 250) }, - { "mistyrose", NSVG_RGB(255, 228, 225) }, - { "moccasin", NSVG_RGB(255, 228, 181) }, - { "navajowhite", NSVG_RGB(255, 222, 173) }, - { "navy", NSVG_RGB( 0, 0, 128) }, - { "oldlace", NSVG_RGB(253, 245, 230) }, - { "olive", NSVG_RGB(128, 128, 0) }, - { "olivedrab", NSVG_RGB(107, 142, 35) }, - { "orange", NSVG_RGB(255, 165, 0) }, - { "orangered", NSVG_RGB(255, 69, 0) }, - { "orchid", NSVG_RGB(218, 112, 214) }, - { "palegoldenrod", NSVG_RGB(238, 232, 170) }, - { "palegreen", NSVG_RGB(152, 251, 152) }, - { "paleturquoise", NSVG_RGB(175, 238, 238) }, - { "palevioletred", NSVG_RGB(219, 112, 147) }, - { "papayawhip", NSVG_RGB(255, 239, 213) }, - { "peachpuff", NSVG_RGB(255, 218, 185) }, - { "peru", NSVG_RGB(205, 133, 63) }, - { "pink", NSVG_RGB(255, 192, 203) }, - { "plum", NSVG_RGB(221, 160, 221) }, - { "powderblue", NSVG_RGB(176, 224, 230) }, - { "purple", NSVG_RGB(128, 0, 128) }, - { "rosybrown", NSVG_RGB(188, 143, 143) }, - { "royalblue", NSVG_RGB( 65, 105, 225) }, - { "saddlebrown", NSVG_RGB(139, 69, 19) }, - { "salmon", NSVG_RGB(250, 128, 114) }, - { "sandybrown", NSVG_RGB(244, 164, 96) }, - { "seagreen", NSVG_RGB( 46, 139, 87) }, - { "seashell", NSVG_RGB(255, 245, 238) }, - { "sienna", NSVG_RGB(160, 82, 45) }, - { "silver", NSVG_RGB(192, 192, 192) }, - { "skyblue", NSVG_RGB(135, 206, 235) }, - { "slateblue", NSVG_RGB(106, 90, 205) }, - { "slategray", NSVG_RGB(112, 128, 144) }, - { "slategrey", NSVG_RGB(112, 128, 144) }, - { "snow", NSVG_RGB(255, 250, 250) }, - { "springgreen", NSVG_RGB( 0, 255, 127) }, - { "steelblue", NSVG_RGB( 70, 130, 180) }, - { "tan", NSVG_RGB(210, 180, 140) }, - { "teal", NSVG_RGB( 0, 128, 128) }, - { "thistle", NSVG_RGB(216, 191, 216) }, - { "tomato", NSVG_RGB(255, 99, 71) }, - { "turquoise", NSVG_RGB( 64, 224, 208) }, - { "violet", NSVG_RGB(238, 130, 238) }, - { "wheat", NSVG_RGB(245, 222, 179) }, - { "whitesmoke", NSVG_RGB(245, 245, 245) }, - { "yellowgreen", NSVG_RGB(154, 205, 50) }, -#endif -}; - -static unsigned int nsvg__parseColorName(const char* str) -{ - int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); - - for (i = 0; i < ncolors; i++) { - if (strcmp(nsvg__colors[i].name, str) == 0) { - return nsvg__colors[i].color; - } - } - - return NSVG_RGB(128, 128, 128); -} - -static unsigned int nsvg__parseColor(const char* str) -{ - size_t len = 0; - while(*str == ' ') ++str; - len = strlen(str); - if (len >= 1 && *str == '#') - return nsvg__parseColorHex(str); - else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') - return nsvg__parseColorRGB(str); - return nsvg__parseColorName(str); -} - -static float nsvg__parseOpacity(const char* str) -{ - float val = nsvg__atof(str); - if (val < 0.0f) val = 0.0f; - if (val > 1.0f) val = 1.0f; - return val; -} - -static float nsvg__parseMiterLimit(const char* str) -{ - float val = nsvg__atof(str); - if (val < 0.0f) val = 0.0f; - return val; -} - -static int nsvg__parseUnits(const char* units) -{ - if (units[0] == 'p' && units[1] == 'x') - return NSVG_UNITS_PX; - else if (units[0] == 'p' && units[1] == 't') - return NSVG_UNITS_PT; - else if (units[0] == 'p' && units[1] == 'c') - return NSVG_UNITS_PC; - else if (units[0] == 'm' && units[1] == 'm') - return NSVG_UNITS_MM; - else if (units[0] == 'c' && units[1] == 'm') - return NSVG_UNITS_CM; - else if (units[0] == 'i' && units[1] == 'n') - return NSVG_UNITS_IN; - else if (units[0] == '%') - return NSVG_UNITS_PERCENT; - else if (units[0] == 'e' && units[1] == 'm') - return NSVG_UNITS_EM; - else if (units[0] == 'e' && units[1] == 'x') - return NSVG_UNITS_EX; - return NSVG_UNITS_USER; -} - -static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str) -{ - NSVGcoordinate coord = {0, NSVG_UNITS_USER}; - char buf[64]; - coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64)); - coord.value = nsvg__atof(buf); - return coord; -} - -static NSVGcoordinate nsvg__coord(float v, int units) -{ - NSVGcoordinate coord = {v, units}; - return coord; -} - -static float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length) -{ - NSVGcoordinate coord = nsvg__parseCoordinateRaw(str); - return nsvg__convertToPixels(p, coord, orig, length); -} - -static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na) -{ - const char* end; - const char* ptr; - char it[64]; - - *na = 0; - ptr = str; - while (*ptr && *ptr != '(') ++ptr; - if (*ptr == 0) - return 1; - end = ptr; - while (*end && *end != ')') ++end; - if (*end == 0) - return 1; - - while (ptr < end) { - if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) { - if (*na >= maxNa) return 0; - ptr = nsvg__parseNumber(ptr, it, 64); - args[(*na)++] = (float)nsvg__atof(it); - } else { - ++ptr; - } - } - return (int)(end - str); -} - - -static int nsvg__parseMatrix(float* xform, const char* str) -{ - float t[6]; - int na = 0; - int len = nsvg__parseTransformArgs(str, t, 6, &na); - if (na != 6) return len; - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseTranslate(float* xform, const char* str) -{ - float args[2]; - float t[6]; - int na = 0; - int len = nsvg__parseTransformArgs(str, args, 2, &na); - if (na == 1) args[1] = 0.0; - - nsvg__xformSetTranslation(t, args[0], args[1]); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseScale(float* xform, const char* str) -{ - float args[2]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 2, &na); - if (na == 1) args[1] = args[0]; - nsvg__xformSetScale(t, args[0], args[1]); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseSkewX(float* xform, const char* str) -{ - float args[1]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 1, &na); - nsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseSkewY(float* xform, const char* str) -{ - float args[1]; - int na = 0; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 1, &na); - nsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI); - memcpy(xform, t, sizeof(float)*6); - return len; -} - -static int nsvg__parseRotate(float* xform, const char* str) -{ - float args[3]; - int na = 0; - float m[6]; - float t[6]; - int len = nsvg__parseTransformArgs(str, args, 3, &na); - if (na == 1) - args[1] = args[2] = 0.0f; - nsvg__xformIdentity(m); - - if (na > 1) { - nsvg__xformSetTranslation(t, -args[1], -args[2]); - nsvg__xformMultiply(m, t); - } - - nsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI); - nsvg__xformMultiply(m, t); - - if (na > 1) { - nsvg__xformSetTranslation(t, args[1], args[2]); - nsvg__xformMultiply(m, t); - } - - memcpy(xform, m, sizeof(float)*6); - - return len; -} - -static void nsvg__parseTransform(float* xform, const char* str) -{ - float t[6]; - nsvg__xformIdentity(xform); - while (*str) - { - if (strncmp(str, "matrix", 6) == 0) - str += nsvg__parseMatrix(t, str); - else if (strncmp(str, "translate", 9) == 0) - str += nsvg__parseTranslate(t, str); - else if (strncmp(str, "scale", 5) == 0) - str += nsvg__parseScale(t, str); - else if (strncmp(str, "rotate", 6) == 0) - str += nsvg__parseRotate(t, str); - else if (strncmp(str, "skewX", 5) == 0) - str += nsvg__parseSkewX(t, str); - else if (strncmp(str, "skewY", 5) == 0) - str += nsvg__parseSkewY(t, str); - else{ - ++str; - continue; - } - - nsvg__xformPremultiply(xform, t); - } -} - -static void nsvg__parseUrl(char* id, const char* str) -{ - int i = 0; - str += 4; // "url("; - if (*str == '#') - str++; - while (i < 63 && *str != ')') { - id[i] = *str++; - i++; - } - id[i] = '\0'; -} - -static char nsvg__parseLineCap(const char* str) -{ - if (strcmp(str, "butt") == 0) - return NSVG_CAP_BUTT; - else if (strcmp(str, "round") == 0) - return NSVG_CAP_ROUND; - else if (strcmp(str, "square") == 0) - return NSVG_CAP_SQUARE; - // TODO: handle inherit. - return NSVG_CAP_BUTT; -} - -static char nsvg__parseLineJoin(const char* str) -{ - if (strcmp(str, "miter") == 0) - return NSVG_JOIN_MITER; - else if (strcmp(str, "round") == 0) - return NSVG_JOIN_ROUND; - else if (strcmp(str, "bevel") == 0) - return NSVG_JOIN_BEVEL; - // TODO: handle inherit. - return NSVG_JOIN_MITER; -} - -static char nsvg__parseFillRule(const char* str) -{ - if (strcmp(str, "nonzero") == 0) - return NSVG_FILLRULE_NONZERO; - else if (strcmp(str, "evenodd") == 0) - return NSVG_FILLRULE_EVENODD; - // TODO: handle inherit. - return NSVG_FILLRULE_NONZERO; -} - -static const char* nsvg__getNextDashItem(const char* s, char* it) -{ - int n = 0; - it[0] = '\0'; - // Skip white spaces and commas - while (*s && (nsvg__isspace(*s) || *s == ',')) s++; - // Advance until whitespace, comma or end. - while (*s && (!nsvg__isspace(*s) && *s != ',')) { - if (n < 63) - it[n++] = *s; - s++; - } - it[n++] = '\0'; - return s; -} - -static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray) -{ - char item[64]; - int count = 0, i; - float sum = 0.0f; - - // Handle "none" - if (str[0] == 'n') - return 0; - - // Parse dashes - while (*str) { - str = nsvg__getNextDashItem(str, item); - if (!*item) break; - if (count < NSVG_MAX_DASHES) - strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p))); - } - - for (i = 0; i < count; i++) - sum += strokeDashArray[i]; - if (sum <= 1e-6f) - count = 0; - - return count; -} - -static void nsvg__parseStyle(NSVGparser* p, const char* str); - -static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value) -{ - float xform[6]; - NSVGattrib* attr = nsvg__getAttr(p); - if (!attr) return 0; - - if (strcmp(name, "style") == 0) { - nsvg__parseStyle(p, value); - } else if (strcmp(name, "display") == 0) { - if (strcmp(value, "none") == 0) - attr->visible = 0; - // Don't reset ->visible on display:inline, one display:none hides the whole subtree - - } else if (strcmp(name, "fill") == 0) { - if (strcmp(value, "none") == 0) { - attr->hasFill = 0; - } else if (strncmp(value, "url(", 4) == 0) { - attr->hasFill = 2; - nsvg__parseUrl(attr->fillGradient, value); - } else { - attr->hasFill = 1; - attr->fillColor = nsvg__parseColor(value); - } - } else if (strcmp(name, "opacity") == 0) { - attr->opacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "fill-opacity") == 0) { - attr->fillOpacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "stroke") == 0) { - if (strcmp(value, "none") == 0) { - attr->hasStroke = 0; - } else if (strncmp(value, "url(", 4) == 0) { - attr->hasStroke = 2; - nsvg__parseUrl(attr->strokeGradient, value); - } else { - attr->hasStroke = 1; - attr->strokeColor = nsvg__parseColor(value); - } - } else if (strcmp(name, "stroke-width") == 0) { - attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } else if (strcmp(name, "stroke-dasharray") == 0) { - attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray); - } else if (strcmp(name, "stroke-dashoffset") == 0) { - attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } else if (strcmp(name, "stroke-opacity") == 0) { - attr->strokeOpacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "stroke-linecap") == 0) { - attr->strokeLineCap = nsvg__parseLineCap(value); - } else if (strcmp(name, "stroke-linejoin") == 0) { - attr->strokeLineJoin = nsvg__parseLineJoin(value); - } else if (strcmp(name, "stroke-miterlimit") == 0) { - attr->miterLimit = nsvg__parseMiterLimit(value); - } else if (strcmp(name, "fill-rule") == 0) { - attr->fillRule = nsvg__parseFillRule(value); - } else if (strcmp(name, "font-size") == 0) { - attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); - } else if (strcmp(name, "transform") == 0) { - nsvg__parseTransform(xform, value); - nsvg__xformPremultiply(attr->xform, xform); - } else if (strcmp(name, "stop-color") == 0) { - attr->stopColor = nsvg__parseColor(value); - } else if (strcmp(name, "stop-opacity") == 0) { - attr->stopOpacity = nsvg__parseOpacity(value); - } else if (strcmp(name, "offset") == 0) { - attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f); - } else if (strcmp(name, "id") == 0) { - strncpy(attr->id, value, 63); - attr->id[63] = '\0'; - } else { - return 0; - } - return 1; -} - -static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end) -{ - const char* str; - const char* val; - char name[512]; - char value[512]; - int n; - - str = start; - while (str < end && *str != ':') ++str; - - val = str; - - // Right Trim - while (str > start && (*str == ':' || nsvg__isspace(*str))) --str; - ++str; - - n = (int)(str - start); - if (n > 511) n = 511; - if (n) memcpy(name, start, n); - name[n] = 0; - - while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val; - - n = (int)(end - val); - if (n > 511) n = 511; - if (n) memcpy(value, val, n); - value[n] = 0; - - return nsvg__parseAttr(p, name, value); -} - -static void nsvg__parseStyle(NSVGparser* p, const char* str) -{ - const char* start; - const char* end; - - while (*str) { - // Left Trim - while(*str && nsvg__isspace(*str)) ++str; - start = str; - while(*str && *str != ';') ++str; - end = str; - - // Right Trim - while (end > start && (*end == ';' || nsvg__isspace(*end))) --end; - ++end; - - nsvg__parseNameValue(p, start, end); - if (*str) ++str; - } -} - -static void nsvg__parseAttribs(NSVGparser* p, const char** attr) -{ - int i; - for (i = 0; attr[i]; i += 2) - { - if (strcmp(attr[i], "style") == 0) - nsvg__parseStyle(p, attr[i + 1]); - else - nsvg__parseAttr(p, attr[i], attr[i + 1]); - } -} - -static int nsvg__getArgsPerElement(char cmd) -{ - switch (cmd) { - case 'v': - case 'V': - case 'h': - case 'H': - return 1; - case 'm': - case 'M': - case 'l': - case 'L': - case 't': - case 'T': - return 2; - case 'q': - case 'Q': - case 's': - case 'S': - return 4; - case 'c': - case 'C': - return 6; - case 'a': - case 'A': - return 7; - } - return 0; -} - -static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) { - *cpx += args[0]; - *cpy += args[1]; - } else { - *cpx = args[0]; - *cpy = args[1]; - } - nsvg__moveTo(p, *cpx, *cpy); -} - -static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) { - *cpx += args[0]; - *cpy += args[1]; - } else { - *cpx = args[0]; - *cpy = args[1]; - } - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) - *cpx += args[0]; - else - *cpx = args[0]; - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - if (rel) - *cpy += args[0]; - else - *cpy = args[0]; - nsvg__lineTo(p, *cpx, *cpy); -} - -static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x2, y2, cx1, cy1, cx2, cy2; - - if (rel) { - cx1 = *cpx + args[0]; - cy1 = *cpy + args[1]; - cx2 = *cpx + args[2]; - cy2 = *cpy + args[3]; - x2 = *cpx + args[4]; - y2 = *cpy + args[5]; - } else { - cx1 = args[0]; - cy1 = args[1]; - cx2 = args[2]; - cy2 = args[3]; - x2 = args[4]; - y2 = args[5]; - } - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx2; - *cpy2 = cy2; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x1, y1, x2, y2, cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - cx2 = *cpx + args[0]; - cy2 = *cpy + args[1]; - x2 = *cpx + args[2]; - y2 = *cpy + args[3]; - } else { - cx2 = args[0]; - cy2 = args[1]; - x2 = args[2]; - y2 = args[3]; - } - - cx1 = 2*x1 - *cpx2; - cy1 = 2*y1 - *cpy2; - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx2; - *cpy2 = cy2; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x1, y1, x2, y2, cx, cy; - float cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - cx = *cpx + args[0]; - cy = *cpy + args[1]; - x2 = *cpx + args[2]; - y2 = *cpy + args[3]; - } else { - cx = args[0]; - cy = args[1]; - x2 = args[2]; - y2 = args[3]; - } - - // Convert to cubic bezier - cx1 = x1 + 2.0f/3.0f*(cx - x1); - cy1 = y1 + 2.0f/3.0f*(cy - y1); - cx2 = x2 + 2.0f/3.0f*(cx - x2); - cy2 = y2 + 2.0f/3.0f*(cy - y2); - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx; - *cpy2 = cy; - *cpx = x2; - *cpy = y2; -} - -static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy, - float* cpx2, float* cpy2, float* args, int rel) -{ - float x1, y1, x2, y2, cx, cy; - float cx1, cy1, cx2, cy2; - - x1 = *cpx; - y1 = *cpy; - if (rel) { - x2 = *cpx + args[0]; - y2 = *cpy + args[1]; - } else { - x2 = args[0]; - y2 = args[1]; - } - - cx = 2*x1 - *cpx2; - cy = 2*y1 - *cpy2; - - // Convert to cubix bezier - cx1 = x1 + 2.0f/3.0f*(cx - x1); - cy1 = y1 + 2.0f/3.0f*(cy - y1); - cx2 = x2 + 2.0f/3.0f*(cx - x2); - cy2 = y2 + 2.0f/3.0f*(cy - y2); - - nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); - - *cpx2 = cx; - *cpy2 = cy; - *cpx = x2; - *cpy = y2; -} - -static float nsvg__sqr(float x) { return x*x; } -static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); } - -static float nsvg__vecrat(float ux, float uy, float vx, float vy) -{ - return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy)); -} - -static float nsvg__vecang(float ux, float uy, float vx, float vy) -{ - float r = nsvg__vecrat(ux,uy, vx,vy); - if (r < -1.0f) r = -1.0f; - if (r > 1.0f) r = 1.0f; - return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r); -} - -static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) -{ - // Ported from canvg (https://code.google.com/p/canvg/) - float rx, ry, rotx; - float x1, y1, x2, y2, cx, cy, dx, dy, d; - float x1p, y1p, cxp, cyp, s, sa, sb; - float ux, uy, vx, vy, a1, da; - float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; - float sinrx, cosrx; - int fa, fs; - int i, ndivs; - float hda, kappa; - - rx = fabsf(args[0]); // y radius - ry = fabsf(args[1]); // x radius - rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle - fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc - fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction - x1 = *cpx; // start point - y1 = *cpy; - if (rel) { // end point - x2 = *cpx + args[5]; - y2 = *cpy + args[6]; - } else { - x2 = args[5]; - y2 = args[6]; - } - - dx = x1 - x2; - dy = y1 - y2; - d = sqrtf(dx*dx + dy*dy); - if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { - // The arc degenerates to a line - nsvg__lineTo(p, x2, y2); - *cpx = x2; - *cpy = y2; - return; - } - - sinrx = sinf(rotx); - cosrx = cosf(rotx); - - // Convert to center point parameterization. - // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes - // 1) Compute x1', y1' - x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; - y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; - d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry); - if (d > 1) { - d = sqrtf(d); - rx *= d; - ry *= d; - } - // 2) Compute cx', cy' - s = 0.0f; - sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p); - sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p); - if (sa < 0.0f) sa = 0.0f; - if (sb > 0.0f) - s = sqrtf(sa / sb); - if (fa == fs) - s = -s; - cxp = s * rx * y1p / ry; - cyp = s * -ry * x1p / rx; - - // 3) Compute cx,cy from cx',cy' - cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp; - cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp; - - // 4) Calculate theta1, and delta theta. - ux = (x1p - cxp) / rx; - uy = (y1p - cyp) / ry; - vx = (-x1p - cxp) / rx; - vy = (-y1p - cyp) / ry; - a1 = nsvg__vecang(1.0f,0.0f, ux,uy); // Initial angle - da = nsvg__vecang(ux,uy, vx,vy); // Delta angle - -// if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; -// if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; - - if (fs == 0 && da > 0) - da -= 2 * NSVG_PI; - else if (fs == 1 && da < 0) - da += 2 * NSVG_PI; - - // Approximate the arc using cubic spline segments. - t[0] = cosrx; t[1] = sinrx; - t[2] = -sinrx; t[3] = cosrx; - t[4] = cx; t[5] = cy; - - // Split arc into max 90 degree segments. - // The loop assumes an iteration per end point (including start and end), this +1. - ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f); - hda = (da / (float)ndivs) / 2.0f; - kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); - if (da < 0.0f) - kappa = -kappa; - - for (i = 0; i <= ndivs; i++) { - a = a1 + da * ((float)i/(float)ndivs); - dx = cosf(a); - dy = sinf(a); - nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position - nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent - if (i > 0) - nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y); - px = x; - py = y; - ptanx = tanx; - ptany = tany; - } - - *cpx = x2; - *cpy = y2; -} - -static void nsvg__parsePath(NSVGparser* p, const char** attr) -{ - const char* s = NULL; - char cmd = '\0'; - float args[10]; - int nargs; - int rargs = 0; - float cpx, cpy, cpx2, cpy2; - const char* tmp[4]; - char closedFlag; - int i; - char item[64]; - - for (i = 0; attr[i]; i += 2) { - if (strcmp(attr[i], "d") == 0) { - s = attr[i + 1]; - } else { - tmp[0] = attr[i]; - tmp[1] = attr[i + 1]; - tmp[2] = 0; - tmp[3] = 0; - nsvg__parseAttribs(p, tmp); - } - } - - if (s) { - nsvg__resetPath(p); - cpx = 0; cpy = 0; - cpx2 = 0; cpy2 = 0; - closedFlag = 0; - nargs = 0; - - while (*s) { - s = nsvg__getNextPathItem(s, item); - if (!*item) break; - if (nsvg__isnum(item[0])) { - if (nargs < 10) - args[nargs++] = (float)nsvg__atof(item); - if (nargs >= rargs) { - switch (cmd) { - case 'm': - case 'M': - nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); - // Moveto can be followed by multiple coordinate pairs, - // which should be treated as linetos. - cmd = (cmd == 'm') ? 'l' : 'L'; - rargs = nsvg__getArgsPerElement(cmd); - cpx2 = cpx; cpy2 = cpy; - break; - case 'l': - case 'L': - nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - case 'H': - case 'h': - nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - case 'V': - case 'v': - nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - case 'C': - case 'c': - nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); - break; - case 'S': - case 's': - nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); - break; - case 'Q': - case 'q': - nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); - break; - case 'T': - case 't': - nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); - break; - case 'A': - case 'a': - nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); - cpx2 = cpx; cpy2 = cpy; - break; - default: - if (nargs >= 2) { - cpx = args[nargs-2]; - cpy = args[nargs-1]; - cpx2 = cpx; cpy2 = cpy; - } - break; - } - nargs = 0; - } - } else { - cmd = item[0]; - rargs = nsvg__getArgsPerElement(cmd); - if (cmd == 'M' || cmd == 'm') { - // Commit path. - if (p->npts > 0) - nsvg__addPath(p, closedFlag); - // Start new subpath. - nsvg__resetPath(p); - closedFlag = 0; - nargs = 0; - } else if (cmd == 'Z' || cmd == 'z') { - closedFlag = 1; - // Commit path. - if (p->npts > 0) { - // Move current point to first point - cpx = p->pts[0]; - cpy = p->pts[1]; - cpx2 = cpx; cpy2 = cpy; - nsvg__addPath(p, closedFlag); - } - // Start new subpath. - nsvg__resetPath(p); - nsvg__moveTo(p, cpx, cpy); - closedFlag = 0; - nargs = 0; - } - } - } - // Commit path. - if (p->npts) - nsvg__addPath(p, closedFlag); - } - - nsvg__addShape(p); -} - -static void nsvg__parseRect(NSVGparser* p, const char** attr) -{ - float x = 0.0f; - float y = 0.0f; - float w = 0.0f; - float h = 0.0f; - float rx = -1.0f; // marks not set - float ry = -1.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "x") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "width") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)); - if (strcmp(attr[i], "height") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)); - if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); - if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); - } - } - - if (rx < 0.0f && ry > 0.0f) rx = ry; - if (ry < 0.0f && rx > 0.0f) ry = rx; - if (rx < 0.0f) rx = 0.0f; - if (ry < 0.0f) ry = 0.0f; - if (rx > w/2.0f) rx = w/2.0f; - if (ry > h/2.0f) ry = h/2.0f; - - if (w != 0.0f && h != 0.0f) { - nsvg__resetPath(p); - - if (rx < 0.00001f || ry < 0.0001f) { - nsvg__moveTo(p, x, y); - nsvg__lineTo(p, x+w, y); - nsvg__lineTo(p, x+w, y+h); - nsvg__lineTo(p, x, y+h); - } else { - // Rounded rectangle - nsvg__moveTo(p, x+rx, y); - nsvg__lineTo(p, x+w-rx, y); - nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry); - nsvg__lineTo(p, x+w, y+h-ry); - nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h); - nsvg__lineTo(p, x+rx, y+h); - nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry); - nsvg__lineTo(p, x, y+ry); - nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y); - } - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseCircle(NSVGparser* p, const char** attr) -{ - float cx = 0.0f; - float cy = 0.0f; - float r = 0.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p))); - } - } - - if (r > 0.0f) { - nsvg__resetPath(p); - - nsvg__moveTo(p, cx+r, cy); - nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r); - nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy); - nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r); - nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy); - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseEllipse(NSVGparser* p, const char** attr) -{ - float cx = 0.0f; - float cy = 0.0f; - float rx = 0.0f; - float ry = 0.0f; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); - if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); - } - } - - if (rx > 0.0f && ry > 0.0f) { - - nsvg__resetPath(p); - - nsvg__moveTo(p, cx+rx, cy); - nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry); - nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy); - nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry); - nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy); - - nsvg__addPath(p, 1); - - nsvg__addShape(p); - } -} - -static void nsvg__parseLine(NSVGparser* p, const char** attr) -{ - float x1 = 0.0; - float y1 = 0.0; - float x2 = 0.0; - float y2 = 0.0; - int i; - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); - if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); - } - } - - nsvg__resetPath(p); - - nsvg__moveTo(p, x1, y1); - nsvg__lineTo(p, x2, y2); - - nsvg__addPath(p, 0); - - nsvg__addShape(p); -} - -static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag) -{ - int i; - const char* s; - float args[2]; - int nargs, npts = 0; - char item[64]; - - nsvg__resetPath(p); - - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "points") == 0) { - s = attr[i + 1]; - nargs = 0; - while (*s) { - s = nsvg__getNextPathItem(s, item); - args[nargs++] = (float)nsvg__atof(item); - if (nargs >= 2) { - if (npts == 0) - nsvg__moveTo(p, args[0], args[1]); - else - nsvg__lineTo(p, args[0], args[1]); - nargs = 0; - npts++; - } - } - } - } - } - - nsvg__addPath(p, (char)closeFlag); - - nsvg__addShape(p); -} - -static void nsvg__parseSVG(NSVGparser* p, const char** attr) -{ - int i; - for (i = 0; attr[i]; i += 2) { - if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "width") == 0) { - p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); - } else if (strcmp(attr[i], "height") == 0) { - p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); - } else if (strcmp(attr[i], "viewBox") == 0) { - const char *s = attr[i + 1]; - char buf[64]; - s = nsvg__parseNumber(s, buf, 64); - p->viewMinx = nsvg__atof(buf); - while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; - if (!*s) return; - s = nsvg__parseNumber(s, buf, 64); - p->viewMiny = nsvg__atof(buf); - while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; - if (!*s) return; - s = nsvg__parseNumber(s, buf, 64); - p->viewWidth = nsvg__atof(buf); - while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; - if (!*s) return; - s = nsvg__parseNumber(s, buf, 64); - p->viewHeight = nsvg__atof(buf); - } else if (strcmp(attr[i], "preserveAspectRatio") == 0) { - if (strstr(attr[i + 1], "none") != 0) { - // No uniform scaling - p->alignType = NSVG_ALIGN_NONE; - } else { - // Parse X align - if (strstr(attr[i + 1], "xMin") != 0) - p->alignX = NSVG_ALIGN_MIN; - else if (strstr(attr[i + 1], "xMid") != 0) - p->alignX = NSVG_ALIGN_MID; - else if (strstr(attr[i + 1], "xMax") != 0) - p->alignX = NSVG_ALIGN_MAX; - // Parse X align - if (strstr(attr[i + 1], "yMin") != 0) - p->alignY = NSVG_ALIGN_MIN; - else if (strstr(attr[i + 1], "yMid") != 0) - p->alignY = NSVG_ALIGN_MID; - else if (strstr(attr[i + 1], "yMax") != 0) - p->alignY = NSVG_ALIGN_MAX; - // Parse meet/slice - p->alignType = NSVG_ALIGN_MEET; - if (strstr(attr[i + 1], "slice") != 0) - p->alignType = NSVG_ALIGN_SLICE; - } - } - } - } -} - -static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type) -{ - int i; - NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData)); - if (grad == NULL) return; - memset(grad, 0, sizeof(NSVGgradientData)); - grad->units = NSVG_OBJECT_SPACE; - grad->type = type; - if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) { - grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT); - grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); - } else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) { - grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); - } - - nsvg__xformIdentity(grad->xform); - - for (i = 0; attr[i]; i += 2) { - if (strcmp(attr[i], "id") == 0) { - strncpy(grad->id, attr[i+1], 63); - grad->id[63] = '\0'; - } else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { - if (strcmp(attr[i], "gradientUnits") == 0) { - if (strcmp(attr[i+1], "objectBoundingBox") == 0) - grad->units = NSVG_OBJECT_SPACE; - else - grad->units = NSVG_USER_SPACE; - } else if (strcmp(attr[i], "gradientTransform") == 0) { - nsvg__parseTransform(grad->xform, attr[i + 1]); - } else if (strcmp(attr[i], "cx") == 0) { - grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "cy") == 0) { - grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "r") == 0) { - grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "fx") == 0) { - grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "fy") == 0) { - grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "x1") == 0) { - grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "y1") == 0) { - grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "x2") == 0) { - grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "y2") == 0) { - grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]); - } else if (strcmp(attr[i], "spreadMethod") == 0) { - if (strcmp(attr[i+1], "pad") == 0) - grad->spread = NSVG_SPREAD_PAD; - else if (strcmp(attr[i+1], "reflect") == 0) - grad->spread = NSVG_SPREAD_REFLECT; - else if (strcmp(attr[i+1], "repeat") == 0) - grad->spread = NSVG_SPREAD_REPEAT; - } else if (strcmp(attr[i], "xlink:href") == 0) { - const char *href = attr[i+1]; - strncpy(grad->ref, href+1, 62); - grad->ref[62] = '\0'; - } - } - } - - grad->next = p->gradients; - p->gradients = grad; -} - -static void nsvg__parseGradientStop(NSVGparser* p, const char** attr) -{ - NSVGattrib* curAttr = nsvg__getAttr(p); - NSVGgradientData* grad; - NSVGgradientStop* stop; - int i, idx; - - curAttr->stopOffset = 0; - curAttr->stopColor = 0; - curAttr->stopOpacity = 1.0f; - - for (i = 0; attr[i]; i += 2) { - nsvg__parseAttr(p, attr[i], attr[i + 1]); - } - - // Add stop to the last gradient. - grad = p->gradients; - if (grad == NULL) return; - - grad->nstops++; - grad->stops = (NSVGgradientStop*)realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops); - if (grad->stops == NULL) return; - - // Insert - idx = grad->nstops-1; - for (i = 0; i < grad->nstops-1; i++) { - if (curAttr->stopOffset < grad->stops[i].offset) { - idx = i; - break; - } - } - if (idx != grad->nstops-1) { - for (i = grad->nstops-1; i > idx; i--) - grad->stops[i] = grad->stops[i-1]; - } - - stop = &grad->stops[idx]; - stop->color = curAttr->stopColor; - stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24; - stop->offset = curAttr->stopOffset; -} - -static void nsvg__startElement(void* ud, const char* el, const char** attr) -{ - NSVGparser* p = (NSVGparser*)ud; - - if (p->defsFlag) { - // Skip everything but gradients in defs - if (strcmp(el, "linearGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); - } else if (strcmp(el, "radialGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); - } else if (strcmp(el, "stop") == 0) { - nsvg__parseGradientStop(p, attr); - } - return; - } - - if (strcmp(el, "g") == 0) { - nsvg__pushAttr(p); - nsvg__parseAttribs(p, attr); - } else if (strcmp(el, "path") == 0) { - if (p->pathFlag) // Do not allow nested paths. - return; - nsvg__pushAttr(p); - nsvg__parsePath(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "rect") == 0) { - nsvg__pushAttr(p); - nsvg__parseRect(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "circle") == 0) { - nsvg__pushAttr(p); - nsvg__parseCircle(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "ellipse") == 0) { - nsvg__pushAttr(p); - nsvg__parseEllipse(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "line") == 0) { - nsvg__pushAttr(p); - nsvg__parseLine(p, attr); - nsvg__popAttr(p); - } else if (strcmp(el, "polyline") == 0) { - nsvg__pushAttr(p); - nsvg__parsePoly(p, attr, 0); - nsvg__popAttr(p); - } else if (strcmp(el, "polygon") == 0) { - nsvg__pushAttr(p); - nsvg__parsePoly(p, attr, 1); - nsvg__popAttr(p); - } else if (strcmp(el, "linearGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); - } else if (strcmp(el, "radialGradient") == 0) { - nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); - } else if (strcmp(el, "stop") == 0) { - nsvg__parseGradientStop(p, attr); - } else if (strcmp(el, "defs") == 0) { - p->defsFlag = 1; - } else if (strcmp(el, "svg") == 0) { - nsvg__parseSVG(p, attr); - } -} - -static void nsvg__endElement(void* ud, const char* el) -{ - NSVGparser* p = (NSVGparser*)ud; - - if (strcmp(el, "g") == 0) { - nsvg__popAttr(p); - } else if (strcmp(el, "path") == 0) { - p->pathFlag = 0; - } else if (strcmp(el, "defs") == 0) { - p->defsFlag = 0; - } -} - -static void nsvg__content(void* ud, const char* s) -{ - NSVG_NOTUSED(ud); - NSVG_NOTUSED(s); - // empty -} - -static void nsvg__imageBounds(NSVGparser* p, float* bounds) -{ - NSVGshape* shape; - shape = p->image->shapes; - if (shape == NULL) { - bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; - return; - } - bounds[0] = shape->bounds[0]; - bounds[1] = shape->bounds[1]; - bounds[2] = shape->bounds[2]; - bounds[3] = shape->bounds[3]; - for (shape = shape->next; shape != NULL; shape = shape->next) { - bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); - bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); - bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); - bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); - } -} - -static float nsvg__viewAlign(float content, float container, int type) -{ - if (type == NSVG_ALIGN_MIN) - return 0; - else if (type == NSVG_ALIGN_MAX) - return container - content; - // mid - return (container - content) * 0.5f; -} - -static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy) -{ - float t[6]; - nsvg__xformSetTranslation(t, tx, ty); - nsvg__xformMultiply (grad->xform, t); - - nsvg__xformSetScale(t, sx, sy); - nsvg__xformMultiply (grad->xform, t); -} - -static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) -{ - NSVGshape* shape; - NSVGpath* path; - float tx, ty, sx, sy, us, bounds[4], t[6], avgs; - int i; - float* pt; - - // Guess image size if not set completely. - nsvg__imageBounds(p, bounds); - - if (p->viewWidth == 0) { - if (p->image->width > 0) { - p->viewWidth = p->image->width; - } else { - p->viewMinx = bounds[0]; - p->viewWidth = bounds[2] - bounds[0]; - } - } - if (p->viewHeight == 0) { - if (p->image->height > 0) { - p->viewHeight = p->image->height; - } else { - p->viewMiny = bounds[1]; - p->viewHeight = bounds[3] - bounds[1]; - } - } - if (p->image->width == 0) - p->image->width = p->viewWidth; - if (p->image->height == 0) - p->image->height = p->viewHeight; - - tx = -p->viewMinx; - ty = -p->viewMiny; - sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; - sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; - // Unit scaling - us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f); - - // Fix aspect ratio - if (p->alignType == NSVG_ALIGN_MEET) { - // fit whole image into viewbox - sx = sy = nsvg__minf(sx, sy); - tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; - ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; - } else if (p->alignType == NSVG_ALIGN_SLICE) { - // fill whole viewbox with image - sx = sy = nsvg__maxf(sx, sy); - tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; - ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; - } - - // Transform - sx *= us; - sy *= us; - avgs = (sx+sy) / 2.0f; - for (shape = p->image->shapes; shape != NULL; shape = shape->next) { - shape->bounds[0] = (shape->bounds[0] + tx) * sx; - shape->bounds[1] = (shape->bounds[1] + ty) * sy; - shape->bounds[2] = (shape->bounds[2] + tx) * sx; - shape->bounds[3] = (shape->bounds[3] + ty) * sy; - for (path = shape->paths; path != NULL; path = path->next) { - path->bounds[0] = (path->bounds[0] + tx) * sx; - path->bounds[1] = (path->bounds[1] + ty) * sy; - path->bounds[2] = (path->bounds[2] + tx) * sx; - path->bounds[3] = (path->bounds[3] + ty) * sy; - for (i =0; i < path->npts; i++) { - pt = &path->pts[i*2]; - pt[0] = (pt[0] + tx) * sx; - pt[1] = (pt[1] + ty) * sy; - } - } - - if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { - nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy); - memcpy(t, shape->fill.gradient->xform, sizeof(float)*6); - nsvg__xformInverse(shape->fill.gradient->xform, t); - } - if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { - nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy); - memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6); - nsvg__xformInverse(shape->stroke.gradient->xform, t); - } - - shape->strokeWidth *= avgs; - shape->strokeDashOffset *= avgs; - for (i = 0; i < shape->strokeDashCount; i++) - shape->strokeDashArray[i] *= avgs; - } -} - -NSVGimage* nsvgParse(char* input, const char* units, float dpi) -{ - NSVGparser* p; - NSVGimage* ret = 0; - - p = nsvg__createParser(); - if (p == NULL) { - return NULL; - } - p->dpi = dpi; - - nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); - - // Scale to viewBox - nsvg__scaleToViewbox(p, units); - - ret = p->image; - p->image = NULL; - - nsvg__deleteParser(p); - - return ret; -} - -#include - -NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) -{ - FILE* fp = NULL; - size_t size; - char* data = NULL; - NSVGimage* image = NULL; - - fp = boost::nowide::fopen(filename, "rb"); - if (!fp) goto error; - fseek(fp, 0, SEEK_END); - size = ftell(fp); - fseek(fp, 0, SEEK_SET); - data = (char*)malloc(size+1); - if (data == NULL) goto error; - if (fread(data, 1, size, fp) != size) goto error; - data[size] = '\0'; // Must be null terminated. - fclose(fp); - - image = nsvgParse(data, units, dpi); - free(data); - return image; - -error: - if (fp) fclose(fp); - if (data) free(data); - if (image) nsvgDelete(image); - return NULL; -} - -NSVGpath* nsvgDuplicatePath(NSVGpath* p) -{ - NSVGpath* res = NULL; - - if (p == NULL) - return NULL; - - res = (NSVGpath*)malloc(sizeof(NSVGpath)); - if (res == NULL) goto error; - memset(res, 0, sizeof(NSVGpath)); - - res->pts = (float*)malloc(p->npts*2*sizeof(float)); - if (res->pts == NULL) goto error; - memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2); - res->npts = p->npts; - - memcpy(res->bounds, p->bounds, sizeof(p->bounds)); - - res->closed = p->closed; - - return res; - -error: - if (res != NULL) { - free(res->pts); - free(res); - } - return NULL; -} - -void nsvgDelete(NSVGimage* image) -{ - NSVGshape *snext, *shape; - if (image == NULL) return; - shape = image->shapes; - while (shape != NULL) { - snext = shape->next; - nsvg__deletePaths(shape->paths); - nsvg__deletePaint(&shape->fill); - nsvg__deletePaint(&shape->stroke); - free(shape); - shape = snext; - } - free(image); -} - -#endif diff --git a/src/nanosvg/nanosvgrast.h b/src/nanosvg/nanosvgrast.h deleted file mode 100644 index b740c316c..000000000 --- a/src/nanosvg/nanosvgrast.h +++ /dev/null @@ -1,1452 +0,0 @@ -/* - * Copyright (c) 2013-14 Mikko Mononen memon@inside.org - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - * - * The polygon rasterization is heavily based on stb_truetype rasterizer - * by Sean Barrett - http://nothings.org/ - * - */ - -#ifndef NANOSVGRAST_H -#define NANOSVGRAST_H - -#ifndef NANOSVGRAST_CPLUSPLUS -#ifdef __cplusplus -extern "C" { -#endif -#endif - -typedef struct NSVGrasterizer NSVGrasterizer; - -/* Example Usage: - // Load SVG - NSVGimage* image; - image = nsvgParseFromFile("test.svg", "px", 96); - - // Create rasterizer (can be used to render multiple images). - struct NSVGrasterizer* rast = nsvgCreateRasterizer(); - // Allocate memory for image - unsigned char* img = malloc(w*h*4); - // Rasterize - nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4); -*/ - -// Allocated rasterizer context. -NSVGrasterizer* nsvgCreateRasterizer(); - -// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha) -// r - pointer to rasterizer context -// image - pointer to image to rasterize -// tx,ty - image offset (applied after scaling) -// scale - image scale -// dst - pointer to destination image data, 4 bytes per pixel (RGBA) -// w - width of the image to render -// h - height of the image to render -// stride - number of bytes per scaleline in the destination buffer -void nsvgRasterize(NSVGrasterizer* r, - NSVGimage* image, float tx, float ty, float scale, - unsigned char* dst, int w, int h, int stride); - -// Deletes rasterizer context. -void nsvgDeleteRasterizer(NSVGrasterizer*); - - -#ifndef NANOSVGRAST_CPLUSPLUS -#ifdef __cplusplus -} -#endif -#endif - -#endif // NANOSVGRAST_H - -#ifdef NANOSVGRAST_IMPLEMENTATION - -#include - -#define NSVG__SUBSAMPLES 5 -#define NSVG__FIXSHIFT 10 -#define NSVG__FIX (1 << NSVG__FIXSHIFT) -#define NSVG__FIXMASK (NSVG__FIX-1) -#define NSVG__MEMPAGE_SIZE 1024 - -typedef struct NSVGedge { - float x0,y0, x1,y1; - int dir; - struct NSVGedge* next; -} NSVGedge; - -typedef struct NSVGpoint { - float x, y; - float dx, dy; - float len; - float dmx, dmy; - unsigned char flags; -} NSVGpoint; - -typedef struct NSVGactiveEdge { - int x,dx; - float ey; - int dir; - struct NSVGactiveEdge *next; -} NSVGactiveEdge; - -typedef struct NSVGmemPage { - unsigned char mem[NSVG__MEMPAGE_SIZE]; - int size; - struct NSVGmemPage* next; -} NSVGmemPage; - -typedef struct NSVGcachedPaint { - char type; - char spread; - float xform[6]; - unsigned int colors[256]; -} NSVGcachedPaint; - -struct NSVGrasterizer -{ - float px, py; - - float tessTol; - float distTol; - - NSVGedge* edges; - int nedges; - int cedges; - - NSVGpoint* points; - int npoints; - int cpoints; - - NSVGpoint* points2; - int npoints2; - int cpoints2; - - NSVGactiveEdge* freelist; - NSVGmemPage* pages; - NSVGmemPage* curpage; - - unsigned char* scanline; - int cscanline; - - unsigned char* bitmap; - int width, height, stride; -}; - -NSVGrasterizer* nsvgCreateRasterizer() -{ - NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer)); - if (r == NULL) goto error; - memset(r, 0, sizeof(NSVGrasterizer)); - - r->tessTol = 0.25f; - r->distTol = 0.01f; - - return r; - -error: - nsvgDeleteRasterizer(r); - return NULL; -} - -void nsvgDeleteRasterizer(NSVGrasterizer* r) -{ - NSVGmemPage* p; - - if (r == NULL) return; - - p = r->pages; - while (p != NULL) { - NSVGmemPage* next = p->next; - free(p); - p = next; - } - - if (r->edges) free(r->edges); - if (r->points) free(r->points); - if (r->points2) free(r->points2); - if (r->scanline) free(r->scanline); - - free(r); -} - -static NSVGmemPage* nsvg__nextPage(NSVGrasterizer* r, NSVGmemPage* cur) -{ - NSVGmemPage *newp; - - // If using existing chain, return the next page in chain - if (cur != NULL && cur->next != NULL) { - return cur->next; - } - - // Alloc new page - newp = (NSVGmemPage*)malloc(sizeof(NSVGmemPage)); - if (newp == NULL) return NULL; - memset(newp, 0, sizeof(NSVGmemPage)); - - // Add to linked list - if (cur != NULL) - cur->next = newp; - else - r->pages = newp; - - return newp; -} - -static void nsvg__resetPool(NSVGrasterizer* r) -{ - NSVGmemPage* p = r->pages; - while (p != NULL) { - p->size = 0; - p = p->next; - } - r->curpage = r->pages; -} - -static unsigned char* nsvg__alloc(NSVGrasterizer* r, int size) -{ - unsigned char* buf; - if (size > NSVG__MEMPAGE_SIZE) return NULL; - if (r->curpage == NULL || r->curpage->size+size > NSVG__MEMPAGE_SIZE) { - r->curpage = nsvg__nextPage(r, r->curpage); - } - buf = &r->curpage->mem[r->curpage->size]; - r->curpage->size += size; - return buf; -} - -static int nsvg__ptEquals(float x1, float y1, float x2, float y2, float tol) -{ - float dx = x2 - x1; - float dy = y2 - y1; - return dx*dx + dy*dy < tol*tol; -} - -static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags) -{ - NSVGpoint* pt; - - if (r->npoints > 0) { - pt = &r->points[r->npoints-1]; - if (nsvg__ptEquals(pt->x,pt->y, x,y, r->distTol)) { - pt->flags = (unsigned char)(pt->flags | flags); - return; - } - } - - if (r->npoints+1 > r->cpoints) { - r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; - r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); - if (r->points == NULL) return; - } - - pt = &r->points[r->npoints]; - pt->x = x; - pt->y = y; - pt->flags = (unsigned char)flags; - r->npoints++; -} - -static void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt) -{ - if (r->npoints+1 > r->cpoints) { - r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; - r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); - if (r->points == NULL) return; - } - r->points[r->npoints] = pt; - r->npoints++; -} - -static void nsvg__duplicatePoints(NSVGrasterizer* r) -{ - if (r->npoints > r->cpoints2) { - r->cpoints2 = r->npoints; - r->points2 = (NSVGpoint*)realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2); - if (r->points2 == NULL) return; - } - - memcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints); - r->npoints2 = r->npoints; -} - -static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1) -{ - NSVGedge* e; - - // Skip horizontal edges - if (y0 == y1) - return; - - if (r->nedges+1 > r->cedges) { - r->cedges = r->cedges > 0 ? r->cedges * 2 : 64; - r->edges = (NSVGedge*)realloc(r->edges, sizeof(NSVGedge) * r->cedges); - if (r->edges == NULL) return; - } - - e = &r->edges[r->nedges]; - r->nedges++; - - if (y0 < y1) { - e->x0 = x0; - e->y0 = y0; - e->x1 = x1; - e->y1 = y1; - e->dir = 1; - } else { - e->x0 = x1; - e->y0 = y1; - e->x1 = x0; - e->y1 = y0; - e->dir = -1; - } -} - -static float nsvg__normalize(float *x, float* y) -{ - float d = sqrtf((*x)*(*x) + (*y)*(*y)); - if (d > 1e-6f) { - float id = 1.0f / d; - *x *= id; - *y *= id; - } - return d; -} - -static float nsvg__absf(float x) { return x < 0 ? -x : x; } - -static void nsvg__flattenCubicBez(NSVGrasterizer* r, - float x1, float y1, float x2, float y2, - float x3, float y3, float x4, float y4, - int level, int type) -{ - float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; - float dx,dy,d2,d3; - - if (level > 10) return; - - x12 = (x1+x2)*0.5f; - y12 = (y1+y2)*0.5f; - x23 = (x2+x3)*0.5f; - y23 = (y2+y3)*0.5f; - x34 = (x3+x4)*0.5f; - y34 = (y3+y4)*0.5f; - x123 = (x12+x23)*0.5f; - y123 = (y12+y23)*0.5f; - - dx = x4 - x1; - dy = y4 - y1; - d2 = nsvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); - d3 = nsvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); - - if ((d2 + d3)*(d2 + d3) < r->tessTol * (dx*dx + dy*dy)) { - nsvg__addPathPoint(r, x4, y4, type); - return; - } - - x234 = (x23+x34)*0.5f; - y234 = (y23+y34)*0.5f; - x1234 = (x123+x234)*0.5f; - y1234 = (y123+y234)*0.5f; - - nsvg__flattenCubicBez(r, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); - nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); -} - -static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) -{ - int i, j; - NSVGpath* path; - - for (path = shape->paths; path != NULL; path = path->next) { - r->npoints = 0; - // Flatten path - nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); - for (i = 0; i < path->npts-1; i += 3) { - float* p = &path->pts[i*2]; - nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); - } - // Close path - nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); - // Build edges - for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) - nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); - } -} - -enum NSVGpointFlags -{ - NSVG_PT_CORNER = 0x01, - NSVG_PT_BEVEL = 0x02, - NSVG_PT_LEFT = 0x04 -}; - -static void nsvg__initClosed(NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) -{ - float w = lineWidth * 0.5f; - float dx = p1->x - p0->x; - float dy = p1->y - p0->y; - float len = nsvg__normalize(&dx, &dy); - float px = p0->x + dx*len*0.5f, py = p0->y + dy*len*0.5f; - float dlx = dy, dly = -dx; - float lx = px - dlx*w, ly = py - dly*w; - float rx = px + dlx*w, ry = py + dly*w; - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static void nsvg__buttCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) -{ - float w = lineWidth * 0.5f; - float px = p->x, py = p->y; - float dlx = dy, dly = -dx; - float lx = px - dlx*w, ly = py - dly*w; - float rx = px + dlx*w, ry = py + dly*w; - - nsvg__addEdge(r, lx, ly, rx, ry); - - if (connect) { - nsvg__addEdge(r, left->x, left->y, lx, ly); - nsvg__addEdge(r, rx, ry, right->x, right->y); - } - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static void nsvg__squareCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) -{ - float w = lineWidth * 0.5f; - float px = p->x - dx*w, py = p->y - dy*w; - float dlx = dy, dly = -dx; - float lx = px - dlx*w, ly = py - dly*w; - float rx = px + dlx*w, ry = py + dly*w; - - nsvg__addEdge(r, lx, ly, rx, ry); - - if (connect) { - nsvg__addEdge(r, left->x, left->y, lx, ly); - nsvg__addEdge(r, rx, ry, right->x, right->y); - } - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -#ifndef NSVG_PI -#define NSVG_PI (3.14159265358979323846264338327f) -#endif - -static void nsvg__roundCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int ncap, int connect) -{ - int i; - float w = lineWidth * 0.5f; - float px = p->x, py = p->y; - float dlx = dy, dly = -dx; - float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0; - - for (i = 0; i < ncap; i++) { - float a = (float)i/(float)(ncap-1)*NSVG_PI; - float ax = cosf(a) * w, ay = sinf(a) * w; - float x = px - dlx*ax - dx*ay; - float y = py - dly*ax - dy*ay; - - if (i > 0) - nsvg__addEdge(r, prevx, prevy, x, y); - - prevx = x; - prevy = y; - - if (i == 0) { - lx = x; ly = y; - } else if (i == ncap-1) { - rx = x; ry = y; - } - } - - if (connect) { - nsvg__addEdge(r, left->x, left->y, lx, ly); - nsvg__addEdge(r, rx, ry, right->x, right->y); - } - - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static void nsvg__bevelJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) -{ - float w = lineWidth * 0.5f; - float dlx0 = p0->dy, dly0 = -p0->dx; - float dlx1 = p1->dy, dly1 = -p1->dx; - float lx0 = p1->x - (dlx0 * w), ly0 = p1->y - (dly0 * w); - float rx0 = p1->x + (dlx0 * w), ry0 = p1->y + (dly0 * w); - float lx1 = p1->x - (dlx1 * w), ly1 = p1->y - (dly1 * w); - float rx1 = p1->x + (dlx1 * w), ry1 = p1->y + (dly1 * w); - - nsvg__addEdge(r, lx0, ly0, left->x, left->y); - nsvg__addEdge(r, lx1, ly1, lx0, ly0); - - nsvg__addEdge(r, right->x, right->y, rx0, ry0); - nsvg__addEdge(r, rx0, ry0, rx1, ry1); - - left->x = lx1; left->y = ly1; - right->x = rx1; right->y = ry1; -} - -static void nsvg__miterJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) -{ - float w = lineWidth * 0.5f; - float dlx0 = p0->dy, dly0 = -p0->dx; - float dlx1 = p1->dy, dly1 = -p1->dx; - float lx0, rx0, lx1, rx1; - float ly0, ry0, ly1, ry1; - - if (p1->flags & NSVG_PT_LEFT) { - lx0 = lx1 = p1->x - p1->dmx * w; - ly0 = ly1 = p1->y - p1->dmy * w; - nsvg__addEdge(r, lx1, ly1, left->x, left->y); - - rx0 = p1->x + (dlx0 * w); - ry0 = p1->y + (dly0 * w); - rx1 = p1->x + (dlx1 * w); - ry1 = p1->y + (dly1 * w); - nsvg__addEdge(r, right->x, right->y, rx0, ry0); - nsvg__addEdge(r, rx0, ry0, rx1, ry1); - } else { - lx0 = p1->x - (dlx0 * w); - ly0 = p1->y - (dly0 * w); - lx1 = p1->x - (dlx1 * w); - ly1 = p1->y - (dly1 * w); - nsvg__addEdge(r, lx0, ly0, left->x, left->y); - nsvg__addEdge(r, lx1, ly1, lx0, ly0); - - rx0 = rx1 = p1->x + p1->dmx * w; - ry0 = ry1 = p1->y + p1->dmy * w; - nsvg__addEdge(r, right->x, right->y, rx1, ry1); - } - - left->x = lx1; left->y = ly1; - right->x = rx1; right->y = ry1; -} - -static void nsvg__roundJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth, int ncap) -{ - int i, n; - float w = lineWidth * 0.5f; - float dlx0 = p0->dy, dly0 = -p0->dx; - float dlx1 = p1->dy, dly1 = -p1->dx; - float a0 = atan2f(dly0, dlx0); - float a1 = atan2f(dly1, dlx1); - float da = a1 - a0; - float lx, ly, rx, ry; - - if (da < NSVG_PI) da += NSVG_PI*2; - if (da > NSVG_PI) da -= NSVG_PI*2; - - n = (int)ceilf((nsvg__absf(da) / NSVG_PI) * (float)ncap); - if (n < 2) n = 2; - if (n > ncap) n = ncap; - - lx = left->x; - ly = left->y; - rx = right->x; - ry = right->y; - - for (i = 0; i < n; i++) { - float u = (float)i/(float)(n-1); - float a = a0 + u*da; - float ax = cosf(a) * w, ay = sinf(a) * w; - float lx1 = p1->x - ax, ly1 = p1->y - ay; - float rx1 = p1->x + ax, ry1 = p1->y + ay; - - nsvg__addEdge(r, lx1, ly1, lx, ly); - nsvg__addEdge(r, rx, ry, rx1, ry1); - - lx = lx1; ly = ly1; - rx = rx1; ry = ry1; - } - - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static void nsvg__straightJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p1, float lineWidth) -{ - float w = lineWidth * 0.5f; - float lx = p1->x - (p1->dmx * w), ly = p1->y - (p1->dmy * w); - float rx = p1->x + (p1->dmx * w), ry = p1->y + (p1->dmy * w); - - nsvg__addEdge(r, lx, ly, left->x, left->y); - nsvg__addEdge(r, right->x, right->y, rx, ry); - - left->x = lx; left->y = ly; - right->x = rx; right->y = ry; -} - -static int nsvg__curveDivs(float r, float arc, float tol) -{ - float da = acosf(r / (r + tol)) * 2.0f; - int divs = (int)ceilf(arc / da); - if (divs < 2) divs = 2; - return divs; -} - -static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth) -{ - int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol); // Calculate divisions per half circle. - NSVGpoint left = {0,0,0,0,0,0,0,0}, right = {0,0,0,0,0,0,0,0}, firstLeft = {0,0,0,0,0,0,0,0}, firstRight = {0,0,0,0,0,0,0,0}; - NSVGpoint* p0, *p1; - int j, s, e; - - // Build stroke edges - if (closed) { - // Looping - p0 = &points[npoints-1]; - p1 = &points[0]; - s = 0; - e = npoints; - } else { - // Add cap - p0 = &points[0]; - p1 = &points[1]; - s = 1; - e = npoints-1; - } - - if (closed) { - nsvg__initClosed(&left, &right, p0, p1, lineWidth); - firstLeft = left; - firstRight = right; - } else { - // Add cap - float dx = p1->x - p0->x; - float dy = p1->y - p0->y; - nsvg__normalize(&dx, &dy); - if (lineCap == NSVG_CAP_BUTT) - nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0); - else if (lineCap == NSVG_CAP_SQUARE) - nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0); - else if (lineCap == NSVG_CAP_ROUND) - nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0); - } - - for (j = s; j < e; ++j) { - if (p1->flags & NSVG_PT_CORNER) { - if (lineJoin == NSVG_JOIN_ROUND) - nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap); - else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL)) - nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth); - else - nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth); - } else { - nsvg__straightJoin(r, &left, &right, p1, lineWidth); - } - p0 = p1++; - } - - if (closed) { - // Loop it - nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y); - nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y); - } else { - // Add cap - float dx = p1->x - p0->x; - float dy = p1->y - p0->y; - nsvg__normalize(&dx, &dy); - if (lineCap == NSVG_CAP_BUTT) - nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); - else if (lineCap == NSVG_CAP_SQUARE) - nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); - else if (lineCap == NSVG_CAP_ROUND) - nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1); - } -} - -static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin) -{ - int i, j; - NSVGpoint* p0, *p1; - - p0 = &r->points[r->npoints-1]; - p1 = &r->points[0]; - for (i = 0; i < r->npoints; i++) { - // Calculate segment direction and length - p0->dx = p1->x - p0->x; - p0->dy = p1->y - p0->y; - p0->len = nsvg__normalize(&p0->dx, &p0->dy); - // Advance - p0 = p1++; - } - - // calculate joins - p0 = &r->points[r->npoints-1]; - p1 = &r->points[0]; - for (j = 0; j < r->npoints; j++) { - float dlx0, dly0, dlx1, dly1, dmr2, cross; - dlx0 = p0->dy; - dly0 = -p0->dx; - dlx1 = p1->dy; - dly1 = -p1->dx; - // Calculate extrusions - p1->dmx = (dlx0 + dlx1) * 0.5f; - p1->dmy = (dly0 + dly1) * 0.5f; - dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; - if (dmr2 > 0.000001f) { - float s2 = 1.0f / dmr2; - if (s2 > 600.0f) { - s2 = 600.0f; - } - p1->dmx *= s2; - p1->dmy *= s2; - } - - // Clear flags, but keep the corner. - p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0; - - // Keep track of left turns. - cross = p1->dx * p0->dy - p0->dx * p1->dy; - if (cross > 0.0f) - p1->flags |= NSVG_PT_LEFT; - - // Check to see if the corner needs to be beveled. - if (p1->flags & NSVG_PT_CORNER) { - if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) { - p1->flags |= NSVG_PT_BEVEL; - } - } - - p0 = p1++; - } -} - -static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) -{ - int i, j, closed; - NSVGpath* path; - NSVGpoint* p0, *p1; - float miterLimit = shape->miterLimit; - int lineJoin = shape->strokeLineJoin; - int lineCap = shape->strokeLineCap; - float lineWidth = shape->strokeWidth * scale; - - for (path = shape->paths; path != NULL; path = path->next) { - // Flatten path - r->npoints = 0; - nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); - for (i = 0; i < path->npts-1; i += 3) { - float* p = &path->pts[i*2]; - nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER); - } - if (r->npoints < 2) - continue; - - closed = path->closed; - - // If the first and last points are the same, remove the last, mark as closed path. - p0 = &r->points[r->npoints-1]; - p1 = &r->points[0]; - if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) { - r->npoints--; - p0 = &r->points[r->npoints-1]; - closed = 1; - } - - if (shape->strokeDashCount > 0) { - int idash = 0, dashState = 1; - float totalDist = 0, dashLen, allDashLen, dashOffset; - NSVGpoint cur; - - if (closed) - nsvg__appendPathPoint(r, r->points[0]); - - // Duplicate points -> points2. - nsvg__duplicatePoints(r); - - r->npoints = 0; - cur = r->points2[0]; - nsvg__appendPathPoint(r, cur); - - // Figure out dash offset. - allDashLen = 0; - for (j = 0; j < shape->strokeDashCount; j++) - allDashLen += shape->strokeDashArray[j]; - if (shape->strokeDashCount & 1) - allDashLen *= 2.0f; - // Find location inside pattern - dashOffset = fmodf(shape->strokeDashOffset, allDashLen); - if (dashOffset < 0.0f) - dashOffset += allDashLen; - - while (dashOffset > shape->strokeDashArray[idash]) { - dashOffset -= shape->strokeDashArray[idash]; - idash = (idash + 1) % shape->strokeDashCount; - } - dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; - - for (j = 1; j < r->npoints2; ) { - float dx = r->points2[j].x - cur.x; - float dy = r->points2[j].y - cur.y; - float dist = sqrtf(dx*dx + dy*dy); - - if ((totalDist + dist) > dashLen) { - // Calculate intermediate point - float d = (dashLen - totalDist) / dist; - float x = cur.x + dx * d; - float y = cur.y + dy * d; - nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER); - - // Stroke - if (r->npoints > 1 && dashState) { - nsvg__prepareStroke(r, miterLimit, lineJoin); - nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); - } - // Advance dash pattern - dashState = !dashState; - idash = (idash+1) % shape->strokeDashCount; - dashLen = shape->strokeDashArray[idash] * scale; - // Restart - cur.x = x; - cur.y = y; - cur.flags = NSVG_PT_CORNER; - totalDist = 0.0f; - r->npoints = 0; - nsvg__appendPathPoint(r, cur); - } else { - totalDist += dist; - cur = r->points2[j]; - nsvg__appendPathPoint(r, cur); - j++; - } - } - // Stroke any leftover path - if (r->npoints > 1 && dashState) - nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); - } else { - nsvg__prepareStroke(r, miterLimit, lineJoin); - nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth); - } - } -} - -static int nsvg__cmpEdge(const void *p, const void *q) -{ - const NSVGedge* a = (const NSVGedge*)p; - const NSVGedge* b = (const NSVGedge*)q; - - if (a->y0 < b->y0) return -1; - if (a->y0 > b->y0) return 1; - return 0; -} - - -static NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float startPoint) -{ - NSVGactiveEdge* z; - - if (r->freelist != NULL) { - // Restore from freelist. - z = r->freelist; - r->freelist = z->next; - } else { - // Alloc new edge. - z = (NSVGactiveEdge*)nsvg__alloc(r, sizeof(NSVGactiveEdge)); - if (z == NULL) return NULL; - } - - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); -// STBTT_assert(e->y0 <= start_point); - // round dx down to avoid going too far - if (dxdy < 0) - z->dx = (int)(-floorf(NSVG__FIX * -dxdy)); - else - z->dx = (int)floorf(NSVG__FIX * dxdy); - z->x = (int)floorf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0))); -// z->x -= off_x * FIX; - z->ey = e->y1; - z->next = 0; - z->dir = e->dir; - - return z; -} - -static void nsvg__freeActive(NSVGrasterizer* r, NSVGactiveEdge* z) -{ - z->next = r->freelist; - r->freelist = z; -} - -static void nsvg__fillScanline(unsigned char* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax) -{ - int i = x0 >> NSVG__FIXSHIFT; - int j = x1 >> NSVG__FIXSHIFT; - if (i < *xmin) *xmin = i; - if (j > *xmax) *xmax = j; - if (i < len && j >= 0) { - if (i == j) { - // x0,x1 are the same pixel, so compute combined coverage - scanline[i] = (unsigned char)(scanline[i] + ((x1 - x0) * maxWeight >> NSVG__FIXSHIFT)); - } else { - if (i >= 0) // add antialiasing for x0 - scanline[i] = (unsigned char)(scanline[i] + (((NSVG__FIX - (x0 & NSVG__FIXMASK)) * maxWeight) >> NSVG__FIXSHIFT)); - else - i = -1; // clip - - if (j < len) // add antialiasing for x1 - scanline[j] = (unsigned char)(scanline[j] + (((x1 & NSVG__FIXMASK) * maxWeight) >> NSVG__FIXSHIFT)); - else - j = len; // clip - - for (++i; i < j; ++i) // fill pixels between x0 and x1 - scanline[i] = (unsigned char)(scanline[i] + maxWeight); - } - } -} - -// note: this routine clips fills that extend off the edges... ideally this -// wouldn't happen, but it could happen if the truetype glyph bounding boxes -// are wrong, or if the user supplies a too-small bitmap -static void nsvg__fillActiveEdges(unsigned char* scanline, int len, NSVGactiveEdge* e, int maxWeight, int* xmin, int* xmax, char fillRule) -{ - // non-zero winding fill - int x0 = 0, w = 0; - - if (fillRule == NSVG_FILLRULE_NONZERO) { - // Non-zero - while (e != NULL) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; w += e->dir; - } else { - int x1 = e->x; w += e->dir; - // if we went to zero, we need to draw - if (w == 0) - nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); - } - e = e->next; - } - } else if (fillRule == NSVG_FILLRULE_EVENODD) { - // Even-odd - while (e != NULL) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; w = 1; - } else { - int x1 = e->x; w = 0; - nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); - } - e = e->next; - } - } -} - -static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } - -static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - return (r) | (g << 8) | (b << 16) | (a << 24); -} - -static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u) -{ - int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); - int r = (((c0) & 0xff)*(256-iu) + (((c1) & 0xff)*iu)) >> 8; - int g = (((c0>>8) & 0xff)*(256-iu) + (((c1>>8) & 0xff)*iu)) >> 8; - int b = (((c0>>16) & 0xff)*(256-iu) + (((c1>>16) & 0xff)*iu)) >> 8; - int a = (((c0>>24) & 0xff)*(256-iu) + (((c1>>24) & 0xff)*iu)) >> 8; - return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); -} - -static unsigned int nsvg__applyOpacity(unsigned int c, float u) -{ - int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); - int r = (c) & 0xff; - int g = (c>>8) & 0xff; - int b = (c>>16) & 0xff; - int a = (((c>>24) & 0xff)*iu) >> 8; - return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); -} - -static inline int nsvg__div255(int x) -{ - return ((x+1) * 257) >> 16; -} - -static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y, - float tx, float ty, float scale, NSVGcachedPaint* cache) -{ - - if (cache->type == NSVG_PAINT_COLOR) { - int i, cr, cg, cb, ca; - cr = cache->colors[0] & 0xff; - cg = (cache->colors[0] >> 8) & 0xff; - cb = (cache->colors[0] >> 16) & 0xff; - ca = (cache->colors[0] >> 24) & 0xff; - - for (i = 0; i < count; i++) { - int r,g,b; - int a = nsvg__div255((int)cover[0] * ca); - int ia = 255 - a; - // Premultiply - r = nsvg__div255(cr * a); - g = nsvg__div255(cg * a); - b = nsvg__div255(cb * a); - - // Blend over - r += nsvg__div255(ia * (int)dst[0]); - g += nsvg__div255(ia * (int)dst[1]); - b += nsvg__div255(ia * (int)dst[2]); - a += nsvg__div255(ia * (int)dst[3]); - - dst[0] = (unsigned char)r; - dst[1] = (unsigned char)g; - dst[2] = (unsigned char)b; - dst[3] = (unsigned char)a; - - cover++; - dst += 4; - } - } else if (cache->type == NSVG_PAINT_LINEAR_GRADIENT) { - // TODO: spread modes. - // TODO: plenty of opportunities to optimize. - float fx, fy, dx, gy; - float* t = cache->xform; - int i, cr, cg, cb, ca; - unsigned int c; - - fx = ((float)x - tx) / scale; - fy = ((float)y - ty) / scale; - dx = 1.0f / scale; - - for (i = 0; i < count; i++) { - int r,g,b,a,ia; - gy = fx*t[1] + fy*t[3] + t[5]; - c = cache->colors[(int)nsvg__clampf(gy*255.0f, 0, 255.0f)]; - cr = (c) & 0xff; - cg = (c >> 8) & 0xff; - cb = (c >> 16) & 0xff; - ca = (c >> 24) & 0xff; - - a = nsvg__div255((int)cover[0] * ca); - ia = 255 - a; - - // Premultiply - r = nsvg__div255(cr * a); - g = nsvg__div255(cg * a); - b = nsvg__div255(cb * a); - - // Blend over - r += nsvg__div255(ia * (int)dst[0]); - g += nsvg__div255(ia * (int)dst[1]); - b += nsvg__div255(ia * (int)dst[2]); - a += nsvg__div255(ia * (int)dst[3]); - - dst[0] = (unsigned char)r; - dst[1] = (unsigned char)g; - dst[2] = (unsigned char)b; - dst[3] = (unsigned char)a; - - cover++; - dst += 4; - fx += dx; - } - } else if (cache->type == NSVG_PAINT_RADIAL_GRADIENT) { - // TODO: spread modes. - // TODO: plenty of opportunities to optimize. - // TODO: focus (fx,fy) - float fx, fy, dx, gx, gy, gd; - float* t = cache->xform; - int i, cr, cg, cb, ca; - unsigned int c; - - fx = ((float)x - tx) / scale; - fy = ((float)y - ty) / scale; - dx = 1.0f / scale; - - for (i = 0; i < count; i++) { - int r,g,b,a,ia; - gx = fx*t[0] + fy*t[2] + t[4]; - gy = fx*t[1] + fy*t[3] + t[5]; - gd = sqrtf(gx*gx + gy*gy); - c = cache->colors[(int)nsvg__clampf(gd*255.0f, 0, 255.0f)]; - cr = (c) & 0xff; - cg = (c >> 8) & 0xff; - cb = (c >> 16) & 0xff; - ca = (c >> 24) & 0xff; - - a = nsvg__div255((int)cover[0] * ca); - ia = 255 - a; - - // Premultiply - r = nsvg__div255(cr * a); - g = nsvg__div255(cg * a); - b = nsvg__div255(cb * a); - - // Blend over - r += nsvg__div255(ia * (int)dst[0]); - g += nsvg__div255(ia * (int)dst[1]); - b += nsvg__div255(ia * (int)dst[2]); - a += nsvg__div255(ia * (int)dst[3]); - - dst[0] = (unsigned char)r; - dst[1] = (unsigned char)g; - dst[2] = (unsigned char)b; - dst[3] = (unsigned char)a; - - cover++; - dst += 4; - fx += dx; - } - } -} - -static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule) -{ - NSVGactiveEdge *active = NULL; - int y, s; - int e = 0; - int maxWeight = (255 / NSVG__SUBSAMPLES); // weight per vertical scanline - int xmin, xmax; - - for (y = 0; y < r->height; y++) { - memset(r->scanline, 0, r->width); - xmin = r->width; - xmax = 0; - for (s = 0; s < NSVG__SUBSAMPLES; ++s) { - // find center of pixel for this scanline - float scany = (float)(y*NSVG__SUBSAMPLES + s) + 0.5f; - NSVGactiveEdge **step = &active; - - // update all active edges; - // remove all active edges that terminate before the center of this scanline - while (*step) { - NSVGactiveEdge *z = *step; - if (z->ey <= scany) { - *step = z->next; // delete from list -// NSVG__assert(z->valid); - nsvg__freeActive(r, z); - } else { - z->x += z->dx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - } - - // resort the list if needed - for (;;) { - int changed = 0; - step = &active; - while (*step && (*step)->next) { - if ((*step)->x > (*step)->next->x) { - NSVGactiveEdge* t = *step; - NSVGactiveEdge* q = t->next; - t->next = q->next; - q->next = t; - *step = q; - changed = 1; - } - step = &(*step)->next; - } - if (!changed) break; - } - - // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline - while (e < r->nedges && r->edges[e].y0 <= scany) { - if (r->edges[e].y1 > scany) { - NSVGactiveEdge* z = nsvg__addActive(r, &r->edges[e], scany); - if (z == NULL) break; - // find insertion point - if (active == NULL) { - active = z; - } else if (z->x < active->x) { - // insert at front - z->next = active; - active = z; - } else { - // find thing to insert AFTER - NSVGactiveEdge* p = active; - while (p->next && p->next->x < z->x) - p = p->next; - // at this point, p->next->x is NOT < z->x - z->next = p->next; - p->next = z; - } - } - e++; - } - - // now process all active edges in non-zero fashion - if (active != NULL) - nsvg__fillActiveEdges(r->scanline, r->width, active, maxWeight, &xmin, &xmax, fillRule); - } - // Blit - if (xmin < 0) xmin = 0; - if (xmax > r->width-1) xmax = r->width-1; - if (xmin <= xmax) { - nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache); - } - } - -} - -static void nsvg__unpremultiplyAlpha(unsigned char* image, int w, int h, int stride) -{ - int x,y; - - // Unpremultiply - for (y = 0; y < h; y++) { - unsigned char *row = &image[y*stride]; - for (x = 0; x < w; x++) { - int r = row[0], g = row[1], b = row[2], a = row[3]; - if (a != 0) { - row[0] = (unsigned char)(r*255/a); - row[1] = (unsigned char)(g*255/a); - row[2] = (unsigned char)(b*255/a); - } - row += 4; - } - } - - // Defringe - for (y = 0; y < h; y++) { - unsigned char *row = &image[y*stride]; - for (x = 0; x < w; x++) { - int r = 0, g = 0, b = 0, a = row[3], n = 0; - if (a == 0) { - if (x-1 > 0 && row[-1] != 0) { - r += row[-4]; - g += row[-3]; - b += row[-2]; - n++; - } - if (x+1 < w && row[7] != 0) { - r += row[4]; - g += row[5]; - b += row[6]; - n++; - } - if (y-1 > 0 && row[-stride+3] != 0) { - r += row[-stride]; - g += row[-stride+1]; - b += row[-stride+2]; - n++; - } - if (y+1 < h && row[stride+3] != 0) { - r += row[stride]; - g += row[stride+1]; - b += row[stride+2]; - n++; - } - if (n > 0) { - row[0] = (unsigned char)(r/n); - row[1] = (unsigned char)(g/n); - row[2] = (unsigned char)(b/n); - } - } - row += 4; - } - } -} - - -static void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opacity) -{ - int i, j; - NSVGgradient* grad; - - cache->type = paint->type; - - if (paint->type == NSVG_PAINT_COLOR) { - cache->colors[0] = nsvg__applyOpacity(paint->color, opacity); - return; - } - - grad = paint->gradient; - - cache->spread = grad->spread; - memcpy(cache->xform, grad->xform, sizeof(float)*6); - - if (grad->nstops == 0) { - for (i = 0; i < 256; i++) - cache->colors[i] = 0; - } if (grad->nstops == 1) { - for (i = 0; i < 256; i++) - cache->colors[i] = nsvg__applyOpacity(grad->stops[i].color, opacity); - } else { - unsigned int ca, cb = 0; - float ua, ub, du, u; - int ia, ib, count; - - ca = nsvg__applyOpacity(grad->stops[0].color, opacity); - ua = nsvg__clampf(grad->stops[0].offset, 0, 1); - ub = nsvg__clampf(grad->stops[grad->nstops-1].offset, ua, 1); - ia = (int)(ua * 255.0f); - ib = (int)(ub * 255.0f); - for (i = 0; i < ia; i++) { - cache->colors[i] = ca; - } - - for (i = 0; i < grad->nstops-1; i++) { - ca = nsvg__applyOpacity(grad->stops[i].color, opacity); - cb = nsvg__applyOpacity(grad->stops[i+1].color, opacity); - ua = nsvg__clampf(grad->stops[i].offset, 0, 1); - ub = nsvg__clampf(grad->stops[i+1].offset, 0, 1); - ia = (int)(ua * 255.0f); - ib = (int)(ub * 255.0f); - count = ib - ia; - if (count <= 0) continue; - u = 0; - du = 1.0f / (float)count; - for (j = 0; j < count; j++) { - cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u); - u += du; - } - } - - for (i = ib; i < 256; i++) - cache->colors[i] = cb; - } - -} - -/* -static void dumpEdges(NSVGrasterizer* r, const char* name) -{ - float xmin = 0, xmax = 0, ymin = 0, ymax = 0; - NSVGedge *e = NULL; - int i; - if (r->nedges == 0) return; - FILE* fp = fopen(name, "w"); - if (fp == NULL) return; - - xmin = xmax = r->edges[0].x0; - ymin = ymax = r->edges[0].y0; - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - xmin = nsvg__minf(xmin, e->x0); - xmin = nsvg__minf(xmin, e->x1); - xmax = nsvg__maxf(xmax, e->x0); - xmax = nsvg__maxf(xmax, e->x1); - ymin = nsvg__minf(ymin, e->y0); - ymin = nsvg__minf(ymin, e->y1); - ymax = nsvg__maxf(ymax, e->y0); - ymax = nsvg__maxf(ymax, e->y1); - } - - fprintf(fp, "", xmin, ymin, (xmax - xmin), (ymax - ymin)); - - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - fprintf(fp ,"", e->x0,e->y0, e->x1,e->y1); - } - - for (i = 0; i < r->npoints; i++) { - if (i+1 < r->npoints) - fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y); - fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? "#f00" : "#0f0"); - } - - fprintf(fp, ""); - fclose(fp); -} -*/ - -void nsvgRasterize(NSVGrasterizer* r, - NSVGimage* image, float tx, float ty, float scale, - unsigned char* dst, int w, int h, int stride) -{ - NSVGshape *shape = NULL; - NSVGedge *e = NULL; - NSVGcachedPaint cache; - int i; - - r->bitmap = dst; - r->width = w; - r->height = h; - r->stride = stride; - - if (w > r->cscanline) { - r->cscanline = w; - r->scanline = (unsigned char*)realloc(r->scanline, w); - if (r->scanline == NULL) return; - } - - for (i = 0; i < h; i++) - memset(&dst[i*stride], 0, w*4); - - for (shape = image->shapes; shape != NULL; shape = shape->next) { - if (!(shape->flags & NSVG_FLAGS_VISIBLE)) - continue; - - if (shape->fill.type != NSVG_PAINT_NONE) { - nsvg__resetPool(r); - r->freelist = NULL; - r->nedges = 0; - - nsvg__flattenShape(r, shape, scale); - - // Scale and translate edges - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - e->x0 = tx + e->x0; - e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; - e->x1 = tx + e->x1; - e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; - } - - // Rasterize edges - qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); - - // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule - nsvg__initPaint(&cache, &shape->fill, shape->opacity); - - nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); - } - if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { - nsvg__resetPool(r); - r->freelist = NULL; - r->nedges = 0; - - nsvg__flattenShapeStroke(r, shape, scale); - -// dumpEdges(r, "edge.svg"); - - // Scale and translate edges - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - e->x0 = tx + e->x0; - e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; - e->x1 = tx + e->x1; - e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; - } - - // Rasterize edges - qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); - - // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule - nsvg__initPaint(&cache, &shape->stroke, shape->opacity); - - nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); - } - } - - nsvg__unpremultiplyAlpha(dst, w, h, stride); - - r->bitmap = NULL; - r->width = 0; - r->height = 0; - r->stride = 0; -} - -#endif diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 29b8b7e73..430da0c34 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -255,6 +255,8 @@ set(SLIC3R_GUI_SOURCES Utils/WinRegistry.hpp ) +find_package(NanoSVG REQUIRED) + if (APPLE) list(APPEND SLIC3R_GUI_SOURCES Utils/RetinaHelperImpl.mm @@ -279,7 +281,7 @@ endforeach() encoding_check(libslic3r_gui) -target_link_libraries(libslic3r_gui libslic3r avrdude libcereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES}) +target_link_libraries(libslic3r_gui libslic3r avrdude libcereal imgui GLEW::GLEW OpenGL::GL hidapi libcurl ${wxWidgets_LIBRARIES} NanoSVG::nanosvg NanoSVG::nanosvgrast) if (MSVC) target_link_libraries(libslic3r_gui Setupapi.lib) diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index b585798d3..844f6dea9 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -15,10 +15,8 @@ #include #endif /* __WXGTK2__ */ -//#define NANOSVG_IMPLEMENTATION -#include "nanosvg/nanosvg.h" -#define NANOSVGRAST_IMPLEMENTATION -#include "nanosvg/nanosvgrast.h" +#include +#include namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 6065f22a5..9c039376d 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -23,8 +23,8 @@ #define STB_DXT_IMPLEMENTATION #include "stb_dxt/stb_dxt.h" -#include "nanosvg/nanosvg.h" -#include "nanosvg/nanosvgrast.h" +#include +#include #include "libslic3r/Utils.hpp" diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 5ab4685bf..9aa8833ff 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -37,9 +37,8 @@ #endif // ENABLE_GL_IMGUI_SHADERS #include "../Utils/MacDarkMode.hpp" - -#include "nanosvg/nanosvg.h" -#include "nanosvg/nanosvgrast.h" +#include +#include namespace Slic3r { namespace GUI { From 2ab64819aabdc28661fcc4e552c05e91d9366bc0 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 26 Apr 2022 15:53:28 +0200 Subject: [PATCH 163/169] Fixes to support wxWidgets 3.1.6(7) --- src/slic3r/GUI/AboutDialog.cpp | 9 ++++--- src/slic3r/GUI/BitmapComboBox.cpp | 4 ++- src/slic3r/GUI/GUI_App.cpp | 3 ++- src/slic3r/GUI/GUI_Factories.cpp | 14 ++++++++--- src/slic3r/GUI/GUI_Factories.hpp | 4 ++- src/slic3r/GUI/KBShortcutsDialog.cpp | 10 +++++--- src/slic3r/GUI/MsgDialog.cpp | 8 ++++++ src/slic3r/GUI/ObjectDataViewModel.cpp | 2 +- src/slic3r/GUI/PresetComboBoxes.cpp | 7 +++--- src/slic3r/GUI/SysInfoDialog.cpp | 10 +++++--- src/slic3r/GUI/Tab.cpp | 7 ++++-- src/slic3r/GUI/wxExtensions.cpp | 35 ++++++++++++++++++-------- src/slic3r/GUI/wxExtensions.hpp | 7 ++++-- 13 files changed, 83 insertions(+), 37 deletions(-) diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index e444fb03c..0fe75d453 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -221,8 +221,9 @@ AboutDialog::AboutDialog() main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20); // logo - m_logo_bitmap = ScalableBitmap(this, wxGetApp().logo_name(), 192); - m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bitmap.bmp()); +// m_logo_bitmap = ScalableBitmap(this, wxGetApp().logo_name(), 192); +// m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bitmap.bmp()); + m_logo = new wxStaticBitmap(this, wxID_ANY, wxBitmapBundle::FromSVGFile(Slic3r::var(wxGetApp().logo_name()+".svg"), wxSize(192, 192))); hsizer->Add(m_logo, 1, wxALIGN_CENTER_VERTICAL); wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); @@ -322,8 +323,8 @@ AboutDialog::AboutDialog() void AboutDialog::on_dpi_changed(const wxRect &suggested_rect) { - m_logo_bitmap.msw_rescale(); - m_logo->SetBitmap(m_logo_bitmap.bmp()); +// m_logo_bitmap.msw_rescale(); +// m_logo->SetBitmap(m_logo_bitmap.bmp()); const wxFont& font = GetFont(); const int fs = font.GetPointSize() - 1; diff --git a/src/slic3r/GUI/BitmapComboBox.cpp b/src/slic3r/GUI/BitmapComboBox.cpp index 3396c627b..54e1a31fa 100644 --- a/src/slic3r/GUI/BitmapComboBox.cpp +++ b/src/slic3r/GUI/BitmapComboBox.cpp @@ -166,7 +166,8 @@ int BitmapComboBox::Append(const wxString& item) //2. But then set width to 0 value for no using of bitmap left and right spacing //3. Set this empty bitmap to the at list one item and BitmapCombobox will be recreated correct - wxBitmap bitmap(1, int(1.6 * wxGetApp().em_unit() + 1)); +// wxBitmap bitmap(1, int(1.6 * wxGetApp().em_unit() + 1)); + wxBitmap bitmap(1, 16); { // bitmap.SetWidth(0); is depricated now // so, use next code @@ -268,6 +269,7 @@ void BitmapComboBox::DrawBackground_(wxDC& dc, const wxRect& rect, int WXUNUSED( void BitmapComboBox::Rescale() { + return; // Next workaround: To correct scaling of a BitmapCombobox // we need to refill control with new bitmaps const wxString selection = this->GetValue(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9a4921a84..4b0544488 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2088,6 +2088,7 @@ bool GUI_App::load_language(wxString language, bool initial) { // Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance. wxLocale temp_locale; + temp_locale.Init(); // Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code). wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT); // Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer @@ -2228,7 +2229,7 @@ void GUI_App::update_mode() { sidebar().update_mode(); -#ifdef _MSW_DARK_MODE +#ifdef _WIN32 //_MSW_DARK_MODE if (!wxGetApp().tabs_as_menu()) dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); #endif diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 7b3476d71..4adb161c2 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -142,13 +142,20 @@ std::map SettingsFactory::CATEGORY_ICON = { L("Hollowing") , "hollowing" } }; -wxBitmap SettingsFactory::get_category_bitmap(const std::string& category_name, bool menu_bmp /*= true*/) +//wxBitmap SettingsFactory::get_category_bitmap(const std::string& category_name, bool menu_bmp /*= true*/) +wxBitmap SettingsFactory::get_category_bitmap_(const std::string& category_name, bool menu_bmp /*= true*/) { if (CATEGORY_ICON.find(category_name) == CATEGORY_ICON.end()) return wxNullBitmap; - return menu_bmp ? create_menu_bitmap(CATEGORY_ICON.at(category_name)) : create_scaled_bitmap(CATEGORY_ICON.at(category_name)); + return /*menu_bmp ? create_menu_bitmap(CATEGORY_ICON.at(category_name)) : */create_scaled_bitmap(CATEGORY_ICON.at(category_name)); } +wxBitmapBundle SettingsFactory::get_category_bitmap(const std::string& category_name) +{ + if (CATEGORY_ICON.find(category_name) == CATEGORY_ICON.end()) + return wxNullBitmap; + return create_menu_bitmap(CATEGORY_ICON.at(category_name)); +} //------------------------------------- // MenuFactory @@ -435,7 +442,8 @@ std::vector MenuFactory::get_volume_bitmaps() std::vector volume_bmps; volume_bmps.reserve(ADD_VOLUME_MENU_ITEMS.size()); for (auto item : ADD_VOLUME_MENU_ITEMS) - volume_bmps.push_back(create_menu_bitmap(item.second)); +// volume_bmps.push_back(create_menu_bitmap(item.second)); + volume_bmps.push_back(create_scaled_bitmap(item.second, nullptr, 16, false, "", true)); return volume_bmps; } diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index 0c478a97b..cba828c29 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -25,7 +25,9 @@ struct SettingsFactory typedef std::map> Bundle; static std::map CATEGORY_ICON; - static wxBitmap get_category_bitmap(const std::string& category_name, bool menu_bmp = true); +// static wxBitmap get_category_bitmap(const std::string& category_name, bool menu_bmp = true); + static wxBitmap get_category_bitmap_(const std::string& category_name, bool menu_bmp = true); + static wxBitmapBundle get_category_bitmap(const std::string& category_name); static Bundle get_bundle(const DynamicPrintConfig* config, bool is_object_settings); static std::vector get_options(bool is_part); }; diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index ba3f6675f..a749ad405 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -57,8 +57,8 @@ KBShortcutsDialog::KBShortcutsDialog() void KBShortcutsDialog::on_dpi_changed(const wxRect& suggested_rect) { - m_logo_bmp.msw_rescale(); - m_header_bitmap->SetBitmap(m_logo_bmp.bmp()); + //m_logo_bmp.msw_rescale(); + //m_header_bitmap->SetBitmap(m_logo_bmp.bmp()); msw_buttons_rescale(this, em_unit(), { wxID_OK }); Layout(); @@ -270,8 +270,10 @@ wxPanel* KBShortcutsDialog::create_header(wxWindow* parent, const wxFont& bold_f sizer->AddStretchSpacer(); // logo - m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 32); - m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, m_logo_bmp.bmp()); + //m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 32); + //m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, m_logo_bmp.bmp()); + m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, wxBitmapBundle::FromSVGFile(Slic3r::var(wxGetApp().logo_name() + ".svg"), wxSize(32, 32))); + sizer->Add(m_header_bitmap, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); // text diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 94e9ca5f3..76bcfdd4a 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -99,9 +99,17 @@ void MsgDialog::apply_style(long style) if (style & wxNO) add_button(wxID_NO, (style & wxNO_DEFAULT)); if (style & wxCANCEL) add_button(wxID_CANCEL, (style & wxCANCEL_DEFAULT)); +#if 0 logo->SetBitmap( create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" : style & wxICON_INFORMATION ? "info" : style & wxICON_QUESTION ? "question" : "PrusaSlicer", this, 64, style & wxICON_ERROR)); +#else + std::string icon_name = style & wxICON_WARNING ? "exclamation" : + style & wxICON_INFORMATION ? "info" : + style & wxICON_QUESTION ? "question" : "PrusaSlicer"; + icon_name += ".svg"; + logo->SetBitmap(wxBitmapBundle::FromSVGFile(Slic3r::var(icon_name), wxSize(64, 64))); +#endif } void MsgDialog::finalize() diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 496cdcfc7..4344deb24 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -191,7 +191,7 @@ void ObjectDataViewModelNode::update_settings_digest_bitmaps() if (bmp == nullptr) { std::vector bmps; for (auto& category : m_opt_categories) - bmps.emplace_back(SettingsFactory::get_category_bitmap(category, false)); + bmps.emplace_back(SettingsFactory::get_category_bitmap_(category, false)); bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index ed4888a87..def48a1e4 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -376,8 +376,8 @@ void PresetComboBox::msw_rescale() { m_em_unit = em_unit(this); - m_bitmapIncompatible.msw_rescale(); - m_bitmapCompatible.msw_rescale(); + //m_bitmapIncompatible.msw_rescale(); + //m_bitmapCompatible.msw_rescale(); // parameters for an icon's drawing fill_width_height(); @@ -403,7 +403,8 @@ void PresetComboBox::fill_width_height() * So set sizes for solid_colored icons used for filament preset * and scale them in respect to em_unit value */ - const float scale_f = (float)m_em_unit * 0.1f; +// const float scale_f = (float)m_em_unit * 0.1f; + const float scale_f = 1.0f; thin_icon_width = lroundf(8 * scale_f); // analogue to 8px; wide_icon_width = norm_icon_width + thin_icon_width; diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 53e7d637d..db8cfc9c2 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -102,8 +102,10 @@ SysInfoDialog::SysInfoDialog() main_sizer->Add(hsizer, 1, wxEXPAND | wxALL, 10); // logo - m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 192); - m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bmp.bmp()); + //m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 192); + //m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bmp.bmp()); + m_logo = new wxStaticBitmap(this, wxID_ANY, wxBitmapBundle::FromSVGFile(Slic3r::var(wxGetApp().logo_name() + ".svg"), wxSize(192, 192))); + hsizer->Add(m_logo, 0, wxALIGN_CENTER_VERTICAL); wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); @@ -194,8 +196,8 @@ SysInfoDialog::SysInfoDialog() void SysInfoDialog::on_dpi_changed(const wxRect &suggested_rect) { - m_logo_bmp.msw_rescale(); - m_logo->SetBitmap(m_logo_bmp.bmp()); + //m_logo_bmp.msw_rescale(); + //m_logo->SetBitmap(m_logo_bmp.bmp()); wxFont font = get_default_font(this); const int fs = font.GetPointSize() - 1; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 96151d8c7..1cb6ec8fc 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -772,8 +772,11 @@ void Tab::update_changed_tree_ui() void Tab::update_undo_buttons() { - m_undo_btn-> SetBitmap_(m_is_modified_values ? m_bmp_value_revert: m_bmp_white_bullet); - m_undo_to_sys_btn-> SetBitmap_(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); + m_undo_btn-> SetBitmap_(m_is_modified_values ? m_bmp_value_revert.name(): m_bmp_white_bullet.name()); + m_undo_to_sys_btn-> SetBitmap_(m_is_nonsys_values ? m_bmp_non_system->name() : m_bmp_value_lock.name()); + + //m_undo_btn-> SetBitmap_(m_is_modified_values ? m_bmp_value_revert: m_bmp_white_bullet); + //m_undo_to_sys_btn-> SetBitmap_(m_is_nonsys_values ? *m_bmp_non_system : m_bmp_value_lock); m_undo_btn->SetToolTip(m_is_modified_values ? m_ttg_value_revert : m_ttg_white_bullet); m_undo_to_sys_btn->SetToolTip(m_is_nonsys_values ? *m_ttg_non_system : m_ttg_value_lock); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 876398510..9326c9258 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -16,6 +16,7 @@ #include "Plater.hpp" #include "../Utils/MacDarkMode.hpp" #include "BitmapComboBox.hpp" +#include "libslic3r/Utils.hpp" #include "OG_CustomCtrl.hpp" #include "libslic3r/Color.hpp" @@ -26,11 +27,13 @@ static std::map msw_menuitem_bitmaps; #ifdef __WXMSW__ void msw_rescale_menu(wxMenu* menu) { + return; struct update_icons { static void run(wxMenuItem* item) { const auto it = msw_menuitem_bitmaps.find(item->GetId()); if (it != msw_menuitem_bitmaps.end()) { - const wxBitmap& item_icon = create_menu_bitmap(it->second); +// const wxBitmap& item_icon = create_menu_bitmap(it->second); + const wxBitmapBundle& item_icon = create_menu_bitmap(it->second); if (item_icon.IsOk()) item->SetBitmap(item_icon); } @@ -63,7 +66,8 @@ void enable_menu_item(wxUpdateUIEvent& evt, std::function const cb_condi } wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, - std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler, +// std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler, + std::function cb, const wxBitmapBundle& icon, wxEvtHandler* event_handler, std::function const cb_condition, wxWindow* parent, int insert_pos/* = wxNOT_FOUND*/) { if (id == wxID_ANY) @@ -100,7 +104,9 @@ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const if (id == wxID_ANY) id = wxNewId(); - const wxBitmap& bmp = !icon.empty() ? create_menu_bitmap(icon) : wxNullBitmap; // FIXME: pass window ptr +// const wxBitmap& bmp = !icon.empty() ? create_menu_bitmap(icon) : wxNullBitmap; // FIXME: pass window ptr + const wxBitmapBundle& bmp = !icon.empty() ? create_menu_bitmap(icon) : wxNullBitmap; // FIXME: pass window ptr + //#ifdef __WXMSW__ #ifndef __WXGTK__ if (bmp.IsOk()) @@ -420,9 +426,11 @@ int mode_icon_px_size() #endif } -wxBitmap create_menu_bitmap(const std::string& bmp_name) +//wxBitmap create_menu_bitmap(const std::string& bmp_name) +wxBitmapBundle create_menu_bitmap(const std::string& bmp_name) { - return create_scaled_bitmap(bmp_name, nullptr, 16, false, "", true); + // return create_scaled_bitmap(bmp_name, nullptr, 16, false, "", true); + return wxBitmapBundle::FromSVGFile(Slic3r::var(bmp_name + ".svg"), wxSize(16, 16)); } // win is used to get a correct em_unit value @@ -601,6 +609,7 @@ void LockButton::SetLock(bool lock) void LockButton::msw_rescale() { + return; m_bmp_lock_closed.msw_rescale(); m_bmp_lock_closed_f.msw_rescale(); m_bmp_lock_open.msw_rescale(); @@ -639,7 +648,8 @@ ModeButton::ModeButton( wxWindow* parent, const wxString& mode/* = wxEmptyString*/, const std::string& icon_name/* = ""*/, int px_cnt/* = 16*/) : - ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, icon_name, px_cnt), mode, wxBU_EXACTFIT) +// ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, icon_name, px_cnt), mode, wxBU_EXACTFIT) + ScalableButton(parent, wxID_ANY, icon_name, mode, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT) { Init(mode); } @@ -852,9 +862,10 @@ ScalableButton::ScalableButton( wxWindow * parent, Slic3r::GUI::wxGetApp().UpdateDarkUI(this); if (!icon_name.empty()) { - SetBitmap(create_scaled_bitmap(icon_name, parent, m_px_cnt)); - if (m_use_default_disabled_bitmap) - SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); +// SetBitmap(create_scaled_bitmap(icon_name, parent, m_px_cnt)); + SetBitmap(wxBitmapBundle::FromSVGFile(Slic3r::var(icon_name + ".svg"), wxSize(m_px_cnt, m_px_cnt))); + //if (m_use_default_disabled_bitmap) + // SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); if (!label.empty()) SetBitmapMargins(int(0.5* em_unit(parent)), 0); } @@ -896,7 +907,8 @@ bool ScalableButton::SetBitmap_(const std::string& bmp_name) if (m_current_icon_name.empty()) return false; - wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt); +// wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt); + wxBitmapBundle bmp = wxBitmapBundle::FromSVGFile(Slic3r::var(m_current_icon_name + ".svg"), wxSize(16, 16)); SetBitmap(bmp); SetBitmapCurrent(bmp); SetBitmapPressed(bmp); @@ -931,7 +943,8 @@ void ScalableButton::msw_rescale() { Slic3r::GUI::wxGetApp().UpdateDarkUI(this, m_has_border); - if (!m_current_icon_name.empty()) { +// if (!m_current_icon_name.empty()) { + if (0) { wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt); SetBitmap(bmp); SetBitmapCurrent(bmp); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index f3921919e..8387551f2 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,8 @@ inline void msw_rescale_menu(wxMenu* /* menu */) {} #endif /* __WXMSW__ */ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, - std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler = nullptr, +// std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler = nullptr, + std::function cb, const wxBitmapBundle& icon, wxEvtHandler* event_handler = nullptr, std::function const cb_condition = []() { return true;}, wxWindow* parent = nullptr, int insert_pos = wxNOT_FOUND); wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, const std::string& icon = "", wxEvtHandler* event_handler = nullptr, @@ -49,7 +51,8 @@ void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector< int em_unit(wxWindow* win); int mode_icon_px_size(); -wxBitmap create_menu_bitmap(const std::string& bmp_name); +//wxBitmap create_menu_bitmap(const std::string& bmp_name); +wxBitmapBundle create_menu_bitmap(const std::string& bmp_name); wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullptr, const int px_cnt = 16, const bool grayscale = false, From dd6f7a71f1aaf95b93270a14073d1a5239669d5b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 10 May 2022 12:24:04 +0200 Subject: [PATCH 164/169] Using of wxWidgets 3.1.6 WIP: * Create Cache of wxBitmapBundles instead of wxBitmaps * Use wxBitmapBundles instead of wxBitmap for most of Widgets * Use empty bitmabundles instead of wxNullBitmap for wxBitmapComboBoxes. * Updated wxWidgets.cmake * OSX specific: Discard BitmapComboBox overrides + some code cleaning --- src/slic3r/GUI/AboutDialog.cpp | 4 +- src/slic3r/GUI/BitmapCache.cpp | 327 +++++++++++++++++++++- src/slic3r/GUI/BitmapCache.hpp | 21 +- src/slic3r/GUI/BitmapComboBox.cpp | 92 +----- src/slic3r/GUI/BitmapComboBox.hpp | 17 +- src/slic3r/GUI/ButtonsDescription.cpp | 3 - src/slic3r/GUI/ConfigWizard.cpp | 24 +- src/slic3r/GUI/ConfigWizard_private.hpp | 2 +- src/slic3r/GUI/DoubleSlider.cpp | 101 +++---- src/slic3r/GUI/ExtraRenderers.cpp | 2 +- src/slic3r/GUI/ExtruderSequenceDialog.cpp | 3 - src/slic3r/GUI/Field.hpp | 16 +- src/slic3r/GUI/GUI_App.cpp | 4 +- src/slic3r/GUI/GUI_Factories.cpp | 42 +-- src/slic3r/GUI/GUI_Factories.hpp | 7 +- src/slic3r/GUI/GUI_ObjectLayers.cpp | 68 ++--- src/slic3r/GUI/GUI_ObjectList.cpp | 8 +- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 41 +-- src/slic3r/GUI/GUI_ObjectSettings.cpp | 13 +- src/slic3r/GUI/GUI_ObjectSettings.hpp | 1 - src/slic3r/GUI/GalleryDialog.cpp | 44 ++- src/slic3r/GUI/KBShortcutsDialog.cpp | 4 +- src/slic3r/GUI/MainFrame.cpp | 9 +- src/slic3r/GUI/MsgDialog.cpp | 11 +- src/slic3r/GUI/Notebook.cpp | 12 +- src/slic3r/GUI/Notebook.hpp | 6 + src/slic3r/GUI/OG_CustomCtrl.cpp | 42 +-- src/slic3r/GUI/OG_CustomCtrl.hpp | 2 +- src/slic3r/GUI/ObjectDataViewModel.cpp | 84 +++--- src/slic3r/GUI/ObjectDataViewModel.hpp | 38 +-- src/slic3r/GUI/OptionsGroup.cpp | 3 +- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 27 +- src/slic3r/GUI/PhysicalPrinterDialog.hpp | 5 +- src/slic3r/GUI/Plater.cpp | 41 +-- src/slic3r/GUI/PresetComboBoxes.cpp | 177 ++++++------ src/slic3r/GUI/PresetComboBoxes.hpp | 14 +- src/slic3r/GUI/SavePresetDialog.cpp | 4 +- src/slic3r/GUI/Search.cpp | 11 +- src/slic3r/GUI/Search.hpp | 2 +- src/slic3r/GUI/SysInfoDialog.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 116 +++----- src/slic3r/GUI/Tab.hpp | 6 - src/slic3r/GUI/UnsavedChangesDialog.cpp | 58 ++-- src/slic3r/GUI/wxExtensions.cpp | 223 +++++---------- src/slic3r/GUI/wxExtensions.hpp | 60 ++-- 45 files changed, 930 insertions(+), 867 deletions(-) diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 0fe75d453..abd137941 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -221,9 +221,7 @@ AboutDialog::AboutDialog() main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20); // logo -// m_logo_bitmap = ScalableBitmap(this, wxGetApp().logo_name(), 192); -// m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bitmap.bmp()); - m_logo = new wxStaticBitmap(this, wxID_ANY, wxBitmapBundle::FromSVGFile(Slic3r::var(wxGetApp().logo_name()+".svg"), wxSize(192, 192))); + m_logo = new wxStaticBitmap(this, wxID_ANY, *get_bmp_bundle(wxGetApp().logo_name(), 192)); hsizer->Add(m_logo, 1, wxALIGN_CENTER_VERTICAL); wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 844f6dea9..d2bf98b5b 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -60,7 +60,164 @@ static wxBitmap wxImage_to_wxBitmap_with_alpha(wxImage &&image, float scale = 1. #endif } -wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height) +wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vector& bmps) +{ + wxVector bitmaps; + + std::set scales = {1.0}; +#ifdef __APPLE__ + scales.emplace(m_scale); +#else + size_t disp_cnt = wxDisplay::GetCount(); + for (size_t disp = 0; disp < disp_cnt; ++disp) + scales.emplace(wxDisplay(disp).GetScaleFactor()); +#endif + + for (double scale : scales) { + size_t width = 0; + size_t height = 0; + for (const wxBitmapBundle* bmp_bndl : bmps) { +#ifdef __APPLE__ + wxSize size = bmp_bndl->GetPreferredBitmapSizeAtScale(1.0); +#else + wxSize size = bmp_bndl->GetPreferredBitmapSizeAtScale(scale); +#endif + width += size.GetWidth(); + height = std::max(height, size.GetHeight()); + } + + std::string bitmap_key = name + "," +float_to_string_decimal_point(scale); + +#ifdef __WXGTK2__ + // Broken alpha workaround + wxImage image(width, height); + image.InitAlpha(); + // Fill in with a white color. + memset(image.GetData(), 0x0ff, width * height * 3); + // Fill in with full transparency. + memset(image.GetAlpha(), 0, width * height); + size_t x = 0; + for (const wxBitmapBundle* bmp_bndl : bmps) { + wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetPreferredBitmapSizeAtScale(scale)); + if (bmp.GetWidth() > 0) { + if (bmp.GetDepth() == 32) { + wxAlphaPixelData data(bmp); + //FIXME The following method is missing from wxWidgets 3.1.1. + // It looks like the wxWidgets 3.0.3 called the wrapped bitmap's UseAlpha(). + //data.UseAlpha(); + if (data) { + for (int r = 0; r < bmp.GetHeight(); ++r) { + wxAlphaPixelData::Iterator src(data); + src.Offset(data, 0, r); + unsigned char* dst_pixels = image.GetData() + (x + r * width) * 3; + unsigned char* dst_alpha = image.GetAlpha() + x + r * width; + for (int c = 0; c < bmp.GetWidth(); ++c, ++src) { + *dst_pixels++ = src.Red(); + *dst_pixels++ = src.Green(); + *dst_pixels++ = src.Blue(); + *dst_alpha++ = src.Alpha(); + } + } + } + } + else if (bmp.GetDepth() == 24) { + wxNativePixelData data(bmp); + if (data) { + for (int r = 0; r < bmp.GetHeight(); ++r) { + wxNativePixelData::Iterator src(data); + src.Offset(data, 0, r); + unsigned char* dst_pixels = image.GetData() + (x + r * width) * 3; + unsigned char* dst_alpha = image.GetAlpha() + x + r * width; + for (int c = 0; c < bmp.GetWidth(); ++c, ++src) { + *dst_pixels++ = src.Red(); + *dst_pixels++ = src.Green(); + *dst_pixels++ = src.Blue(); + *dst_alpha++ = wxALPHA_OPAQUE; + } + } + } + } + } + x += bmp.GetWidth(); + } + + bitmaps.push_back(* this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)))); + +#else + + wxBitmap* bitmap = this->insert(bitmap_key, width, height, scale); + wxMemoryDC memDC; + memDC.SelectObject(*bitmap); + memDC.SetBackground(*wxTRANSPARENT_BRUSH); + memDC.Clear(); + size_t x = 0; + for (const wxBitmapBundle* bmp_bndl : bmps) { + wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetPreferredBitmapSizeAtScale(scale)); + + if (bmp.GetWidth() > 0) + memDC.DrawBitmap(bmp, x, 0, true); +#ifdef __APPLE__ + // we should "move" with step equal to non-scaled width + x += bmp.GetScaledWidth(); +#else + x += bmp.GetWidth(); +#endif + } + memDC.SelectObject(wxNullBitmap); + bitmaps.push_back(*bitmap); + +#endif + } + + return insert_bndl(name, bitmaps); +} + +wxBitmapBundle* BitmapCache::insert_bndl(const std::string &bitmap_key, const char* data, size_t width, size_t height) +{ + wxBitmapBundle* bndl = nullptr; + auto it = m_bndl_map.find(bitmap_key); + if (it == m_bndl_map.end()) { + bndl = new wxBitmapBundle(wxBitmapBundle::FromSVG(data, wxSize(width, height))); + m_bndl_map[bitmap_key] = bndl; + } + else { + bndl = it->second; + *bndl = wxBitmapBundle::FromSVG(data, wxSize(width, height)); + } + return bndl; +} + +wxBitmapBundle* BitmapCache::insert_bndl(const std::string& bitmap_key, const wxBitmapBundle& bmp) +{ + wxBitmapBundle* bndl = nullptr; + auto it = m_bndl_map.find(bitmap_key); + if (it == m_bndl_map.end()) { + bndl = new wxBitmapBundle(bmp); + m_bndl_map[bitmap_key] = bndl; + } + else { + bndl = it->second; + *bndl = wxBitmapBundle(bmp); + } + return bndl; +} + +wxBitmapBundle* BitmapCache::insert_bndl(const std::string& bitmap_key, const wxVector& bmps) +{ + wxBitmapBundle* bndl = nullptr; + auto it = m_bndl_map.find(bitmap_key); + if (it == m_bndl_map.end()) { + bndl = new wxBitmapBundle(wxBitmapBundle::FromBitmaps(bmps)); + m_bndl_map[bitmap_key] = bndl; + } + else { + bndl = it->second; + *bndl = wxBitmapBundle::FromBitmaps(bmps); + } + return bndl; +} + +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height, double scale/* = -1.0*/) { wxBitmap *bitmap = nullptr; auto it = m_map.find(bitmap_key); @@ -76,7 +233,7 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_ // So, We need to let the Mac OS wxBitmap implementation // know that the image may already be scaled appropriately for Retina, // and thereby that it's not supposed to upscale it. - bitmap->CreateScaled(width, height, -1, m_scale); + bitmap->CreateScaled(width, height, -1, scale < 0.0 ? m_scale : scale); #endif m_map[bitmap_key] = bitmap; } else { @@ -297,6 +454,93 @@ error: return NULL; } +void BitmapCache::nsvgGetDataFromFileWithReplace(const char* filename, std::string& data_str, const std::map& replaces) +{ + FILE* fp = NULL; + size_t size; + char* data = NULL; + + fp = boost::nowide::fopen(filename, "rb"); + if (!fp) goto error; + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + data = (char*)malloc(size + 1); + if (data == NULL) goto error; + if (fread(data, 1, size, fp) != size) goto error; + data[size] = '\0'; // Must be null terminated. + fclose(fp); + + data_str.assign(data); + for (auto val : replaces) + boost::replace_all(data_str, val.first, val.second); + + free(data); + return; + +error: + if (fp) fclose(fp); + if (data) free(data); + return; +} + +wxBitmapBundle* BitmapCache::from_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height, + const bool dark_mode, const std::string& new_color /*= ""*/) +{ + if (target_width == 0) + target_width = target_height; + std::string bitmap_key = bitmap_name + (target_height != 0 ? + "-h" + std::to_string(target_height) : + "-w" + std::to_string(target_width)) +// + (m_scale != 1.0f ? "-s" + float_to_string_decimal_point(m_scale) : "") + + (dark_mode ? "-dm" : "") + + new_color; + + auto it = m_bndl_map.find(bitmap_key); + if (it != m_bndl_map.end()) + return it->second; + + // map of color replaces + std::map replaces; + if (dark_mode) + replaces["\"#808080\""] = "\"#FFFFFF\""; + if (!new_color.empty()) + replaces["\"#ED6B21\""] = "\"" + new_color + "\""; + + std::string str; + nsvgGetDataFromFileWithReplace(Slic3r::var(bitmap_name + ".svg").c_str(), str, replaces); + if (str.empty()) + return nullptr; + + return insert_bndl(bitmap_key, str.data(), target_width, target_height); +} + +wxBitmapBundle* BitmapCache::from_png(const std::string& bitmap_name, unsigned width, unsigned height) +{ + std::string bitmap_key = bitmap_name + (height != 0 ? + "-h" + std::to_string(height) : + "-w" + std::to_string(width)); + + auto it = m_bndl_map.find(bitmap_key); + if (it != m_bndl_map.end()) + return it->second; + + wxImage image; + if (!image.LoadFile(Slic3r::GUI::from_u8(Slic3r::var(bitmap_name + ".png")), wxBITMAP_TYPE_PNG) || + image.GetWidth() == 0 || image.GetHeight() == 0) + return nullptr; + + if (height != 0 && unsigned(image.GetHeight()) != height) + width = unsigned(0.5f + float(image.GetWidth()) * height / image.GetHeight()); + else if (width != 0 && unsigned(image.GetWidth()) != width) + height = unsigned(0.5f + float(image.GetHeight()) * width / image.GetWidth()); + + if (height != 0 && width != 0) + image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); + + return this->insert_bndl(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image))); +} + wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height, const bool grayscale/* = false*/, const bool dark_mode/* = false*/, const std::string& new_color /*= ""*/) { @@ -395,5 +639,84 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi return wxImage_to_wxBitmap_with_alpha(std::move(image), scale); } +//we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap +wxBitmapBundle BitmapCache::mksolid(size_t width_in, size_t height_in, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, size_t border_width /*= 0*/, bool dark_mode/* = false*/) +{ + wxVector bitmaps; + + std::set scales = { 1.0 }; +#ifdef __APPLE__ + scales.emplace(m_scale); +#else + size_t disp_cnt = wxDisplay::GetCount(); + for (size_t disp = 0; disp < disp_cnt; ++disp) + scales.emplace(wxDisplay(disp).GetScaleFactor()); +#endif + + for (double scale : scales) { + size_t width = width_in * scale; + size_t height = height_in * scale; + + wxImage image(width, height); + image.InitAlpha(); + unsigned char* imgdata = image.GetData(); + unsigned char* imgalpha = image.GetAlpha(); + for (size_t i = 0; i < width * height; ++i) { + *imgdata++ = r; + *imgdata++ = g; + *imgdata++ = b; + *imgalpha++ = transparency; + } + + // Add border, make white/light spools easier to see + if (border_width > 0) { + + // Restrict to width of image + if (border_width > height) border_width = height - 1; + if (border_width > width) border_width = width - 1; + + auto px_data = (uint8_t*)image.GetData(); + auto a_data = (uint8_t*)image.GetAlpha(); + + for (size_t x = 0; x < width; ++x) { + for (size_t y = 0; y < height; ++y) { + if (x < border_width || y < border_width || + x >= (width - border_width) || y >= (height - border_width)) { + const size_t idx = (x + y * width); + const size_t idx_rgb = (x + y * width) * 3; + px_data[idx_rgb] = px_data[idx_rgb + 1] = px_data[idx_rgb + 2] = dark_mode ? 245u : 110u; + a_data[idx] = 255u; + } + } + } + } + + bitmaps.push_back(wxImage_to_wxBitmap_with_alpha(std::move(image), scale)); + } + return wxBitmapBundle::FromBitmaps(bitmaps); +} + +wxBitmapBundle* BitmapCache::mksolid_bndl(size_t width, size_t height, const std::string& color, size_t border_width, bool dark_mode) +{ + std::string bitmap_key = (color.empty() ? "empty-w" : color) + "-h" + std::to_string(height) + "-w" + std::to_string(width) + (dark_mode ? "-dm" : ""); + + wxBitmapBundle* bndl = nullptr; + auto it = m_bndl_map.find(bitmap_key); + if (it == m_bndl_map.end()) { + if (color.empty()) + bndl = new wxBitmapBundle(mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT, size_t(0))); + else { + ColorRGB rgb;// [3] ; + decode_color(color, rgb); + bndl = new wxBitmapBundle(mksolid(width, height, rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar(), wxALPHA_OPAQUE, border_width, dark_mode)); + } + m_bndl_map[bitmap_key] = bndl; + } + else + return it->second; + + return bndl; +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index 5af90c5f7..28058d94b 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -24,10 +24,18 @@ public: void clear(); double scale() { return m_scale; } + wxBitmapBundle* find_bndl(const std::string &name) { auto it = m_bndl_map.find(name); return (it == m_bndl_map.end()) ? nullptr : it->second; } + const wxBitmapBundle* find_bndl(const std::string &name) const { return const_cast(this)->find_bndl(name); } wxBitmap* find(const std::string &name) { auto it = m_map.find(name); return (it == m_map.end()) ? nullptr : it->second; } const wxBitmap* find(const std::string &name) const { return const_cast(this)->find(name); } - wxBitmap* insert(const std::string &name, size_t width, size_t height); + wxBitmapBundle* insert_bndl(const std::string& bitmap_key, const char* data, size_t width, size_t height); + wxBitmapBundle* insert_bndl(const std::string& bitmap_key, const wxBitmapBundle &bmp); + wxBitmapBundle* insert_bndl(const std::string& bitmap_key, const wxVector& bmps); + wxBitmapBundle* insert_bndl(const std::string& name, const std::vector& bmps); + wxBitmapBundle* insert_raw_rgba_bndl(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale = false); + + wxBitmap* insert(const std::string &name, size_t width, size_t height, double scale = -1.0); wxBitmap* insert(const std::string &name, const wxBitmap &bmp); wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2); wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3); @@ -42,15 +50,24 @@ public: // And makes replases befor parsing // replace_map containes old_value->new_value static NSVGimage* nsvgParseFromFileWithReplace(const char* filename, const char* units, float dpi, const std::map& replaces); + // Gets a data from SVG file and makes replases + // replace_map containes old_value->new_value + static void nsvgGetDataFromFileWithReplace(const char* filename, std::string& data_str, const std::map& replaces); + wxBitmapBundle* from_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height, const bool dark_mode, const std::string& new_color = ""); + wxBitmapBundle* from_png(const std::string& bitmap_name, unsigned width, unsigned height); // Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width. wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::string& new_color = ""); wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false); wxBitmap mksolid(size_t width, size_t height, const ColorRGB& rgb, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false) { return mksolid(width, height, rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar(), wxALPHA_OPAQUE, suppress_scaling, border_width, dark_mode); } - wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); } + wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT, true, 0); } + wxBitmapBundle mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, size_t border_width = 0, bool dark_mode = false); + wxBitmapBundle* mksolid_bndl(size_t width, size_t height, const std::string& color = std::string(), size_t border_width = 0, bool dark_mode = false); + wxBitmapBundle* mkclear_bndl(size_t width, size_t height) { return mksolid_bndl(width, height); } private: std::map m_map; + std::map m_bndl_map; double m_gs = 0.2; // value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs) double m_scale = 1.0; // value, used for correct scaling of SVG icons on Retina display }; diff --git a/src/slic3r/GUI/BitmapComboBox.cpp b/src/slic3r/GUI/BitmapComboBox.cpp index 54e1a31fa..70c985cf9 100644 --- a/src/slic3r/GUI/BitmapComboBox.cpp +++ b/src/slic3r/GUI/BitmapComboBox.cpp @@ -54,17 +54,6 @@ using Slic3r::GUI::format_wxstr; namespace Slic3r { namespace GUI { -/* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina - * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean - * "please scale this to such and such" but rather - * "the wxImage is already sized for backing scale such and such". ) - * Unfortunately, the constructor changes the size of wxBitmap too. - * Thus We need to use unscaled size value for bitmaps that we use - * to avoid scaled size of control items. - * For this purpose control drawing methods and - * control size calculation methods (virtual) are overridden. - **/ - BitmapComboBox::BitmapComboBox(wxWindow* parent, wxWindowID id/* = wxID_ANY*/, const wxString& value/* = wxEmptyString*/, @@ -90,72 +79,6 @@ BitmapComboBox::~BitmapComboBox() { } -#ifdef __APPLE__ -bool BitmapComboBox::OnAddBitmap(const wxBitmap& bitmap) -{ - if (bitmap.IsOk()) - { - // we should use scaled! size values of bitmap - int width = (int)bitmap.GetScaledWidth(); - int height = (int)bitmap.GetScaledHeight(); - - if (m_usedImgSize.x < 0) - { - // If size not yet determined, get it from this image. - m_usedImgSize.x = width; - m_usedImgSize.y = height; - - // Adjust control size to vertically fit the bitmap - wxWindow* ctrl = GetControl(); - ctrl->InvalidateBestSize(); - wxSize newSz = ctrl->GetBestSize(); - wxSize sz = ctrl->GetSize(); - if (newSz.y > sz.y) - ctrl->SetSize(sz.x, newSz.y); - else - DetermineIndent(); - } - - wxCHECK_MSG(width == m_usedImgSize.x && height == m_usedImgSize.y, - false, - "you can only add images of same size"); - - return true; - } - - return false; -} - -void BitmapComboBox::OnDrawItem(wxDC& dc, - const wxRect& rect, - int item, - int flags) const -{ - const wxBitmap& bmp = *(static_cast(m_bitmaps[item])); - if (bmp.IsOk()) - { - // we should use scaled! size values of bitmap - wxCoord w = bmp.GetScaledWidth(); - wxCoord h = bmp.GetScaledHeight(); - - const int imgSpacingLeft = 4; - - // Draw the image centered - dc.DrawBitmap(bmp, - rect.x + (m_usedImgSize.x - w) / 2 + imgSpacingLeft, - rect.y + (rect.height - h) / 2, - true); - } - - wxString text = GetString(item); - if (!text.empty()) - dc.DrawText(text, - rect.x + m_imgAreaWidth + 1, - rect.y + (rect.height - dc.GetCharHeight()) / 2); -} -#endif - - #ifdef _WIN32 int BitmapComboBox::Append(const wxString& item) @@ -166,19 +89,11 @@ int BitmapComboBox::Append(const wxString& item) //2. But then set width to 0 value for no using of bitmap left and right spacing //3. Set this empty bitmap to the at list one item and BitmapCombobox will be recreated correct -// wxBitmap bitmap(1, int(1.6 * wxGetApp().em_unit() + 1)); - wxBitmap bitmap(1, 16); - { - // bitmap.SetWidth(0); is depricated now - // so, use next code - bitmap.UnShare();// AllocExclusive(); - bitmap.GetGDIImageData()->m_width = 0; - } - + wxBitmapBundle bitmap = *get_empty_bmp_bundle(1, 16); OnAddBitmap(bitmap); + const int n = wxComboBox::Append(item); - if (n != wxNOT_FOUND) - DoSetItemBitmap(n, bitmap); + return n; } @@ -269,7 +184,6 @@ void BitmapComboBox::DrawBackground_(wxDC& dc, const wxRect& rect, int WXUNUSED( void BitmapComboBox::Rescale() { - return; // Next workaround: To correct scaling of a BitmapCombobox // we need to refill control with new bitmaps const wxString selection = this->GetValue(); diff --git a/src/slic3r/GUI/BitmapComboBox.hpp b/src/slic3r/GUI/BitmapComboBox.hpp index a77bf401d..545213fc3 100644 --- a/src/slic3r/GUI/BitmapComboBox.hpp +++ b/src/slic3r/GUI/BitmapComboBox.hpp @@ -29,28 +29,13 @@ BitmapComboBox(wxWindow* parent, #ifdef _WIN32 int Append(const wxString& item); #endif - int Append(const wxString& item, const wxBitmap& bitmap) + int Append(const wxString& item, const wxBitmapBundle& bitmap) { return wxBitmapComboBox::Append(item, bitmap); } protected: -#ifdef __APPLE__ -/* For PresetComboBox we use bitmaps that are created from images that are already scaled appropriately for Retina - * (Contrary to the intuition, the `scale` argument for Bitmap's constructor doesn't mean - * "please scale this to such and such" but rather - * "the wxImage is already sized for backing scale such and such". ) - * Unfortunately, the constructor changes the size of wxBitmap too. - * Thus We need to use unscaled size value for bitmaps that we use - * to avoid scaled size of control items. - * For this purpose control drawing methods and - * control size calculation methods (virtual) are overridden. - **/ -bool OnAddBitmap(const wxBitmap& bitmap) override; -void OnDrawItem(wxDC& dc, const wxRect& rect, int item, int flags) const override; -#endif - #ifdef _WIN32 bool MSWOnDraw(WXDRAWITEMSTRUCT* item) override; void DrawBackground_(wxDC& dc, const wxRect& rect, int WXUNUSED(item), int flags) const; diff --git a/src/slic3r/GUI/ButtonsDescription.cpp b/src/slic3r/GUI/ButtonsDescription.cpp index 2c5262d47..37daffd9d 100644 --- a/src/slic3r/GUI/ButtonsDescription.cpp +++ b/src/slic3r/GUI/ButtonsDescription.cpp @@ -17,9 +17,6 @@ void ButtonsDescription::FillSizerWithTextColorDescriptions(wxSizer* sizer, wxWi wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(3, 5, 5); sizer->Add(grid_sizer, 0, wxEXPAND); - ScalableBitmap bmp_delete = ScalableBitmap(parent, "cross"); - ScalableBitmap bmp_delete_focus = ScalableBitmap(parent, "cross_focus"); - auto add_color = [grid_sizer, parent](wxColourPickerCtrl** color_picker, const wxColour& color, const wxColour& def_color, wxString label_text) { // wrap the label_text to the max 80 characters if (label_text.Len() > 80) { diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 080de997e..b5da80e90 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1609,7 +1609,7 @@ ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) #ifndef __WXOSX__ SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX #endif //__WXOSX__ - SetMinSize(bg.bmp().GetSize()); + SetMinSize(bg.GetSize()); const wxSize size = GetTextExtent("m"); em_w = size.x; @@ -1734,8 +1734,8 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) wxPaintDC dc(this); - const auto bullet_w = bullet_black.bmp().GetSize().GetWidth(); - const auto bullet_h = bullet_black.bmp().GetSize().GetHeight(); + const auto bullet_w = bullet_black.GetWidth(); + const auto bullet_h = bullet_black.GetHeight(); const int yoff_icon = bullet_h < em_h ? (em_h - bullet_h) / 2 : 0; const int yoff_text = bullet_h > em_h ? (bullet_h - em_h) / 2 : 0; const int yinc = item_height(); @@ -1748,10 +1748,10 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) unsigned x = em_w/2 + item.indent * em_w; if (i == item_active || (item_hover >= 0 && i == (size_t)item_hover)) { - dc.DrawBitmap(bullet_blue.bmp(), x, y + yoff_icon, false); + dc.DrawBitmap(bullet_blue.get_bitmap(), x, y + yoff_icon, false); } - else if (i < item_active) { dc.DrawBitmap(bullet_black.bmp(), x, y + yoff_icon, false); } - else if (i > item_active) { dc.DrawBitmap(bullet_white.bmp(), x, y + yoff_icon, false); } + else if (i < item_active) { dc.DrawBitmap(bullet_black.get_bitmap(), x, y + yoff_icon, false); } + else if (i > item_active) { dc.DrawBitmap(bullet_white.get_bitmap(), x, y + yoff_icon, false); } x += + bullet_w + em_w/2; const auto text_size = dc.GetTextExtent(item.label); @@ -1763,9 +1763,9 @@ void ConfigWizardIndex::on_paint(wxPaintEvent & evt) } //draw logo - if (int y = size.y - bg.GetBmpHeight(); y>=0) { - dc.DrawBitmap(bg.bmp(), 0, y, false); - index_width = std::max(index_width, bg.GetBmpWidth() + em_w / 2); + if (int y = size.y - bg.GetHeight(); y>=0) { + dc.DrawBitmap(bg.get_bitmap(), 0, y, false); + index_width = std::max(index_width, bg.GetWidth() + em_w / 2); } if (GetMinSize().x < index_width) { @@ -1797,12 +1797,8 @@ void ConfigWizardIndex::msw_rescale() em_w = size.x; em_h = size.y; - bg.msw_rescale(); - SetMinSize(bg.bmp().GetSize()); + SetMinSize(bg.GetSize()); - bullet_black.msw_rescale(); - bullet_blue.msw_rescale(); - bullet_white.msw_rescale(); Refresh(); } diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 4de8381ff..aa074f925 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -519,7 +519,7 @@ private: ssize_t item_hover; size_t last_page; - int item_height() const { return std::max(bullet_black.bmp().GetSize().GetHeight(), em_w) + em_w; } + int item_height() const { return std::max(bullet_black.GetHeight(), em_w) + em_w; } void on_paint(wxPaintEvent &evt); void on_mouse_move(wxMouseEvent &evt); diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index dda50ec05..717af39ba 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -86,24 +86,24 @@ Control::Control( wxWindow *parent, m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_right") : ScalableBitmap(this, "thumb_up")); m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_left") : ScalableBitmap(this, "thumb_down")); - m_thumb_size = m_bmp_thumb_lower.GetBmpSize(); + m_thumb_size = m_bmp_thumb_lower.GetSize(); m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add"); m_bmp_add_tick_off = ScalableBitmap(this, "colorchange_add_f"); m_bmp_del_tick_on = ScalableBitmap(this, "colorchange_del"); m_bmp_del_tick_off = ScalableBitmap(this, "colorchange_del_f"); - m_tick_icon_dim = m_bmp_add_tick_on.GetBmpWidth(); + m_tick_icon_dim = m_bmp_add_tick_on.GetWidth(); m_bmp_one_layer_lock_on = ScalableBitmap(this, "lock_closed"); m_bmp_one_layer_lock_off = ScalableBitmap(this, "lock_closed_f"); m_bmp_one_layer_unlock_on = ScalableBitmap(this, "lock_open"); m_bmp_one_layer_unlock_off = ScalableBitmap(this, "lock_open_f"); - m_lock_icon_dim = m_bmp_one_layer_lock_on.GetBmpWidth(); + m_lock_icon_dim = m_bmp_one_layer_lock_on.GetWidth(); m_bmp_revert = ScalableBitmap(this, "undo"); - m_revert_icon_dim = m_bmp_revert.GetBmpWidth(); + m_revert_icon_dim = m_bmp_revert.GetWidth(); m_bmp_cog = ScalableBitmap(this, "cog"); - m_cog_icon_dim = m_bmp_cog.GetBmpWidth(); + m_cog_icon_dim = m_bmp_cog.GetWidth(); m_selection = ssUndef; m_ticks.set_pause_print_msg(_utf8(L("Place bearings in slots and resume printing"))); @@ -155,26 +155,11 @@ void Control::msw_rescale() { m_font = GUI::wxGetApp().normal_font(); - m_bmp_thumb_higher.msw_rescale(); - m_bmp_thumb_lower .msw_rescale(); - m_thumb_size = m_bmp_thumb_lower.bmp().GetSize(); - - m_bmp_add_tick_on .msw_rescale(); - m_bmp_add_tick_off.msw_rescale(); - m_bmp_del_tick_on .msw_rescale(); - m_bmp_del_tick_off.msw_rescale(); - m_tick_icon_dim = m_bmp_add_tick_on.bmp().GetSize().x; - - m_bmp_one_layer_lock_on .msw_rescale(); - m_bmp_one_layer_lock_off .msw_rescale(); - m_bmp_one_layer_unlock_on .msw_rescale(); - m_bmp_one_layer_unlock_off.msw_rescale(); - m_lock_icon_dim = m_bmp_one_layer_lock_on.bmp().GetSize().x; - - m_bmp_revert.msw_rescale(); - m_revert_icon_dim = m_bmp_revert.bmp().GetSize().x; - m_bmp_cog.msw_rescale(); - m_cog_icon_dim = m_bmp_cog.bmp().GetSize().x; + m_thumb_size = m_bmp_thumb_lower.GetSize(); + m_tick_icon_dim = m_bmp_add_tick_on.GetWidth(); + m_lock_icon_dim = m_bmp_one_layer_lock_on.GetWidth(); + m_revert_icon_dim = m_bmp_revert.GetWidth(); + m_cog_icon_dim = m_bmp_cog.GetWidth(); SLIDER_MARGIN = 4 + GUI::wxGetApp().em_unit(); @@ -189,22 +174,18 @@ void Control::sys_color_changed() { GUI::wxGetApp().UpdateDarkUI(GetParent()); - m_bmp_add_tick_on .msw_rescale(); - m_bmp_add_tick_off.msw_rescale(); - m_bmp_del_tick_on .msw_rescale(); - m_bmp_del_tick_off.msw_rescale(); - m_tick_icon_dim = m_bmp_add_tick_on.GetBmpWidth(); + m_bmp_add_tick_on .sys_color_changed(); + m_bmp_add_tick_off.sys_color_changed(); + m_bmp_del_tick_on .sys_color_changed(); + m_bmp_del_tick_off.sys_color_changed(); - m_bmp_one_layer_lock_on .msw_rescale(); - m_bmp_one_layer_lock_off .msw_rescale(); - m_bmp_one_layer_unlock_on .msw_rescale(); - m_bmp_one_layer_unlock_off.msw_rescale(); - m_lock_icon_dim = m_bmp_one_layer_lock_on.GetBmpWidth(); + m_bmp_one_layer_lock_on .sys_color_changed(); + m_bmp_one_layer_lock_off .sys_color_changed(); + m_bmp_one_layer_unlock_on .sys_color_changed(); + m_bmp_one_layer_unlock_off.sys_color_changed(); - m_bmp_revert.msw_rescale(); - m_revert_icon_dim = m_bmp_revert.GetBmpWidth(); - m_bmp_cog.msw_rescale(); - m_cog_icon_dim = m_bmp_cog.GetBmpWidth(); + m_bmp_revert.sys_color_changed(); + m_bmp_cog .sys_color_changed(); } int Control::GetActiveValue() const @@ -604,9 +585,12 @@ void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_ return; } - wxBitmap* icon = m_focus == fiActionIcon ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp(); + //wxBitmap* icon = m_focus == fiActionIcon ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp(); + //if (m_ticks.ticks.find(TickCode{tick}) != m_ticks.ticks.end()) + // icon = m_focus == fiActionIcon ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp(); + ScalableBitmap* icon = m_focus == fiActionIcon ? &m_bmp_add_tick_off : &m_bmp_add_tick_on; if (m_ticks.ticks.find(TickCode{tick}) != m_ticks.ticks.end()) - icon = m_focus == fiActionIcon ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp(); + icon = m_focus == fiActionIcon ? &m_bmp_del_tick_off : &m_bmp_del_tick_on; wxCoord x_draw, y_draw; is_horizontal() ? x_draw = pt_beg.x - 0.5*m_tick_icon_dim : y_draw = pt_beg.y - 0.5*m_tick_icon_dim; @@ -615,10 +599,12 @@ void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_ else is_horizontal() ? y_draw = pt_beg.y - m_tick_icon_dim-2 : x_draw = pt_end.x + 3; - if (m_draw_mode == dmSequentialFffPrint) - dc.DrawBitmap(create_scaled_bitmap("colorchange_add", nullptr, 16, true), x_draw, y_draw); + if (m_draw_mode == dmSequentialFffPrint) { + wxBitmap disabled_add = get_bmp_bundle("colorchange_add")->GetBitmapFor(this).ConvertToDisabled(); + dc.DrawBitmap(disabled_add, x_draw, y_draw); + } else - dc.DrawBitmap(*icon, x_draw, y_draw); + dc.DrawBitmap((*icon).get_bitmap(), x_draw, y_draw); //update rect of the tick action icon m_rect_tick_action = wxRect(x_draw, y_draw, m_tick_icon_dim, m_tick_icon_dim); @@ -851,7 +837,7 @@ void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider { wxCoord x_draw = pos.x - int(0.5 * m_thumb_size.x); wxCoord y_draw = pos.y - int(0.5 * m_thumb_size.y); - dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower.bmp() : m_bmp_thumb_higher.bmp(), x_draw, y_draw); + dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower.get_bitmap() : m_bmp_thumb_higher.get_bitmap(), x_draw, y_draw); // Update thumb rect update_thumb_rect(x_draw, y_draw, selection); @@ -945,12 +931,12 @@ void Control::draw_ticks(wxDC& dc) // Draw icon for "Pause print", "Custom Gcode" or conflict tick if (!icon_name.empty()) { - wxBitmap icon = create_scaled_bitmap(icon_name); + wxBitmapBundle* icon = get_bmp_bundle(icon_name); wxCoord x_draw, y_draw; is_horizontal() ? x_draw = pos - 0.5 * m_tick_icon_dim : y_draw = pos - 0.5 * m_tick_icon_dim; is_horizontal() ? y_draw = mid + 22 : x_draw = mid + m_thumb_size.x + 3; - dc.DrawBitmap(icon, x_draw, y_draw); + dc.DrawBitmap(icon->GetBitmapFor(this), x_draw, y_draw); } } } @@ -1262,9 +1248,12 @@ void Control::draw_one_layer_icon(wxDC& dc) if (m_draw_mode == dmSequentialGCodeView) return; - const wxBitmap& icon = m_is_one_layer ? - m_focus == fiOneLayerIcon ? m_bmp_one_layer_lock_off.bmp() : m_bmp_one_layer_lock_on.bmp() : - m_focus == fiOneLayerIcon ? m_bmp_one_layer_unlock_off.bmp() : m_bmp_one_layer_unlock_on.bmp(); + //const wxBitmap& icon = m_is_one_layer ? + // m_focus == fiOneLayerIcon ? m_bmp_one_layer_lock_off.bmp() : m_bmp_one_layer_lock_on.bmp() : + // m_focus == fiOneLayerIcon ? m_bmp_one_layer_unlock_off.bmp() : m_bmp_one_layer_unlock_on.bmp(); + const ScalableBitmap& icon = m_is_one_layer ? + m_focus == fiOneLayerIcon ? m_bmp_one_layer_lock_off : m_bmp_one_layer_lock_on : + m_focus == fiOneLayerIcon ? m_bmp_one_layer_unlock_off : m_bmp_one_layer_unlock_on; int width, height; get_size(&width, &height); @@ -1273,7 +1262,7 @@ void Control::draw_one_layer_icon(wxDC& dc) is_horizontal() ? x_draw = width-2 : x_draw = 0.5*width - 0.5*m_lock_icon_dim; is_horizontal() ? y_draw = 0.5*height - 0.5*m_lock_icon_dim : y_draw = height-2; - dc.DrawBitmap(icon, x_draw, y_draw); + dc.DrawBitmap(icon.bmp().GetBitmapFor(this), x_draw, y_draw); //update rect of the lock/unlock icon m_rect_one_layer_icon = wxRect(x_draw, y_draw, m_lock_icon_dim, m_lock_icon_dim); @@ -1291,7 +1280,7 @@ void Control::draw_revert_icon(wxDC& dc) is_horizontal() ? x_draw = width-2 : x_draw = 0.25*SLIDER_MARGIN; is_horizontal() ? y_draw = 0.25*SLIDER_MARGIN: y_draw = height-2; - dc.DrawBitmap(m_bmp_revert.bmp(), x_draw, y_draw); + dc.DrawBitmap(m_bmp_revert.get_bitmap(), x_draw, y_draw); //update rect of the lock/unlock icon m_rect_revert_icon = wxRect(x_draw, y_draw, m_revert_icon_dim, m_revert_icon_dim); @@ -1315,7 +1304,7 @@ void Control::draw_cog_icon(wxDC& dc) is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height - 2; } - dc.DrawBitmap(m_bmp_cog.bmp(), x_draw, y_draw); + dc.DrawBitmap(m_bmp_cog.get_bitmap(), x_draw, y_draw); //update rect of the lock/unlock icon m_rect_cog_icon = wxRect(x_draw, y_draw, m_cog_icon_dim, m_cog_icon_dim); @@ -1673,7 +1662,7 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current if (extruders_cnt > 1) { std::array active_extruders = get_active_extruders_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value); - std::vector icons = get_extruder_color_icons(true); + std::vector icons = get_extruder_color_icons(true); wxMenu* change_extruder_menu = new wxMenu(); @@ -1684,7 +1673,7 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current if (m_mode == MultiAsSingle) append_menu_item(change_extruder_menu, wxID_ANY, item_name, "", - [this, i](wxCommandEvent&) { add_code_as_tick(ToolChange, i); }, *icons[i-1], menu, + [this, i](wxCommandEvent&) { add_code_as_tick(ToolChange, i); }, icons[i-1], menu, [is_active_extruder]() { return !is_active_extruder; }, GUI::wxGetApp().plater()); } @@ -1722,7 +1711,7 @@ void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_curren format_wxstr(_L("Switch code to Color change (%1%) for:"), gcode(ColorChange)) : format_wxstr(_L("Add color change (%1%) for:"), gcode(ColorChange)); wxMenuItem* add_color_change_menu_item = menu->AppendSubMenu(add_color_change_menu, menu_name, ""); - add_color_change_menu_item->SetBitmap(create_menu_bitmap("colorchange_add_m")); + add_color_change_menu_item->SetBitmap(*get_bmp_bundle("colorchange_add_m")); } } diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index d72e1dd32..9bccb6b63 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -297,7 +297,7 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR if (can_create_editor_ctrl && !can_create_editor_ctrl()) return nullptr; - std::vector icons = get_extruder_color_icons(); + std::vector icons = get_extruder_color_icons(); if (icons.empty()) return nullptr; diff --git a/src/slic3r/GUI/ExtruderSequenceDialog.cpp b/src/slic3r/GUI/ExtruderSequenceDialog.cpp index 42313636e..e1c6a7ce0 100644 --- a/src/slic3r/GUI/ExtruderSequenceDialog.cpp +++ b/src/slic3r/GUI/ExtruderSequenceDialog.cpp @@ -264,9 +264,6 @@ void ExtruderSequenceDialog::on_dpi_changed(const wxRect& suggested_rect) { SetFont(wxGetApp().normal_font()); - m_bmp_add.msw_rescale(); - m_bmp_del.msw_rescale(); - const int em = em_unit(); m_intervals_grid_sizer->SetHGap(em); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index a9812abf2..95caa8ed3 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -106,14 +106,14 @@ public: bool set_undo_to_sys_tooltip(const wxString* tip) { return m_undo_ui.set_undo_to_sys_tooltip(tip); } // ui items used for revert line value - bool has_undo_ui() const { return m_undo_ui.undo_bitmap != nullptr; } - const wxBitmap& undo_bitmap() const { return m_undo_ui.undo_bitmap->bmp(); } - const wxString* undo_tooltip() const { return m_undo_ui.undo_tooltip; } - const wxBitmap& undo_to_sys_bitmap() const { return m_undo_ui.undo_to_sys_bitmap->bmp(); } - const wxString* undo_to_sys_tooltip() const { return m_undo_ui.undo_to_sys_tooltip; } - const wxColour* label_color() const { return m_undo_ui.label_color; } - const bool blink() const { return m_undo_ui.blink; } - bool* get_blink_ptr() { return &m_undo_ui.blink; } + bool has_undo_ui() const { return m_undo_ui.undo_bitmap != nullptr; } + const wxBitmapBundle& undo_bitmap() const { return m_undo_ui.undo_bitmap->bmp(); } + const wxString* undo_tooltip() const { return m_undo_ui.undo_tooltip; } + const wxBitmapBundle& undo_to_sys_bitmap() const { return m_undo_ui.undo_to_sys_bitmap->bmp(); } + const wxString* undo_to_sys_tooltip() const { return m_undo_ui.undo_to_sys_tooltip; } + const wxColour* label_color() const { return m_undo_ui.label_color; } + const bool blink() const { return m_undo_ui.blink; } + bool* get_blink_ptr() { return &m_undo_ui.blink; } }; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 4b0544488..0412bca21 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -331,7 +331,7 @@ private: // See https://github.com/wxWidgets/wxWidgets/blob/master/src/msw/font.cpp // void wxNativeFontInfo::SetFractionalPointSize(float pointSizeNew) wxNativeFontInfo nfi= *font.GetNativeFontInfo(); - float pointSizeNew = scale * font.GetPointSize(); + float pointSizeNew = wxDisplay(this).GetScaleFactor() * scale * font.GetPointSize(); nfi.lf.lfHeight = nfi.GetLogFontHeightAtPPI(pointSizeNew, get_dpi_for_window(this)); nfi.pointSize = pointSizeNew; font = wxFont(nfi); @@ -1179,7 +1179,7 @@ bool GUI_App::on_init_inner() } // create splash screen with updated bmp - scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("PrusaSlicer", nullptr, 400), + scrn = new SplashScreen(bmp.IsOk() ? bmp : get_bmp_bundle("PrusaSlicer", 400)->GetPreferredBitmapSizeAtScale(1.0), wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos); if (!default_splashscreen_pos) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 4adb161c2..6a3dad5f4 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -142,19 +142,11 @@ std::map SettingsFactory::CATEGORY_ICON = { L("Hollowing") , "hollowing" } }; -//wxBitmap SettingsFactory::get_category_bitmap(const std::string& category_name, bool menu_bmp /*= true*/) -wxBitmap SettingsFactory::get_category_bitmap_(const std::string& category_name, bool menu_bmp /*= true*/) +wxBitmapBundle* SettingsFactory::get_category_bitmap(const std::string& category_name) { if (CATEGORY_ICON.find(category_name) == CATEGORY_ICON.end()) - return wxNullBitmap; - return /*menu_bmp ? create_menu_bitmap(CATEGORY_ICON.at(category_name)) : */create_scaled_bitmap(CATEGORY_ICON.at(category_name)); -} - -wxBitmapBundle SettingsFactory::get_category_bitmap(const std::string& category_name) -{ - if (CATEGORY_ICON.find(category_name) == CATEGORY_ICON.end()) - return wxNullBitmap; - return create_menu_bitmap(CATEGORY_ICON.at(category_name)); + return get_bmp_bundle("empty"); + return get_bmp_bundle(CATEGORY_ICON.at(category_name)); } //------------------------------------- @@ -437,13 +429,12 @@ static void create_freq_settings_popupmenu(wxMenu* menu, const bool is_object_se #endif } -std::vector MenuFactory::get_volume_bitmaps() +std::vector MenuFactory::get_volume_bitmaps() { - std::vector volume_bmps; + std::vector volume_bmps; volume_bmps.reserve(ADD_VOLUME_MENU_ITEMS.size()); for (auto item : ADD_VOLUME_MENU_ITEMS) -// volume_bmps.push_back(create_menu_bitmap(item.second)); - volume_bmps.push_back(create_scaled_bitmap(item.second, nullptr, 16, false, "", true)); + volume_bmps.push_back(get_bmp_bundle(item.second)); return volume_bmps; } @@ -623,7 +614,7 @@ wxMenuItem* MenuFactory::append_menu_item_settings(wxMenu* menu_) // Add full settings list auto menu_item = new wxMenuItem(menu, wxID_ANY, menu_name); - menu_item->SetBitmap(create_menu_bitmap("cog")); + menu_item->SetBitmap(*get_bmp_bundle("cog")); menu_item->SetSubMenu(create_settings_popupmenu(menu, is_object_settings, item)); return menu->Append(menu_item); @@ -768,7 +759,7 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu) return; } - std::vector icons = get_extruder_color_icons(true); + std::vector icons = get_extruder_color_icons(true); wxMenu* extruder_selection_menu = new wxMenu(); const wxString& name = sels.Count() == 1 ? names[0] : names[1]; @@ -787,7 +778,7 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu) (is_active_extruder ? " (" + _L("active") + ")" : ""); append_menu_item(extruder_selection_menu, wxID_ANY, item_name, "", - [i](wxCommandEvent&) { obj_list()->set_extruder_for_selected_items(i); }, *icons[icon_idx], menu, + [i](wxCommandEvent&) { obj_list()->set_extruder_for_selected_items(i); }, icons[icon_idx], menu, [is_active_extruder]() { return !is_active_extruder; }, m_parent); } @@ -1147,12 +1138,6 @@ void MenuFactory::update_default_menu() create_default_menu(); } -void MenuFactory::msw_rescale() -{ - for (MenuWithSeparators* menu : { &m_object_menu, &m_sla_object_menu, &m_part_menu, &m_default_menu }) - msw_rescale_menu(dynamic_cast(menu)); -} - #ifdef _WIN32 // For this class is used code from stackoverflow: // https://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence @@ -1182,7 +1167,7 @@ static void update_menu_item_def_colors(T* item) void MenuFactory::sys_color_changed() { for (MenuWithSeparators* menu : { &m_object_menu, &m_sla_object_menu, &m_part_menu, &m_default_menu }) { - msw_rescale_menu(dynamic_cast(menu));// msw_rescale_menu updates just icons, so use it + sys_color_changed_menu(dynamic_cast(menu));// msw_rescale_menu updates just icons, so use it #ifdef _WIN32 // but under MSW we have to update item's bachground color for (wxMenuItem* item : menu->GetMenuItems()) @@ -1195,14 +1180,17 @@ void MenuFactory::sys_color_changed(wxMenuBar* menubar) { for (size_t id = 0; id < menubar->GetMenuCount(); id++) { wxMenu* menu = menubar->GetMenu(id); - msw_rescale_menu(menu); + sys_color_changed_menu(menu); +#ifndef __linux__ + menu->SetupBitmaps(); #ifdef _WIN32 // but under MSW we have to update item's bachground color for (wxMenuItem* item : menu->GetMenuItems()) update_menu_item_def_colors(item); +#endif #endif } - menubar->Refresh(); +// menubar->Refresh(); } diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index cba828c29..bbbc00d42 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -25,9 +25,7 @@ struct SettingsFactory typedef std::map> Bundle; static std::map CATEGORY_ICON; -// static wxBitmap get_category_bitmap(const std::string& category_name, bool menu_bmp = true); - static wxBitmap get_category_bitmap_(const std::string& category_name, bool menu_bmp = true); - static wxBitmapBundle get_category_bitmap(const std::string& category_name); + static wxBitmapBundle* get_category_bitmap(const std::string& category_name); static Bundle get_bundle(const DynamicPrintConfig* config, bool is_object_settings); static std::vector get_options(bool is_part); }; @@ -36,7 +34,7 @@ class MenuFactory { public: static const std::vector> ADD_VOLUME_MENU_ITEMS; - static std::vector get_volume_bitmaps(); + static std::vector get_volume_bitmaps(); MenuFactory(); ~MenuFactory() = default; @@ -45,7 +43,6 @@ public: void update(); void update_object_menu(); void update_default_menu(); - void msw_rescale(); void sys_color_changed(); static void sys_color_changed(wxMenuBar* menu_bar); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index b7ff8e48f..437a526af 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -234,47 +234,47 @@ void ObjectLayers::UpdateAndShow(const bool show) void ObjectLayers::msw_rescale() { - m_bmp_delete.msw_rescale(); - m_bmp_add.msw_rescale(); + //m_bmp_delete.msw_rescale(); + //m_bmp_add.msw_rescale(); - m_grid_sizer->SetHGap(wxGetApp().em_unit()); + //m_grid_sizer->SetHGap(wxGetApp().em_unit()); - // rescale edit-boxes - const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount(); - for (int i = 0; i < cells_cnt; ++i) { - const wxSizerItem* item = m_grid_sizer->GetItem(i); - if (item->IsWindow()) { - LayerRangeEditor* editor = dynamic_cast(item->GetWindow()); - if (editor != nullptr) - editor->msw_rescale(); - } - else if (item->IsSizer()) // case when we have editor with buttons - { - wxSizerItem* e_item = item->GetSizer()->GetItem(size_t(0)); // editor - if (e_item->IsWindow()) { - LayerRangeEditor* editor = dynamic_cast(e_item->GetWindow()); - if (editor != nullptr) - editor->msw_rescale(); - } + //// rescale edit-boxes + //const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount(); + //for (int i = 0; i < cells_cnt; ++i) { + // const wxSizerItem* item = m_grid_sizer->GetItem(i); + // if (item->IsWindow()) { + // LayerRangeEditor* editor = dynamic_cast(item->GetWindow()); + // if (editor != nullptr) + // editor->msw_rescale(); + // } + // else if (item->IsSizer()) // case when we have editor with buttons + // { + // wxSizerItem* e_item = item->GetSizer()->GetItem(size_t(0)); // editor + // if (e_item->IsWindow()) { + // LayerRangeEditor* editor = dynamic_cast(e_item->GetWindow()); + // if (editor != nullptr) + // editor->msw_rescale(); + // } - if (item->GetSizer()->GetItemCount() > 2) // if there are Add/Del buttons - for (size_t btn : {2, 3}) { // del_btn, add_btn - wxSizerItem* b_item = item->GetSizer()->GetItem(btn); - if (b_item->IsWindow()) { - auto button = dynamic_cast(b_item->GetWindow()); - if (button != nullptr) - button->msw_rescale(); - } - } - } - } + // if (item->GetSizer()->GetItemCount() > 2) // if there are Add/Del buttons + // for (size_t btn : {2, 3}) { // del_btn, add_btn + // wxSizerItem* b_item = item->GetSizer()->GetItem(btn); + // if (b_item->IsWindow()) { + // auto button = dynamic_cast(b_item->GetWindow()); + // if (button != nullptr) + // button->msw_rescale(); + // } + // } + // } + //} m_grid_sizer->Layout(); } void ObjectLayers::sys_color_changed() { - m_bmp_delete.msw_rescale(); - m_bmp_add.msw_rescale(); + m_bmp_delete.sys_color_changed(); + m_bmp_add.sys_color_changed(); // rescale edit-boxes const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount(); @@ -286,7 +286,7 @@ void ObjectLayers::sys_color_changed() if (b_item->IsWindow()) { auto button = dynamic_cast(b_item->GetWindow()); if (button != nullptr) - button->msw_rescale(); + button->sys_color_changed(); } } } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index bfedd8e1e..1171149b6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4239,9 +4239,6 @@ void ObjectList::msw_rescale() GetColumn(colExtruder)->SetWidth( 8 * em); GetColumn(colEditing )->SetWidth( 3 * em); - // rescale/update existing items with bitmaps - m_objects_model->Rescale(); - Layout(); } @@ -4249,7 +4246,10 @@ void ObjectList::sys_color_changed() { wxGetApp().UpdateDVCDarkUI(this, true); - msw_rescale(); + // update existing items with bitmaps + m_objects_model->UpdateBitmaps(); + + Layout(); } void ObjectList::ItemValueChanged(wxDataViewEvent &event) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 24ae01389..a538f2b33 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -126,7 +126,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : // Load bitmaps to be used for the mirroring buttons: m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on"); m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off"); - m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png"); + m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent"); const int border = wxOSX ? 0 : 4; const int em = wxGetApp().em_unit(); @@ -1009,7 +1009,7 @@ void ObjectManipulation::update_warning_icon_state(const MeshErrorsInfo& warning m_manifold_warning_bmp = ScalableBitmap(m_parent, warning_icon_name); const wxString& tooltip = warning.tooltip; m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); - m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0,0) : m_manifold_warning_bmp.bmp().GetSize()); + m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0,0) : m_manifold_warning_bmp.GetSize()); m_fix_throught_netfab_bitmap->SetToolTip(tooltip); } @@ -1336,25 +1336,10 @@ void ObjectManipulation::msw_rescale() m_item_name->SetMinSize(wxSize(20*em, wxDefaultCoord)); msw_rescale_word_local_combo(m_word_local_combo); m_word_local_combo_sizer->SetMinSize(wxSize(-1, m_word_local_combo->GetBestHeight(-1))); - m_manifold_warning_bmp.msw_rescale(); const wxString& tooltip = m_fix_throught_netfab_bitmap->GetToolTipText(); m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); - m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0, 0) : m_manifold_warning_bmp.bmp().GetSize()); - - m_mirror_bitmap_on.msw_rescale(); - m_mirror_bitmap_off.msw_rescale(); - m_mirror_bitmap_hidden.msw_rescale(); - m_reset_scale_button->msw_rescale(); - m_reset_rotation_button->msw_rescale(); -#if ENABLE_WORLD_COORDINATE - m_reset_skew_button->msw_rescale(); -#endif /// ENABLE_WORLD_COORDINATE - m_drop_to_bed_button->msw_rescale(); - m_lock_bnt->msw_rescale(); - - for (int id = 0; id < 3; ++id) - m_mirror_buttons[id].first->msw_rescale(); + m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0, 0) : m_manifold_warning_bmp.GetSize()); // rescale label-heights // Text trick to grid sizer layout: @@ -1383,20 +1368,16 @@ void ObjectManipulation::sys_color_changed() for (ManipulationEditor* editor : m_editors) editor->sys_color_changed(this); - // btn...->msw_rescale() updates icon on button, so use it - m_mirror_bitmap_on.msw_rescale(); - m_mirror_bitmap_off.msw_rescale(); - m_mirror_bitmap_hidden.msw_rescale(); - m_reset_scale_button->msw_rescale(); - m_reset_rotation_button->msw_rescale(); -#if ENABLE_WORLD_COORDINATE - m_reset_skew_button->msw_rescale(); -#endif // ENABLE_WORLD_COORDINATE - m_drop_to_bed_button->msw_rescale(); - m_lock_bnt->msw_rescale(); + m_mirror_bitmap_on.sys_color_changed(); + m_mirror_bitmap_off.sys_color_changed(); + m_mirror_bitmap_hidden.sys_color_changed(); + 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(); for (int id = 0; id < 3; ++id) - m_mirror_buttons[id].first->msw_rescale(); + m_mirror_buttons[id].first->sys_color_changed(); } #if ENABLE_WORLD_COORDINATE diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 291013fe9..97eb5f10d 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -99,7 +99,7 @@ bool ObjectSettings::update_settings_list() btn->SetToolTip(_(L("Remove parameter"))); btn->SetBitmapFocus(m_bmp_delete_focus.bmp()); - btn->SetBitmapHover(m_bmp_delete_focus.bmp()); + btn->SetBitmapCurrent(m_bmp_delete_focus.bmp()); btn->Bind(wxEVT_BUTTON, [opt_key, config, this](wxEvent &event) { wxGetApp().plater()->take_snapshot(from_u8((boost::format(_utf8(L("Delete Option %s"))) % opt_key).str())); @@ -133,7 +133,7 @@ bool ObjectSettings::update_settings_list() return; ctrl->SetBitmap_(m_bmp_delete); ctrl->SetBitmapFocus(m_bmp_delete_focus.bmp()); - ctrl->SetBitmapHover(m_bmp_delete_focus.bmp()); + ctrl->SetBitmapCurrent(m_bmp_delete_focus.bmp()); }; const bool is_extruders_cat = cat.first == "Extruders"; @@ -268,15 +268,6 @@ void ObjectSettings::UpdateAndShow(const bool show) OG_Settings::UpdateAndShow(show ? update_settings_list() : false); } -void ObjectSettings::msw_rescale() -{ - m_bmp_delete.msw_rescale(); - m_bmp_delete_focus.msw_rescale(); - - for (auto group : m_og_settings) - group->msw_rescale(); -} - void ObjectSettings::sys_color_changed() { m_og->sys_color_changed(); diff --git a/src/slic3r/GUI/GUI_ObjectSettings.hpp b/src/slic3r/GUI/GUI_ObjectSettings.hpp index e5a6937f1..5d0be1308 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.hpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.hpp @@ -56,7 +56,6 @@ public: bool add_missed_options(ModelConfig *config_to, const DynamicPrintConfig &config_from); void update_config_values(ModelConfig *config); void UpdateAndShow(const bool show) override; - void msw_rescale(); void sys_color_changed(); }; diff --git a/src/slic3r/GUI/GalleryDialog.cpp b/src/slic3r/GUI/GalleryDialog.cpp index 975b807dc..6be6a94fe 100644 --- a/src/slic3r/GUI/GalleryDialog.cpp +++ b/src/slic3r/GUI/GalleryDialog.cpp @@ -157,8 +157,9 @@ bool GalleryDialog::can_change_thumbnail() void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect) { - const int& em = em_unit(); + update(); + const int& em = em_unit(); msw_buttons_rescale(this, em, { ID_BTN_ADD_CUSTOM_SHAPE, ID_BTN_DEL_CUSTOM_SHAPE, ID_BTN_REPLACE_CUSTOM_PNG, wxID_OK, wxID_CLOSE }); wxSize size = wxSize(50 * em, 35 * em); @@ -169,13 +170,14 @@ void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect) Refresh(); } -static void add_lock(wxImage& image) +static void add_lock(wxImage& image, wxWindow* parent_win) { - int lock_sz = 22; + wxBitmapBundle* bmp_bndl = get_bmp_bundle("lock", 22); #ifdef __APPLE__ - lock_sz /= mac_max_scaling_factor(); + wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetDefaultSize() * mac_max_scaling_factor()); +#else + wxBitmap bmp = bmp_bndl->GetBitmapFor(parent_win); #endif - wxBitmap bmp = create_scaled_bitmap("lock", nullptr, lock_sz); wxImage lock_image = bmp.ConvertToImage(); if (!lock_image.IsOk() || lock_image.GetWidth() == 0 || lock_image.GetHeight() == 0) @@ -213,21 +215,28 @@ static void add_lock(wxImage& image) } } -static void add_default_image(wxImageList* img_list, bool is_system) +static void add_default_image(wxImageList* img_list, bool is_system, wxWindow* parent_win) { - int sz = IMG_PX_CNT; + wxBitmapBundle* bmp_bndl = get_bmp_bundle("cog", IMG_PX_CNT); #ifdef __APPLE__ - sz /= mac_max_scaling_factor(); + wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetDefaultSize() * mac_max_scaling_factor()); +#else + wxBitmap bmp = bmp_bndl->GetBitmapFor(parent_win); #endif - wxBitmap bmp = create_scaled_bitmap("cog", nullptr, sz, true); + bmp = bmp.ConvertToDisabled(); if (is_system) { wxImage image = bmp.ConvertToImage(); if (image.IsOk() && image.GetWidth() != 0 && image.GetHeight() != 0) { - add_lock(image); + add_lock(image, parent_win); +#ifdef __APPLE__ + bmp = wxBitmap(std::move(image), -1, mac_max_scaling_factor()); +#else bmp = wxBitmap(std::move(image)); +#endif } } + img_list->Add(bmp); }; @@ -344,8 +353,13 @@ void GalleryDialog::load_label_icon_list() // Make an image list containing large icons +#ifdef __APPLE__ + m_image_list = new wxImageList(IMG_PX_CNT, IMG_PX_CNT); + int px_cnt = IMG_PX_CNT * mac_max_scaling_factor(); +#else int px_cnt = (int)(em_unit() * IMG_PX_CNT * 0.1f + 0.5f); m_image_list = new wxImageList(px_cnt, px_cnt); +#endif std::string ext = ".png"; @@ -364,7 +378,7 @@ void GalleryDialog::load_label_icon_list() if (can_generate_thumbnail) generate_thumbnail_from_model(model_name); else { - add_default_image(m_image_list, item.is_system); + add_default_image(m_image_list, item.is_system, this); continue; } } @@ -373,14 +387,18 @@ void GalleryDialog::load_label_icon_list() if (!image.CanRead(from_u8(img_name)) || !image.LoadFile(from_u8(img_name), wxBITMAP_TYPE_PNG) || image.GetWidth() == 0 || image.GetHeight() == 0) { - add_default_image(m_image_list, item.is_system); + add_default_image(m_image_list, item.is_system, this); continue; } image.Rescale(px_cnt, px_cnt, wxIMAGE_QUALITY_BILINEAR); if (item.is_system) - add_lock(image); + add_lock(image, this); +#ifdef __APPLE__ + wxBitmap bmp = wxBitmap(std::move(image), -1, mac_max_scaling_factor()); +#else wxBitmap bmp = wxBitmap(std::move(image)); +#endif m_image_list->Add(bmp); } diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index a749ad405..bf4fe9dc7 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -270,9 +270,7 @@ wxPanel* KBShortcutsDialog::create_header(wxWindow* parent, const wxFont& bold_f sizer->AddStretchSpacer(); // logo - //m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 32); - //m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, m_logo_bmp.bmp()); - m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, wxBitmapBundle::FromSVGFile(Slic3r::var(wxGetApp().logo_name() + ".svg"), wxSize(32, 32))); + m_header_bitmap = new wxStaticBitmap(panel, wxID_ANY, *get_bmp_bundle(wxGetApp().logo_name(), 32)); sizer->Add(m_header_bitmap, 0, wxEXPAND | wxLEFT | wxRIGHT, 10); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 3d80954bd..bf1f67994 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1014,9 +1014,6 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) for (auto tab : wxGetApp().tabs_list) tab->msw_rescale(); - for (size_t id = 0; id < m_menubar->GetMenuCount(); id++) - msw_rescale_menu(m_menubar->GetMenu(id)); - // Workarounds for correct Window rendering after rescale /* Even if Window is maximized during moving, @@ -1051,7 +1048,7 @@ void MainFrame::on_sys_color_changed() #ifdef _MSW_DARK_MODE // update common mode sizer if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->Rescale(); + dynamic_cast(m_tabpanel)->OnColorsChanged(); #endif #endif @@ -1608,9 +1605,9 @@ void MainFrame::update_menubar() m_changeable_menu_items[miSend] ->SetItemLabel((is_fff ? _L("S&end G-code") : _L("S&end to print")) + dots + "\tCtrl+Shift+G"); m_changeable_menu_items[miMaterialTab] ->SetItemLabel((is_fff ? _L("&Filament Settings Tab") : _L("Mate&rial Settings Tab")) + "\tCtrl+3"); - m_changeable_menu_items[miMaterialTab] ->SetBitmap(create_menu_bitmap(is_fff ? "spool" : "resin")); + m_changeable_menu_items[miMaterialTab] ->SetBitmap(*get_bmp_bundle(is_fff ? "spool" : "resin")); - m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_menu_bitmap(is_fff ? "printer" : "sla_printer")); + m_changeable_menu_items[miPrinterTab] ->SetBitmap(*get_bmp_bundle(is_fff ? "printer" : "sla_printer")); } #if 0 diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 76bcfdd4a..43e13841c 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -99,17 +99,10 @@ void MsgDialog::apply_style(long style) if (style & wxNO) add_button(wxID_NO, (style & wxNO_DEFAULT)); if (style & wxCANCEL) add_button(wxID_CANCEL, (style & wxCANCEL_DEFAULT)); -#if 0 - logo->SetBitmap( create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" : - style & wxICON_INFORMATION ? "info" : - style & wxICON_QUESTION ? "question" : "PrusaSlicer", this, 64, style & wxICON_ERROR)); -#else std::string icon_name = style & wxICON_WARNING ? "exclamation" : style & wxICON_INFORMATION ? "info" : style & wxICON_QUESTION ? "question" : "PrusaSlicer"; - icon_name += ".svg"; - logo->SetBitmap(wxBitmapBundle::FromSVGFile(Slic3r::var(icon_name), wxSize(64, 64))); -#endif + logo->SetBitmap(*get_bmp_bundle(icon_name, 64)); } void MsgDialog::finalize() @@ -238,7 +231,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_ add_msg_content(this, content_sizer, msg, monospaced_font); // Use a small bitmap with monospaced font, as the error text will not be wrapped. - logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/84)); + logo->SetBitmap(*get_bmp_bundle("PrusaSlicer_192px_grayscale.png", monospaced_font ? 48 : /*1*/84)); SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit())); diff --git a/src/slic3r/GUI/Notebook.cpp b/src/slic3r/GUI/Notebook.cpp index 9c5ccb834..380b402d5 100644 --- a/src/slic3r/GUI/Notebook.cpp +++ b/src/slic3r/GUI/Notebook.cpp @@ -95,10 +95,6 @@ void ButtonsListCtrl::UpdateMode() void ButtonsListCtrl::Rescale() { - m_mode_sizer->msw_rescale(); - for (ScalableButton* btn : m_pageButtons) - btn->msw_rescale(); - int em = em_unit(this); m_btn_margin = std::lround(0.3 * em); m_line_margin = std::lround(0.1 * em); @@ -108,6 +104,14 @@ void ButtonsListCtrl::Rescale() m_sizer->Layout(); } +void ButtonsListCtrl::OnColorsChanged() +{ + for (ScalableButton* btn : m_pageButtons) + btn->sys_color_changed(); + + m_sizer->Layout(); +} + void ButtonsListCtrl::SetSelection(int sel) { if (m_selection == sel) diff --git a/src/slic3r/GUI/Notebook.hpp b/src/slic3r/GUI/Notebook.hpp index af03a6a08..bd6c5d85a 100644 --- a/src/slic3r/GUI/Notebook.hpp +++ b/src/slic3r/GUI/Notebook.hpp @@ -21,6 +21,7 @@ public: void SetSelection(int sel); void UpdateMode(); void Rescale(); + void OnColorsChanged(); 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; @@ -245,6 +246,11 @@ public: GetBtnsListCtrl()->Rescale(); } + void OnColorsChanged() + { + GetBtnsListCtrl()->OnColorsChanged(); + } + void OnNavigationKey(wxNavigationKeyEvent& event) { if (event.IsWindowChange()) { diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index 400db751a..c202de5e2 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -19,12 +19,12 @@ static bool is_point_in_rect(const wxPoint& pt, const wxRect& rect) rect.GetTop() <= pt.y && pt.y <= rect.GetBottom(); } -static wxSize get_bitmap_size(const wxBitmap& bmp) +static wxSize get_bitmap_size(const wxBitmapBundle* bmp, wxWindow* parent) { #ifdef __APPLE__ - return bmp.GetScaledSize(); + return bmp->GetDefaultSize(); #else - return bmp.GetSize(); + return bmp->GetBitmapFor(parent).GetSize(); #endif } @@ -45,8 +45,8 @@ 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(create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12)); - m_bmp_blinking_sz = get_bitmap_size(create_scaled_bitmap("search_blink", this)); + m_bmp_mode_sz = get_bitmap_size(get_bmp_bundle("mode_simple", wxOSX ? 10 : 12), this); + m_bmp_blinking_sz = get_bitmap_size(get_bmp_bundle("search_blink"), this); init_ctrl_lines();// from og.lines() @@ -416,8 +416,8 @@ 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 = create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12).GetSize(); - m_bmp_blinking_sz = create_scaled_bitmap("search_blink", this).GetSize(); + m_bmp_mode_sz = get_bitmap_size(get_bmp_bundle("mode_simple", wxOSX ? 10 : 12), this); + m_bmp_blinking_sz = get_bitmap_size(get_bmp_bundle("search_blink"), this); m_max_win_width = 0; @@ -497,7 +497,7 @@ void OG_CustomCtrl::CtrlLine::msw_rescale() { // if we have a single option with no label, no sidetext if (draw_just_act_buttons) - height = get_bitmap_size(create_scaled_bitmap("empty")).GetHeight(); + height = get_bitmap_size(get_bmp_bundle("empty"), ctrl).GetHeight(); if (ctrl->opt_group->label_width != 0 && !og_line.label.IsEmpty()) { wxSize label_sz = ctrl->GetTextExtent(og_line.label); @@ -666,13 +666,13 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_mode_bmp(wxDC& dc, wxCoord v_pos) 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"; - wxBitmap bmp = create_scaled_bitmap(bmp_name, ctrl, wxOSX ? 10 : 12); - wxCoord y_draw = v_pos + lround((height - get_bitmap_size(bmp).GetHeight()) / 2); + wxBitmapBundle* bmp = get_bmp_bundle(bmp_name, wxOSX ? 10 : 12); + 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) - dc.DrawBitmap(bmp, 0, y_draw); + dc.DrawBitmap(bmp->GetBitmapFor(ctrl), 0, y_draw); - return get_bitmap_size(bmp).GetWidth() + ctrl->m_h_gap; + return get_bitmap_size(bmp, ctrl).GetWidth() + ctrl->m_h_gap; } wxCoord OG_CustomCtrl::CtrlLine::draw_text(wxDC& dc, wxPoint pos, const wxString& text, const wxColour* color, int width, bool is_url/* = false*/) @@ -734,33 +734,33 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_text(wxDC& dc, wxPoint pos, const wxStr wxPoint OG_CustomCtrl::CtrlLine::draw_blinking_bmp(wxDC& dc, wxPoint pos, bool is_blinking) { - wxBitmap bmp_blinking = create_scaled_bitmap(is_blinking ? "search_blink" : "empty", ctrl); + wxBitmapBundle* bmp_blinking = get_bmp_bundle(is_blinking ? "search_blink" : "empty"); wxCoord h_pos = pos.x; - wxCoord v_pos = pos.y + lround((height - get_bitmap_size(bmp_blinking).GetHeight()) / 2); + wxCoord v_pos = pos.y + lround((height - get_bitmap_size(bmp_blinking, ctrl).GetHeight()) / 2); - dc.DrawBitmap(bmp_blinking, h_pos, v_pos); + dc.DrawBitmap(bmp_blinking->GetBitmapFor(ctrl), h_pos, v_pos); - int bmp_dim = get_bitmap_size(bmp_blinking).GetWidth(); + int bmp_dim = get_bitmap_size(bmp_blinking, ctrl).GetWidth(); h_pos += bmp_dim + ctrl->m_h_gap; return wxPoint(h_pos, v_pos); } -wxCoord OG_CustomCtrl::CtrlLine::draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmap& bmp_undo_to_sys, const wxBitmap& bmp_undo, bool is_blinking, size_t rect_id) +wxCoord OG_CustomCtrl::CtrlLine::draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmapBundle& bmp_undo_to_sys, const wxBitmapBundle& bmp_undo, bool is_blinking, size_t rect_id) { pos = draw_blinking_bmp(dc, pos, is_blinking); wxCoord h_pos = pos.x; wxCoord v_pos = pos.y; - dc.DrawBitmap(bmp_undo_to_sys, h_pos, v_pos); + dc.DrawBitmap(bmp_undo_to_sys.GetBitmapFor(ctrl), h_pos, v_pos); - int bmp_dim = get_bitmap_size(bmp_undo_to_sys).GetWidth(); + int bmp_dim = get_bitmap_size(&bmp_undo_to_sys, ctrl).GetWidth(); rects_undo_to_sys_icon[rect_id] = wxRect(h_pos, v_pos, bmp_dim, bmp_dim); h_pos += bmp_dim + ctrl->m_h_gap; - dc.DrawBitmap(bmp_undo, h_pos, v_pos); + dc.DrawBitmap(bmp_undo.GetBitmapFor(ctrl), h_pos, v_pos); - bmp_dim = get_bitmap_size(bmp_undo).GetWidth(); + bmp_dim = get_bitmap_size(&bmp_undo, ctrl).GetWidth(); rects_undo_icon[rect_id] = wxRect(h_pos, v_pos, bmp_dim, bmp_dim); h_pos += bmp_dim + ctrl->m_h_gap; diff --git a/src/slic3r/GUI/OG_CustomCtrl.hpp b/src/slic3r/GUI/OG_CustomCtrl.hpp index c15132fec..0308322f7 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.hpp +++ b/src/slic3r/GUI/OG_CustomCtrl.hpp @@ -63,7 +63,7 @@ class OG_CustomCtrl :public wxPanel wxCoord draw_mode_bmp(wxDC& dc, wxCoord v_pos); wxCoord draw_text (wxDC& dc, wxPoint pos, const wxString& text, const wxColour* color, int width, bool is_url = false); wxPoint draw_blinking_bmp(wxDC& dc, wxPoint pos, bool is_blinking); - wxCoord draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmap& bmp_undo_to_sys, const wxBitmap& bmp_undo, bool is_blinking, size_t rect_id = 0); + wxCoord draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmapBundle& bmp_undo_to_sys, const wxBitmapBundle& bmp_undo, bool is_blinking, size_t rect_id = 0); bool launch_browser() const; bool is_separator() const { return og_line.is_separator(); } diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 4344deb24..05d0d60ec 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -56,7 +56,7 @@ const std::map INFO_ITEMS{ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const wxString& sub_obj_name, Slic3r::ModelVolumeType type, - const wxBitmap& bmp, + const wxBitmapBundle& bmp, const wxString& extruder, const int idx/* = -1*/, const std::string& warning_icon_name /*= std::string*/) : @@ -101,7 +101,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } else if (type == itLayerRoot) { - m_bmp = create_scaled_bitmap(LayerRootIcon); // FIXME: pass window ptr + m_bmp = *get_bmp_bundle(LayerRootIcon); m_name = _(L("Layers")); } else if (type == itInfo) @@ -132,7 +132,7 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent } const std::string label_range = (boost::format(" %.2f-%.2f ") % layer_range.first % layer_range.second).str(); m_name = _(L("Range")) + label_range + "(" + _(L("mm")) + ")"; - m_bmp = create_scaled_bitmap(LayerIcon); // FIXME: pass window ptr + m_bmp = *get_bmp_bundle(LayerIcon); set_action_and_extruder_icons(); init_container(); @@ -151,7 +151,7 @@ void ObjectDataViewModelNode::set_action_and_extruder_icons() { m_action_icon_name = m_type & itObject ? "advanced_plus" : m_type & (itVolume | itLayer) ? "cog" : /*m_type & itInstance*/ "set_separate_obj"; - m_action_icon = create_scaled_bitmap(m_action_icon_name); // FIXME: pass window ptr + m_action_icon = *get_bmp_bundle(m_action_icon_name); // set extruder bitmap set_extruder_icon(); @@ -170,7 +170,7 @@ void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable) { m_printable = printable; m_printable_icon = m_printable == piUndef ? m_empty_bmp : - create_scaled_bitmap(m_printable == piPrintable ? "eye_open.png" : "eye_closed.png"); + *get_bmp_bundle(m_printable == piPrintable ? "eye_open" : "eye_closed"); } void ObjectDataViewModelNode::set_warning_icon(const std::string& warning_icon_name) @@ -185,14 +185,14 @@ void ObjectDataViewModelNode::update_settings_digest_bitmaps() m_bmp = m_empty_bmp; std::string scaled_bitmap_name = m_name.ToUTF8().data(); - scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : ""); + scaled_bitmap_name += (wxGetApp().dark_mode() ? "-dm" : ""); - wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); + wxBitmapBundle *bmp = m_bitmap_cache->find_bndl(scaled_bitmap_name); if (bmp == nullptr) { - std::vector bmps; + std::vector bmps; for (auto& category : m_opt_categories) - bmps.emplace_back(SettingsFactory::get_category_bitmap_(category, false)); - bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); + bmps.emplace_back(SettingsFactory::get_category_bitmap(category)); + bmp = m_bitmap_cache->insert_bndl(scaled_bitmap_name, bmps); } m_bmp = *bmp; @@ -216,13 +216,13 @@ bool ObjectDataViewModelNode::update_settings_digest(const std::vectorGetExtruder().IsEmpty()) @@ -1676,14 +1676,14 @@ wxDataViewItem ObjectDataViewModel::SetObjectPrintableState( return obj_item; } -void ObjectDataViewModel::Rescale() +void ObjectDataViewModel::UpdateBitmaps() { m_volume_bmps = MenuFactory::get_volume_bitmaps(); - m_warning_bmp = create_scaled_bitmap(WarningIcon); - m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon); + m_warning_bmp = *get_bmp_bundle(WarningIcon); + m_warning_manifold_bmp = *get_bmp_bundle(WarningManifoldIcon); for (auto item : INFO_ITEMS) - m_info_bmps[item.first] = create_scaled_bitmap(item.second.bmp_name); + m_info_bmps[item.first] = get_bmp_bundle(item.second.bmp_name); wxDataViewItemArray all_items; GetAllChildren(wxDataViewItem(0), all_items); @@ -1694,7 +1694,7 @@ void ObjectDataViewModel::Rescale() continue; ObjectDataViewModelNode *node = static_cast(item.GetID()); - node->msw_rescale(); + node->sys_color_changed(); switch (node->m_type) { @@ -1705,11 +1705,11 @@ void ObjectDataViewModel::Rescale() node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_warning_icon_name); break; case itLayerRoot: - node->m_bmp = create_scaled_bitmap(LayerRootIcon); + node->m_bmp = *get_bmp_bundle(LayerRootIcon); case itLayer: - node->m_bmp = create_scaled_bitmap(LayerIcon); + node->m_bmp = *get_bmp_bundle(LayerIcon); case itInfo: - node->m_bmp = m_info_bmps.at(node->m_info_item_type); + node->m_bmp = *m_info_bmps.at(node->m_info_item_type); break; default: break; } @@ -1718,22 +1718,22 @@ void ObjectDataViewModel::Rescale() } } -wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const std::string& warning_icon_name/* = std::string()*/) +wxBitmapBundle ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const std::string& warning_icon_name/* = std::string()*/) { if (warning_icon_name.empty()) - return m_volume_bmps[static_cast(vol_type)]; + return *m_volume_bmps[static_cast(vol_type)]; std::string scaled_bitmap_name = warning_icon_name + std::to_string(static_cast(vol_type)); scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : "-lm"); - wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); + wxBitmapBundle *bmp = m_bitmap_cache->find_bndl(scaled_bitmap_name); if (bmp == nullptr) { - std::vector bmps; + std::vector bmps; - bmps.emplace_back(GetWarningBitmap(warning_icon_name)); + bmps.emplace_back(&GetWarningBitmap(warning_icon_name)); bmps.emplace_back(m_volume_bmps[static_cast(vol_type)]); - bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); + bmp = m_bitmap_cache->insert_bndl(scaled_bitmap_name, bmps); } return *bmp; @@ -1768,7 +1768,7 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo return; if (node->GetType() & itVolume) { - node->SetWarningBitmap(m_volume_bmps[static_cast(node->volume_type())], ""); + node->SetWarningBitmap(*m_volume_bmps[static_cast(node->volume_type())], ""); return; } diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index f8885b206..7014acccb 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -63,21 +63,21 @@ class ObjectDataViewModelNode { ObjectDataViewModelNode* m_parent; MyObjectTreeModelNodePtrArray m_children; - wxBitmap m_empty_bmp; + wxBitmapBundle m_empty_bmp; size_t m_volumes_cnt = 0; std::vector< std::string > m_opt_categories; t_layer_height_range m_layer_range = { 0.0f, 0.0f }; wxString m_name; - wxBitmap& m_bmp = m_empty_bmp; + wxBitmapBundle& m_bmp = m_empty_bmp; ItemType m_type; int m_idx = -1; bool m_container = false; wxString m_extruder = "default"; - wxBitmap m_extruder_bmp; - wxBitmap m_action_icon; + wxBitmapBundle m_extruder_bmp; + wxBitmapBundle m_action_icon; PrintIndicator m_printable {piUndef}; - wxBitmap m_printable_icon; + wxBitmapBundle m_printable_icon; std::string m_warning_icon_name{ "" }; std::string m_action_icon_name = ""; @@ -99,7 +99,7 @@ public: ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const wxString& sub_obj_name, Slic3r::ModelVolumeType type, - const wxBitmap& bmp, + const wxBitmapBundle& bmp, const wxString& extruder, const int idx = -1, const std::string& warning_icon_name = std::string()); @@ -179,10 +179,10 @@ public: bool SetValue(const wxVariant &variant, unsigned int col); void SetVolumeType(ModelVolumeType type) { m_volume_type = type; } - void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } + void SetBitmap(const wxBitmapBundle &icon) { m_bmp = icon; } void SetExtruder(const wxString &extruder) { m_extruder = extruder; } - void SetWarningBitmap(const wxBitmap& icon, const std::string& warning_icon_name) { m_bmp = icon; m_warning_icon_name = warning_icon_name; } - const wxBitmap& GetBitmap() const { return m_bmp; } + void SetWarningBitmap(const wxBitmapBundle& icon, const std::string& warning_icon_name) { m_bmp = icon; m_warning_icon_name = warning_icon_name; } + const wxBitmapBundle& GetBitmap() const { return m_bmp; } const wxString& GetName() const { return m_name; } ItemType GetType() const { return m_type; } InfoItemType GetInfoItemType() const { return m_info_item_type; } @@ -234,7 +234,7 @@ public: void update_settings_digest_bitmaps(); bool update_settings_digest(const std::vector& categories); int volume_type() const { return int(m_volume_type); } - void msw_rescale(); + void sys_color_changed(); #ifndef NDEBUG bool valid(); @@ -257,11 +257,11 @@ wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); class ObjectDataViewModel :public wxDataViewModel { std::vector m_objects; - std::vector m_volume_bmps; - std::map m_info_bmps; - wxBitmap m_empty_bmp; - wxBitmap m_warning_bmp; - wxBitmap m_warning_manifold_bmp; + std::vector m_volume_bmps; + std::map m_info_bmps; + wxBitmapBundle m_empty_bmp; + wxBitmapBundle m_warning_bmp; + wxBitmapBundle m_warning_manifold_bmp; wxDataViewCtrl* m_ctrl { nullptr }; @@ -315,7 +315,7 @@ public: // helper method for wxLog wxString GetName(const wxDataViewItem &item) const; - wxBitmap& GetBitmap(const wxDataViewItem &item) const; + wxBitmapBundle& GetBitmap(const wxDataViewItem &item) const; wxString GetExtruder(const wxDataViewItem &item) const; int GetExtruderNumber(const wxDataViewItem &item) const; @@ -383,9 +383,9 @@ public: void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } // Rescale bitmaps for existing Items - void Rescale(); + void UpdateBitmaps(); - wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, + wxBitmapBundle GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const std::string& warning_icon_name = std::string()); void AddWarningIcon(const wxDataViewItem& item, const std::string& warning_name); void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); @@ -403,7 +403,7 @@ private: wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); void AddAllChildren(const wxDataViewItem& parent); - wxBitmap& GetWarningBitmap(const std::string& warning_icon_name); + wxBitmapBundle& GetWarningBitmap(const std::string& warning_icon_name); }; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 257fe2532..6055a8e78 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -728,7 +728,6 @@ void ConfigOptionsGroup::msw_rescale() // check if window is ScalableButton ScalableButton* sc_btn = dynamic_cast(win); if (sc_btn) { - sc_btn->msw_rescale(); sc_btn->SetSize(sc_btn->GetBestSize()); return; } @@ -773,7 +772,7 @@ void ConfigOptionsGroup::sys_color_changed() wxWindow* win = item->GetWindow(); // check if window is ScalableButton if (ScalableButton* sc_btn = dynamic_cast(win)) { - sc_btn->msw_rescale(); + sc_btn->sys_color_changed(); return; } wxGetApp().UpdateDarkUI(win, dynamic_cast(win) != nullptr); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index b2983f97f..04ee9d090 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -142,10 +142,10 @@ void PresetForPrinter::AllowDelete() m_presets_list->update(); } -void PresetForPrinter::msw_rescale() +void PresetForPrinter::on_sys_color_changed() { - m_presets_list->msw_rescale(); - m_delete_preset_btn->msw_rescale(); + m_presets_list->sys_color_changed(); + m_delete_preset_btn->sys_color_changed(); } @@ -603,19 +603,10 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); - m_add_preset_btn->msw_rescale(); - m_printhost_browse_btn->msw_rescale(); - m_printhost_test_btn->msw_rescale(); - if (m_printhost_cafile_browse_btn) - m_printhost_cafile_browse_btn->msw_rescale(); - m_optgroup->msw_rescale(); msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); - for (PresetForPrinter* preset : m_presets) - preset->msw_rescale(); - const wxSize& size = wxSize(45 * em, 35 * em); SetMinSize(size); @@ -623,6 +614,18 @@ void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect) Refresh(); } +void PhysicalPrinterDialog::on_sys_color_changed() +{ + m_add_preset_btn->sys_color_changed(); + m_printhost_browse_btn->sys_color_changed(); + m_printhost_test_btn->sys_color_changed(); + if (m_printhost_cafile_browse_btn) + m_printhost_cafile_browse_btn->sys_color_changed(); + + for (PresetForPrinter* preset : m_presets) + preset->on_sys_color_changed(); +} + void PhysicalPrinterDialog::OnOK(wxEvent& event) { wxString printer_name = m_printer_name->GetValue(); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp index cb9a48b3e..d8bb70d3c 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.hpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp @@ -48,8 +48,7 @@ public: void SuppressDelete(); void AllowDelete(); - void msw_rescale(); - void on_sys_color_changed() {}; + void on_sys_color_changed(); }; @@ -98,7 +97,7 @@ public: void DeletePreset(PresetForPrinter* preset_for_printer); protected: void on_dpi_changed(const wxRect& suggested_rect) override; - void on_sys_color_changed() override {}; + void on_sys_color_changed() override; bool had_all_mk3; }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b78a6db95..c0a4bedf1 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -180,7 +180,6 @@ public: bool showing_manifold_warning_icon; void show_sizer(bool show); - void msw_rescale(); void update_warning_icon(const std::string& warning_icon_name); }; @@ -210,7 +209,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : init_info_label(&info_size, _L("Size")); - info_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap("info")); + info_icon = new wxStaticBitmap(parent, wxID_ANY, *get_bmp_bundle("info")); info_icon->SetToolTip(_L("For a multipart object, this value isn't accurate.\n" "It doesn't take account of intersections and negative volumes.")); auto* volume_info_sizer = new wxBoxSizer(wxHORIZONTAL); @@ -223,7 +222,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : info_manifold = new wxStaticText(parent, wxID_ANY, ""); info_manifold->SetFont(wxGetApp().small_font()); - manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(m_warning_icon_name)); + manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, *get_bmp_bundle(m_warning_icon_name)); auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL); sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); sizer_manifold->Add(info_manifold, 0, wxLEFT, 2); @@ -242,17 +241,11 @@ void ObjectInfo::show_sizer(bool show) manifold_warning_icon->Show(showing_manifold_warning_icon && show); } -void ObjectInfo::msw_rescale() -{ - manifold_warning_icon->SetBitmap(create_scaled_bitmap(m_warning_icon_name)); - info_icon->SetBitmap(create_scaled_bitmap("info")); -} - void ObjectInfo::update_warning_icon(const std::string& warning_icon_name) { if ((showing_manifold_warning_icon = !warning_icon_name.empty())) { m_warning_icon_name = warning_icon_name; - manifold_warning_icon->SetBitmap(create_scaled_bitmap(m_warning_icon_name)); + manifold_warning_icon->SetBitmap(*get_bmp_bundle(m_warning_icon_name)); } } @@ -350,9 +343,6 @@ void FreqChangedParams::msw_rescale() { m_og->msw_rescale(); m_og_sla->msw_rescale(); - - for (auto btn: m_empty_buttons) - btn->msw_rescale(); } void FreqChangedParams::sys_color_changed() @@ -361,7 +351,7 @@ void FreqChangedParams::sys_color_changed() m_og_sla->sys_color_changed(); for (auto btn: m_empty_buttons) - btn->msw_rescale(); + btn->sys_color_changed(); wxGetApp().UpdateDarkUI(m_wiping_dialog_button, true); } @@ -450,7 +440,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : */ auto empty_widget = [this] (wxWindow* parent) { auto sizer = new wxBoxSizer(wxHORIZONTAL); - auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, int(0.3 * wxGetApp().em_unit())); m_empty_buttons.push_back(btn); @@ -508,7 +498,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : } })); - auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); sizer->Add(btn , 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, int(0.3 * wxGetApp().em_unit())); @@ -1103,9 +1093,6 @@ void Sidebar::msw_rescale() { SetMinSize(wxSize(40 * wxGetApp().em_unit(), -1)); - if (p->mode_sizer) - p->mode_sizer->msw_rescale(); - for (PlaterPresetComboBox* combo : std::vector { p->combo_print, p->combo_sla_print, p->combo_sla_material, @@ -1117,14 +1104,8 @@ void Sidebar::msw_rescale() p->frequently_changed_parameters->msw_rescale(); p->object_list->msw_rescale(); p->object_manipulation->msw_rescale(); - p->object_settings->msw_rescale(); p->object_layers->msw_rescale(); - p->object_info->msw_rescale(); - - p->btn_send_gcode->msw_rescale(); -// p->btn_eject_device->msw_rescale(); - p->btn_export_gcode_removable->msw_rescale(); #ifdef _WIN32 const int scaled_height = p->btn_export_gcode_removable->GetBitmapHeight(); #else @@ -1145,14 +1126,13 @@ void Sidebar::sys_color_changed() for (wxWindow* win : std::vector{ this, p->sliced_info->GetStaticBox(), p->object_info->GetStaticBox(), p->btn_reslice, p->btn_export_gcode }) wxGetApp().UpdateDarkUI(win); - p->object_info->msw_rescale(); for (wxWindow* win : std::vector{ p->scrolled, p->presets_panel }) wxGetApp().UpdateAllStaticTextDarkUI(win); for (wxWindow* btn : std::vector{ p->btn_reslice, p->btn_export_gcode }) wxGetApp().UpdateDarkUI(btn, true); if (p->mode_sizer) - p->mode_sizer->msw_rescale(); + p->mode_sizer->sys_color_changed(); p->frequently_changed_parameters->sys_color_changed(); p->object_settings->sys_color_changed(); #endif @@ -1170,11 +1150,12 @@ void Sidebar::sys_color_changed() p->object_layers->sys_color_changed(); // btn...->msw_rescale() updates icon on button, so use it - p->btn_send_gcode->msw_rescale(); + p->btn_send_gcode->sys_color_changed(); // p->btn_eject_device->msw_rescale(); - p->btn_export_gcode_removable->msw_rescale(); + p->btn_export_gcode_removable->sys_color_changed(); p->scrolled->Layout(); + p->scrolled->Refresh(); p->searcher.dlg_sys_color_changed(); } @@ -6971,8 +6952,6 @@ void Plater::msw_rescale() p->sidebar->msw_rescale(); - p->menus.msw_rescale(); - Layout(); GetParent()->Layout(); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index def48a1e4..93a5fe433 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -108,8 +108,8 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const default: break; } - m_bitmapCompatible = ScalableBitmap(this, "flag_green"); - m_bitmapIncompatible = ScalableBitmap(this, "flag_red"); + m_bitmapCompatible = get_bmp_bundle("flag_green"); + m_bitmapIncompatible = get_bmp_bundle("flag_red"); // parameters for an icon's drawing fill_width_height(); @@ -242,12 +242,12 @@ void PresetComboBox::update(std::string select_preset_name) const std::deque& presets = m_collection->get_presets(); - std::map> nonsys_presets; - std::map incomp_presets; + std::map> nonsys_presets; + std::map incomp_presets; wxString selected = ""; if (!presets.front().is_visible) - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { @@ -268,7 +268,7 @@ void PresetComboBox::update(std::string select_preset_name) } std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); + auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); assert(bmp); if (!is_enabled) @@ -280,17 +280,17 @@ void PresetComboBox::update(std::string select_preset_name) } else { - nonsys_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); + nonsys_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); if (preset.name == select_preset_name || (select_preset_name.empty() && is_enabled)) selected = get_preset_name(preset); } if (i + 1 == m_collection->num_default_presets()) - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } if (!nonsys_presets.empty()) { - set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); - for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); + for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { int item_id = Append(it->first, *it->second.first); bool is_enabled = it->second.second; if (!is_enabled) @@ -300,8 +300,8 @@ void PresetComboBox::update(std::string select_preset_name) } if (!incomp_presets.empty()) { - set_label_marker(Append(separator(L("Incompatible presets")), wxNullBitmap)); - for (std::map::iterator it = incomp_presets.begin(); it != incomp_presets.end(); ++it) { + set_label_marker(Append(separator(L("Incompatible presets")), NullBitmapBndl())); + for (std::map::iterator it = incomp_presets.begin(); it != incomp_presets.end(); ++it) { set_label_marker(Append(it->first, *it->second), LABEL_ITEM_DISABLED); } } @@ -337,7 +337,6 @@ bool PresetComboBox::del_physical_printer(const wxString& note_string/* = wxEmpt msg += note_string + "\n"; msg += format_wxstr(_L("Are you sure you want to delete \"%1%\" printer?"), printer_name); - //if (wxMessageDialog(this, msg, _L("Delete Physical Printer"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal() != wxID_YES) if (MessageDialog(this, msg, _L("Delete Physical Printer"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION).ShowModal() != wxID_YES) return false; @@ -364,7 +363,8 @@ void PresetComboBox::show_all(bool show_all) void PresetComboBox::update() { - this->update(into_u8(this->GetString(this->GetSelection()))); + int n = this->GetSelection(); + this->update(n < 0 ? "" : into_u8(this->GetString(n))); } void PresetComboBox::update_from_bundle() @@ -375,43 +375,31 @@ void PresetComboBox::update_from_bundle() void PresetComboBox::msw_rescale() { m_em_unit = em_unit(this); +} - //m_bitmapIncompatible.msw_rescale(); - //m_bitmapCompatible.msw_rescale(); - - // parameters for an icon's drawing - fill_width_height(); +void PresetComboBox::sys_color_changed() +{ + m_bitmapCompatible = get_bmp_bundle("flag_green"); + m_bitmapIncompatible = get_bmp_bundle("flag_red"); + wxGetApp().UpdateDarkUI(this); // update the control to redraw the icons update(); } -void PresetComboBox::sys_color_changed() -{ - wxGetApp().UpdateDarkUI(this); - msw_rescale(); -} - void PresetComboBox::fill_width_height() { - // To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so - // set a bitmap's height to m_bitmapCompatible->GetHeight() and norm_icon_width to m_bitmapCompatible->GetWidth() - icon_height = m_bitmapCompatible.GetBmpHeight(); - norm_icon_width = m_bitmapCompatible.GetBmpWidth(); + icon_height = m_bitmapCompatible->GetPreferredBitmapSizeAtScale(1.0).GetHeight(); + norm_icon_width = m_bitmapCompatible->GetPreferredBitmapSizeAtScale(1.0).GetWidth(); - /* It's supposed that standard size of an icon is 16px*16px for 100% scaled display. - * So set sizes for solid_colored icons used for filament preset - * and scale them in respect to em_unit value - */ -// const float scale_f = (float)m_em_unit * 0.1f; - const float scale_f = 1.0f; + null_icon_width = 2 * norm_icon_width; - thin_icon_width = lroundf(8 * scale_f); // analogue to 8px; + thin_icon_width = 8; wide_icon_width = norm_icon_width + thin_icon_width; - space_icon_width = lroundf(2 * scale_f); - thin_space_icon_width = lroundf(4 * scale_f); - wide_space_icon_width = lroundf(6 * scale_f); + space_icon_width = 2; + thin_space_icon_width = 4; + wide_space_icon_width = 6; } wxString PresetComboBox::separator(const std::string& label) @@ -419,7 +407,8 @@ wxString PresetComboBox::separator(const std::string& label) return wxString::FromUTF8(separator_head()) + _(label) + wxString::FromUTF8(separator_tail()); } -wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, + +wxBitmapBundle* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, bool is_compatible/* = true*/, bool is_system/* = false*/, bool is_single_bar/* = false*/, const std::string& filament_rgb/* = ""*/, const std::string& extruder_rgb/* = ""*/, const std::string& material_rgb/* = ""*/) { @@ -435,45 +424,41 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, bool wide_icons, con bitmap_key += ",dark"; bitmap_key += material_rgb; - wxBitmap* bmp = bitmap_cache().find(bitmap_key); - if (bmp == nullptr) { + wxBitmapBundle* bmp_bndl = bitmap_cache().find_bndl(bitmap_key); + if (bmp_bndl == nullptr) { // Create the bitmap with color bars. - std::vector bmps; + std::vector bmps; if (wide_icons) // Paint a red flag for incompatible presets. - bmps.emplace_back(is_compatible ? bitmap_cache().mkclear(norm_icon_width, icon_height) : m_bitmapIncompatible.bmp()); + bmps.emplace_back(is_compatible ? get_empty_bmp_bundle(norm_icon_width, icon_height) : m_bitmapIncompatible); if (m_type == Preset::TYPE_FILAMENT && !filament_rgb.empty()) { // Paint the color bars. - ColorRGB color; - decode_color(filament_rgb, color); - bmps.emplace_back(bitmap_cache().mksolid(is_single_bar ? wide_icon_width : norm_icon_width, icon_height, color, false, 1, dark_mode)); - if (!is_single_bar) { - decode_color(extruder_rgb, color); - bmps.emplace_back(bitmap_cache().mksolid(thin_icon_width, icon_height, color, false, 1, dark_mode)); - } + bmps.emplace_back(get_solid_bmp_bundle(is_single_bar ? wide_icon_width : norm_icon_width, icon_height, filament_rgb)); + if (!is_single_bar) + bmps.emplace_back(get_solid_bmp_bundle(thin_icon_width, icon_height, extruder_rgb)); // Paint a lock at the system presets. - bmps.emplace_back(bitmap_cache().mkclear(space_icon_width, icon_height)); + bmps.emplace_back(get_empty_bmp_bundle(space_icon_width, icon_height)); } else { // Paint the color bars. - bmps.emplace_back(bitmap_cache().mkclear(thin_space_icon_width, icon_height)); + bmps.emplace_back(get_empty_bmp_bundle(thin_space_icon_width, icon_height)); if (m_type == Preset::TYPE_SLA_MATERIAL) - bmps.emplace_back(create_scaled_bitmap(main_icon_name, this, 16, false, material_rgb)); + bmps.emplace_back(bitmap_cache().from_svg(main_icon_name, 16, 16, dark_mode, material_rgb)); else - bmps.emplace_back(create_scaled_bitmap(main_icon_name)); + bmps.emplace_back(get_bmp_bundle(main_icon_name)); // Paint a lock at the system presets. - bmps.emplace_back(bitmap_cache().mkclear(wide_space_icon_width, icon_height)); + bmps.emplace_back(get_empty_bmp_bundle(wide_space_icon_width, icon_height)); } - bmps.emplace_back(is_system ? create_scaled_bitmap("lock_closed") : bitmap_cache().mkclear(norm_icon_width, icon_height)); - bmp = bitmap_cache().insert(bitmap_key, bmps); + bmps.emplace_back(is_system ? get_bmp_bundle("lock_closed") : get_empty_bmp_bundle(norm_icon_width, icon_height)); + bmp_bndl = bitmap_cache().insert_bndl(bitmap_key, bmps); } - return bmp; + return bmp_bndl; } -wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, +wxBitmapBundle* PresetComboBox::get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, bool is_enabled/* = true*/, bool is_compatible/* = true*/, bool is_system/* = false*/) { bitmap_key += !is_enabled ? "_disabled" : ""; @@ -483,20 +468,25 @@ wxBitmap* PresetComboBox::get_bmp( std::string bitmap_key, const std::string& m if (wxGetApp().dark_mode()) bitmap_key += ",dark"; - wxBitmap* bmp = bitmap_cache().find(bitmap_key); + wxBitmapBundle* bmp = bitmap_cache().find_bndl(bitmap_key); if (bmp == nullptr) { // Create the bitmap with color bars. - std::vector bmps; - bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? create_scaled_bitmap(main_icon_name, this, 16, !is_enabled) : - is_compatible ? m_bitmapCompatible.bmp() : m_bitmapIncompatible.bmp()); + std::vector bmps; + bmps.emplace_back(m_type == Preset::TYPE_PRINTER ? get_bmp_bundle(main_icon_name) : + is_compatible ? m_bitmapCompatible : m_bitmapIncompatible); // Paint a lock at the system presets. - bmps.emplace_back(is_system ? create_scaled_bitmap(next_icon_name, this, 16, !is_enabled) : bitmap_cache().mkclear(norm_icon_width, icon_height)); - bmp = bitmap_cache().insert(bitmap_key, bmps); + bmps.emplace_back(is_system ? get_bmp_bundle(next_icon_name) : get_empty_bmp_bundle(norm_icon_width, icon_height)); + bmp = bitmap_cache().insert_bndl(bitmap_key, bmps); } return bmp; } +wxBitmapBundle PresetComboBox::NullBitmapBndl() +{ + return *get_empty_bmp_bundle(null_icon_width, icon_height); +} + bool PresetComboBox::is_selected_physical_printer() { auto selected_item = this->GetSelection(); @@ -783,14 +773,16 @@ void PlaterPresetComboBox::update() // and draw a red flag in front of the selected preset. bool wide_icons = selected_preset && !selected_preset->is_compatible; - std::map nonsys_presets; + null_icon_width = (wide_icons ? 3 : 2) * norm_icon_width + thin_space_icon_width + wide_space_icon_width; + + std::map nonsys_presets; wxString selected_user_preset; wxString tooltip; const std::deque& presets = m_collection->get_presets(); if (!presets.front().is_visible) - this->set_label_marker(this->Append(separator(L("System presets")), wxNullBitmap)); + this->set_label_marker(this->Append(separator(L("System presets")), NullBitmapBndl())); for (size_t i = presets.front().is_visible ? 0 : m_collection->num_default_presets(); i < presets.size(); ++i) { @@ -824,7 +816,7 @@ void PlaterPresetComboBox::update() material_rgb = print_config_def.get("material_colour")->get_default_value()->value; } - wxBitmap* bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name, + auto bmp = get_bmp(bitmap_key, wide_icons, bitmap_type_name, preset.is_compatible, preset.is_system || preset.is_default, single_bar, filament_rgb, extruder_rgb, material_rgb); assert(bmp); @@ -845,12 +837,12 @@ void PlaterPresetComboBox::update() } } if (i + 1 == m_collection->num_default_presets()) - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } if (!nonsys_presets.empty()) { - set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); - for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); + for (std::map::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { Append(it->first, *it->second); validate_selection(it->first == selected_user_preset); } @@ -860,7 +852,7 @@ void PlaterPresetComboBox::update() { // add Physical printers, if any exists if (!m_preset_bundle->physical_printers.empty()) { - set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap)); + set_label_marker(Append(separator(L("Physical printers")), NullBitmapBndl())); const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers; for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { @@ -869,7 +861,7 @@ void PlaterPresetComboBox::update() if (!preset || !preset->is_visible) continue; std::string main_icon_name, bitmap_key = main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - wxBitmap* bmp = get_bmp(main_icon_name, wide_icons, main_icon_name); + auto bmp = get_bmp(main_icon_name, wide_icons, main_icon_name); assert(bmp); set_label_marker(Append(from_u8(it->get_full_name(preset_name) + suffix(preset)), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); @@ -880,7 +872,7 @@ void PlaterPresetComboBox::update() } if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) { - wxBitmap* bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni"); + auto bmp = get_bmp("edit_preset_list", wide_icons, "edit_uni"); assert(bmp); if (m_type == Preset::TYPE_FILAMENT) @@ -917,9 +909,20 @@ void PlaterPresetComboBox::update() void PlaterPresetComboBox::msw_rescale() { PresetComboBox::msw_rescale(); - edit_btn->msw_rescale(); +#ifdef __WXMSW__ + // Use this part of code just on Windows to avoid of some layout issues on Linux + // see https://github.com/prusa3d/PrusaSlicer/issues/5163 and https://github.com/prusa3d/PrusaSlicer/issues/5505 + // Update control min size after rescale (changed Display DPI under MSW) + if (GetMinWidth() != 20 * m_em_unit) + SetMinSize(wxSize(20 * m_em_unit, GetSize().GetHeight())); +#endif //__WXMSW__ } +void PlaterPresetComboBox::sys_color_changed() +{ + PresetComboBox::sys_color_changed(); + edit_btn->sys_color_changed(); +} // --------------------------------- // *** TabPresetComboBox *** @@ -982,10 +985,10 @@ void TabPresetComboBox::update() const std::deque& presets = m_collection->get_presets(); - std::map> nonsys_presets; + std::map> nonsys_presets; wxString selected = ""; if (!presets.front().is_visible) - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); size_t idx_selected = m_collection->get_selected_idx(); if (m_type == Preset::TYPE_PRINTER && m_preset_bundle->physical_printers.has_selection()) { @@ -1012,7 +1015,7 @@ void TabPresetComboBox::update() } std::string main_icon_name = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - wxBitmap* bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); + auto bmp = get_bmp(bitmap_key, main_icon_name, "lock_closed", is_enabled, preset.is_compatible, preset.is_system || preset.is_default); assert(bmp); if (preset.is_default || preset.is_system) { @@ -1023,18 +1026,18 @@ void TabPresetComboBox::update() } else { - std::pair pair(bmp, is_enabled); - nonsys_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); + std::pair pair(bmp, is_enabled); + nonsys_presets.emplace(get_preset_name(preset), std::pair(bmp, is_enabled)); if (i == idx_selected) selected = get_preset_name(preset); } if (i + 1 == m_collection->num_default_presets()) - set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); + set_label_marker(Append(separator(L("System presets")), NullBitmapBndl())); } if (!nonsys_presets.empty()) { - set_label_marker(Append(separator(L("User presets")), wxNullBitmap)); - for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { + set_label_marker(Append(separator(L("User presets")), NullBitmapBndl())); + for (std::map>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) { int item_id = Append(it->first, *it->second.first); bool is_enabled = it->second.second; if (!is_enabled) @@ -1047,7 +1050,7 @@ void TabPresetComboBox::update() { // add Physical printers, if any exists if (!m_preset_bundle->physical_printers.empty()) { - set_label_marker(Append(separator(L("Physical printers")), wxNullBitmap)); + set_label_marker(Append(separator(L("Physical printers")), NullBitmapBndl())); const PhysicalPrinterCollection& ph_printers = m_preset_bundle->physical_printers; for (PhysicalPrinterCollection::ConstIterator it = ph_printers.begin(); it != ph_printers.end(); ++it) { @@ -1057,7 +1060,7 @@ void TabPresetComboBox::update() continue; std::string main_icon_name = preset->printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name; - wxBitmap* bmp = get_bmp(main_icon_name, main_icon_name, "", true, true, false); + auto bmp = get_bmp(main_icon_name, main_icon_name, "", true, true, false); assert(bmp); set_label_marker(Append(from_u8(it->get_full_name(preset_name) + suffix(preset)), *bmp), LABEL_ITEM_PHYSICAL_PRINTER); @@ -1068,7 +1071,7 @@ void TabPresetComboBox::update() // add "Add/Remove printers" item std::string icon_name = "edit_uni"; - wxBitmap* bmp = get_bmp("edit_preset_list, tab,", icon_name, ""); + auto bmp = get_bmp("edit_preset_list, tab,", icon_name, ""); assert(bmp); set_label_marker(Append(separator(L("Add/Remove printers")), *bmp), LABEL_ITEM_WIZARD_PRINTERS); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 40a0cfc28..9fe75c126 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -1,7 +1,7 @@ #ifndef slic3r_PresetComboBoxes_hpp_ #define slic3r_PresetComboBoxes_hpp_ -//#include +#include #include #include "libslic3r/Preset.hpp" @@ -88,9 +88,9 @@ protected: static BitmapCache& bitmap_cache(); // Indicator, that the preset is compatible with the selected printer. - ScalableBitmap m_bitmapCompatible; + wxBitmapBundle* m_bitmapCompatible; // Indicator, that the preset is NOT compatible with the selected printer. - ScalableBitmap m_bitmapIncompatible; + wxBitmapBundle* m_bitmapIncompatible; int m_last_selected; int m_em_unit; @@ -99,6 +99,7 @@ protected: // parameters for an icon's drawing int icon_height; int norm_icon_width; + int null_icon_width; int thin_icon_width; int wide_icon_width; int space_icon_width; @@ -120,13 +121,15 @@ protected: #endif // __linux__ static wxString separator(const std::string& label); - wxBitmap* get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, + wxBitmapBundle* get_bmp( std::string bitmap_key, bool wide_icons, const std::string& main_icon_name, bool is_compatible = true, bool is_system = false, bool is_single_bar = false, const std::string& filament_rgb = "", const std::string& extruder_rgb = "", const std::string& material_rgb = ""); - wxBitmap* get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, + wxBitmapBundle* get_bmp( std::string bitmap_key, const std::string& main_icon_name, const std::string& next_icon_name, bool is_enabled = true, bool is_compatible = true, bool is_system = false); + wxBitmapBundle NullBitmapBndl(); + private: void fill_width_height(); }; @@ -155,6 +158,7 @@ public: wxString get_preset_name(const Preset& preset) override; void update() override; void msw_rescale() override; + void sys_color_changed() override; void OnSelect(wxCommandEvent& evt) override; private: diff --git a/src/slic3r/GUI/SavePresetDialog.cpp b/src/slic3r/GUI/SavePresetDialog.cpp index 460b30126..2a2e3bb10 100644 --- a/src/slic3r/GUI/SavePresetDialog.cpp +++ b/src/slic3r/GUI/SavePresetDialog.cpp @@ -56,7 +56,7 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox wxStaticText* label_top = new wxStaticText(m_parent, wxID_ANY, from_u8((boost::format(_utf8(L("Save %s as:"))) % into_u8(tab->title())).str())); - m_valid_bmp = new wxStaticBitmap(m_parent, wxID_ANY, create_scaled_bitmap("tick_mark", m_parent)); + m_valid_bmp = new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark")); m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name), wxDefaultPosition, wxSize(35 * wxGetApp().em_unit(), -1)); for (const std::string& value : values) @@ -173,7 +173,7 @@ void SavePresetDialog::Item::update_valid_bmp() { std::string bmp_name = m_valid_type == Warning ? "exclamation" : m_valid_type == NoValid ? "cross" : "tick_mark" ; - m_valid_bmp->SetBitmap(create_scaled_bitmap(bmp_name, m_parent)); + m_valid_bmp->SetBitmap(*get_bmp_bundle(bmp_name)); } void SavePresetDialog::Item::accept() diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 6b5edc30e..52e58193c 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -720,12 +720,9 @@ void SearchDialog::msw_rescale() { const int& em = em_unit(); - search_list_model->msw_rescale(); search_list->GetColumn(SearchListModel::colIcon )->SetWidth(3 * em); search_list->GetColumn(SearchListModel::colMarkedText)->SetWidth(45 * em); - msw_buttons_rescale(this, em, { wxID_CANCEL }); - const wxSize& size = wxSize(40 * em, 30 * em); SetMinSize(size); @@ -743,7 +740,7 @@ void SearchDialog::on_sys_color_changed() #endif // msw_rescale updates just icons, so use it - search_list_model->msw_rescale(); + search_list_model->sys_color_changed(); Refresh(); } @@ -776,10 +773,10 @@ void SearchListModel::Prepend(const std::string& label) RowPrepended(); } -void SearchListModel::msw_rescale() +void SearchListModel::sys_color_changed() { for (ScalableBitmap& bmp : m_icon) - bmp.msw_rescale(); + bmp.sys_color_changed(); } wxString SearchListModel::GetColumnType(unsigned int col) const @@ -795,7 +792,7 @@ void SearchListModel::GetValueByRow(wxVariant& variant, switch (col) { case colIcon: - variant << m_icon[m_values[row].second].bmp(); + variant << m_icon[m_values[row].second].bmp().GetBitmapFor(m_icon[m_values[row].second].parent()); break; case colMarkedText: variant = m_values[row].first; diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 49866d066..0d7851132 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -213,7 +213,7 @@ public: void Clear(); void Prepend(const std::string& text); - void msw_rescale(); + void sys_color_changed(); // implementation of base class virtuals to define model diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index db8cfc9c2..d4d83dfda 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -104,7 +104,7 @@ SysInfoDialog::SysInfoDialog() // logo //m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 192); //m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bmp.bmp()); - m_logo = new wxStaticBitmap(this, wxID_ANY, wxBitmapBundle::FromSVGFile(Slic3r::var(wxGetApp().logo_name() + ".svg"), wxSize(192, 192))); + m_logo = new wxStaticBitmap(this, wxID_ANY, *get_bmp_bundle(wxGetApp().logo_name(), 192)); hsizer->Add(m_logo, 0, wxALIGN_CENTER_VERTICAL); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 1cb6ec8fc..d8d6dbf19 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -155,10 +155,8 @@ void Tab::create_preset_tab() add_scaled_button(panel, &m_btn_edit_ph_printer, "cog"); m_show_incompatible_presets = false; - add_scaled_bitmap(this, m_bmp_show_incompatible_presets, "flag_red"); - add_scaled_bitmap(this, m_bmp_hide_incompatible_presets, "flag_green"); - add_scaled_button(panel, &m_btn_hide_incompatible_presets, m_bmp_hide_incompatible_presets.name()); + add_scaled_button(panel, &m_btn_hide_incompatible_presets, "flag_green"); m_btn_compare_preset->SetToolTip(_L("Compare this preset with some another")); // TRN "Save current Settings" @@ -207,7 +205,7 @@ void Tab::create_preset_tab() #endif m_mode_sizer = new ModeSizer(panel, int (0.5*em_unit(this))); - const float scale_factor = /*wxGetApp().*/em_unit(this)*0.1;// GetContentScaleFactor(); + const float scale_factor = em_unit(this)*0.1;// GetContentScaleFactor(); m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 0, wxEXPAND | wxBOTTOM, 3); m_hsizer->Add(m_presets_choice, 0, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); @@ -254,11 +252,8 @@ void Tab::create_preset_tab() m_treectrl = new wxTreeCtrl(panel, wxID_ANY, wxDefaultPosition, wxSize(20 * m_em_unit, -1), wxTR_NO_BUTTONS | wxTR_HIDE_ROOT | wxTR_SINGLE | wxTR_NO_LINES | wxBORDER_SUNKEN | wxWANTS_CHARS); m_left_sizer->Add(m_treectrl, 1, wxEXPAND); - const int img_sz = int(16 * scale_factor + 0.5f); - m_icons = new wxImageList(img_sz, img_sz, true, 1); - // Index of the last icon inserted into $self->{icons}. + // Index of the last icon inserted into m_treectrl m_icon_count = -1; - m_treectrl->AssignImageList(m_icons); m_treectrl->AddRoot("root"); m_treectrl->SetIndent(0); wxGetApp().UpdateDarkUI(m_treectrl); @@ -321,6 +316,14 @@ void Tab::create_preset_tab() // Initialize the DynamicPrintConfig by default keys/values. build(); + if (!m_scaled_icons_list.empty()) { + // update icons for tree_ctrl + wxVector img_bundles; + for (const ScalableBitmap& bmp : m_scaled_icons_list) + img_bundles.push_back(bmp.bmp()); + m_treectrl->SetImages(img_bundles); + } + // ys_FIXME: Following should not be needed, the function will be called later // (update_mode->update_visibility->rebuild_page_tree). This does not work, during the // second call of rebuild_page_tree m_treectrl->GetFirstVisibleItem(); returns zero @@ -336,7 +339,7 @@ void Tab::add_scaled_button(wxWindow* parent, const wxString& label/* = wxEmptyString*/, long style /*= wxBU_EXACTFIT | wxNO_BORDER*/) { - *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, style, true); + *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, style); m_scaled_buttons.push_back(*btn); } @@ -366,7 +369,6 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str if (icon_idx == -1) { // Add a new icon to the icon list. m_scaled_icons_list.push_back(ScalableBitmap(this, icon)); - m_icons->Add(m_scaled_icons_list.back().bmp()); icon_idx = ++m_icon_count; m_icon_index[icon] = icon_idx; } @@ -656,16 +658,6 @@ void TabPrinter::init_options_list() m_options_list.emplace("extruders_count", m_opt_status_value); } -void TabPrinter::msw_rescale() -{ - Tab::msw_rescale(); - - if (m_reset_to_filament_color) - m_reset_to_filament_color->msw_rescale(); - - Layout(); -} - void TabSLAMaterial::init_options_list() { if (!m_options_list.empty()) @@ -924,31 +916,9 @@ void Tab::msw_rescale() { m_em_unit = em_unit(m_parent); - if (m_mode_sizer) - m_mode_sizer->msw_rescale(); m_presets_choice->msw_rescale(); - m_treectrl->SetMinSize(wxSize(20 * m_em_unit, -1)); - // rescale buttons and cached bitmaps - for (const auto btn : m_scaled_buttons) - btn->msw_rescale(); - for (const auto bmp : m_scaled_bitmaps) - bmp->msw_rescale(); - - if (m_detach_preset_btn) - m_detach_preset_btn->msw_rescale(); - - // rescale icons for tree_ctrl - for (ScalableBitmap& bmp : m_scaled_icons_list) - bmp.msw_rescale(); - // recreate and set new ImageList for tree_ctrl - m_icons->RemoveAll(); - m_icons = new wxImageList(m_scaled_icons_list.front().bmp().GetWidth(), m_scaled_icons_list.front().bmp().GetHeight()); - for (ScalableBitmap& bmp : m_scaled_icons_list) - m_icons->Add(bmp.bmp()); - m_treectrl->AssignImageList(m_icons); - // rescale options_groups if (m_active_page) m_active_page->msw_rescale(); @@ -962,28 +932,28 @@ void Tab::sys_color_changed() // update buttons and cached bitmaps for (const auto btn : m_scaled_buttons) - btn->msw_rescale(); + btn->sys_color_changed(); for (const auto bmp : m_scaled_bitmaps) - bmp->msw_rescale(); + bmp->sys_color_changed(); if (m_detach_preset_btn) - m_detach_preset_btn->msw_rescale(); + m_detach_preset_btn->sys_color_changed(); + + update_show_hide_incompatible_button(); // update icons for tree_ctrl - for (ScalableBitmap& bmp : m_scaled_icons_list) - bmp.msw_rescale(); - // recreate and set new ImageList for tree_ctrl - m_icons->RemoveAll(); - m_icons = new wxImageList(m_scaled_icons_list.front().bmp().GetWidth(), m_scaled_icons_list.front().bmp().GetHeight()); - for (ScalableBitmap& bmp : m_scaled_icons_list) - m_icons->Add(bmp.bmp()); - m_treectrl->AssignImageList(m_icons); + wxVector img_bundles; + for (ScalableBitmap& bmp : m_scaled_icons_list) { + bmp.sys_color_changed(); + img_bundles.push_back(bmp.bmp()); + } + m_treectrl->SetImages(img_bundles); // Colors for ui "decoration" update_label_colours(); #ifdef _WIN32 wxWindowUpdateLocker noUpdates(this); if (m_mode_sizer) - m_mode_sizer->msw_rescale(); + m_mode_sizer->sys_color_changed(); wxGetApp().UpdateDarkUI(this); wxGetApp().UpdateDarkUI(m_treectrl); #endif @@ -994,6 +964,7 @@ void Tab::sys_color_changed() m_active_page->sys_color_changed(); Layout(); + Refresh(); } Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const @@ -1257,7 +1228,7 @@ void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup) auto detach_preset_btn = [this](wxWindow* parent) { m_detach_preset_btn = new ScalableButton(parent, wxID_ANY, "lock_open_sys", _L("Detach from system preset"), - wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); ScalableButton* btn = m_detach_preset_btn; btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -1670,14 +1641,14 @@ void TabPrint::build() option.opt.height = 5;//50; optgroup->append_single_option_line(option); - page = add_options_page(L("Notes"), "note.png"); + page = add_options_page(L("Notes"), "note"); optgroup = page->new_optgroup(L("Notes"), 0); option = optgroup->get_option("notes"); option.opt.full_width = true; option.opt.height = 25;//250; optgroup->append_single_option_line(option); - page = add_options_page(L("Dependencies"), "wrench.png"); + page = add_options_page(L("Dependencies"), "wrench"); optgroup = page->new_optgroup(L("Profile dependencies")); create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) { @@ -1917,7 +1888,7 @@ void TabFilament::build() m_presets = &m_preset_bundle->filaments; load_initial_data(); - auto page = add_options_page(L("Filament"), "spool.png"); + auto page = add_options_page(L("Filament"), "spool"); auto optgroup = page->new_optgroup(L("Filament")); optgroup->append_single_option_line("filament_colour"); optgroup->append_single_option_line("filament_diameter"); @@ -2057,7 +2028,7 @@ void TabFilament::build() option.opt.height = gcode_field_height;// 150; optgroup->append_single_option_line(option); - page = add_options_page(L("Notes"), "note.png"); + page = add_options_page(L("Notes"), "note"); optgroup = page->new_optgroup(L("Notes"), 0); optgroup->label_width = 0; option = optgroup->get_option("filament_notes"); @@ -2065,7 +2036,7 @@ void TabFilament::build() option.opt.height = notes_field_height;// 250; optgroup->append_single_option_line(option); - page = add_options_page(L("Dependencies"), "wrench.png"); + page = add_options_page(L("Dependencies"), "wrench"); optgroup = page->new_optgroup(L("Profile dependencies")); create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) { return compatible_widget_create(parent, m_compatible_printers); @@ -2449,14 +2420,14 @@ void TabPrinter::build_fff() option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); - page = add_options_page(L("Notes"), "note.png"); + page = add_options_page(L("Notes"), "note"); optgroup = page->new_optgroup(L("Notes"), 0); option = optgroup->get_option("printer_notes"); option.opt.full_width = true; option.opt.height = notes_field_height;//250; optgroup->append_single_option_line(option); - page = add_options_page(L("Dependencies"), "wrench.png"); + page = add_options_page(L("Dependencies"), "wrench"); optgroup = page->new_optgroup(L("Profile dependencies")); build_preset_description_line(optgroup.get()); @@ -2526,14 +2497,14 @@ void TabPrinter::build_sla() const int notes_field_height = 25; // 250 - page = add_options_page(L("Notes"), "note.png"); + page = add_options_page(L("Notes"), "note"); optgroup = page->new_optgroup(L("Notes"), 0); option = optgroup->get_option("printer_notes"); option.opt.full_width = true; option.opt.height = notes_field_height;//250; optgroup->append_single_option_line(option); - page = add_options_page(L("Dependencies"), "wrench.png"); + page = add_options_page(L("Dependencies"), "wrench"); optgroup = page->new_optgroup(L("Profile dependencies")); build_preset_description_line(optgroup.get()); @@ -2791,7 +2762,7 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/) auto reset_to_filament_color = [this, extruder_idx](wxWindow* parent) { m_reset_to_filament_color = new ScalableButton(parent, wxID_ANY, "undo", _L("Reset to Filament Color"), - wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); ScalableButton* btn = m_reset_to_filament_color; btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); btn->SetSize(btn->GetBestSize()); @@ -3758,8 +3729,7 @@ void Tab::toggle_show_hide_incompatible() void Tab::update_show_hide_incompatible_button() { - m_btn_hide_incompatible_presets->SetBitmap_(m_show_incompatible_presets ? - m_bmp_show_incompatible_presets : m_bmp_hide_incompatible_presets); + m_btn_hide_incompatible_presets->SetBitmap(*get_bmp_bundle(m_show_incompatible_presets ? "flag_red" : "flag_green")); m_btn_hide_incompatible_presets->SetToolTip(m_show_incompatible_presets ? "Both compatible an incompatible presets are shown. Click to hide presets not compatible with the current printer." : "Only compatible presets are shown. Click to show both the presets compatible and not compatible with the current printer."); @@ -3808,7 +3778,7 @@ wxSizer* Tab::compatible_widget_create(wxWindow* parent, PresetDependencies &dep deps.checkbox->SetFont(Slic3r::GUI::wxGetApp().normal_font()); wxGetApp().UpdateDarkUI(deps.checkbox, false, true); deps.btn = new ScalableButton(parent, wxID_ANY, "printer", from_u8((boost::format(" %s %s") % _utf8(L("Set")) % std::string(dots.ToUTF8())).str()), - wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); deps.btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); deps.btn->SetSize(deps.btn->GetBestSize()); @@ -4117,7 +4087,7 @@ bool SubstitutionManager::is_empty_substitutions() wxSizer* TabPrint::create_manage_substitution_widget(wxWindow* parent) { auto create_btn = [parent](ScalableButton** btn, const wxString& label, const std::string& icon_name) { - *btn = new ScalableButton(parent, wxID_ANY, icon_name, " " + label + " ", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + *btn = new ScalableButton(parent, wxID_ANY, icon_name, " " + label + " ", wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); (*btn)->SetFont(wxGetApp().normal_font()); (*btn)->SetSize((*btn)->GetBestSize()); }; @@ -4170,7 +4140,7 @@ wxSizer* TabPrint::create_substitutions_widget(wxWindow* parent) wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent) { ScalableButton* btn = new ScalableButton(parent, wxID_ANY, "printer", " " + _(L("Set")) + " " + dots, - wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT, true); + wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT); btn->SetFont(wxGetApp().normal_font()); btn->SetSize(btn->GetBestSize()); @@ -4564,7 +4534,7 @@ void TabSLAMaterial::build() optgroup->append_line(line); - page = add_options_page(L("Notes"), "note.png"); + page = add_options_page(L("Notes"), "note"); optgroup = page->new_optgroup(L("Notes"), 0); optgroup->label_width = 0; Option option = optgroup->get_option("material_notes"); @@ -4572,7 +4542,7 @@ void TabSLAMaterial::build() option.opt.height = 25;//250; optgroup->append_single_option_line(option); - page = add_options_page(L("Dependencies"), "wrench.png"); + page = add_options_page(L("Dependencies"), "wrench"); optgroup = page->new_optgroup(L("Profile dependencies")); create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) { @@ -4593,7 +4563,7 @@ void TabSLAMaterial::build() build_preset_description_line(optgroup.get()); - page = add_options_page(L("Material printing profile"), "note.png"); + page = add_options_page(L("Material printing profile"), "note"); optgroup = page->new_optgroup(L("Material printing profile")); option = optgroup->get_option("material_print_speed"); optgroup->append_single_option_line(option); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index d6f6e0112..6625f71f6 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -174,7 +174,6 @@ protected: wxBoxSizer* m_hsizer; wxBoxSizer* m_left_sizer; wxTreeCtrl* m_treectrl; - wxImageList* m_icons; wxScrolledWindow* m_page_view {nullptr}; wxBoxSizer* m_page_sizer {nullptr}; @@ -203,10 +202,6 @@ protected: ScalableButton* m_undo_to_sys_btn; ScalableButton* m_question_btn; - // Cached bitmaps. - // A "flag" icon to be displayned next to the preset name in the Tab's combo box. - ScalableBitmap m_bmp_show_incompatible_presets; - ScalableBitmap m_bmp_hide_incompatible_presets; // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. ScalableBitmap m_bmp_value_lock; ScalableBitmap m_bmp_value_unlock; @@ -505,7 +500,6 @@ public: void build_unregular_pages(bool from_initial_build = false); void on_preset_loaded() override; void init_options_list() override; - void msw_rescale() override; bool supports_printer_technology(const PrinterTechnology /* tech */) const override { return true; } wxSizer* create_bed_shape_widget(wxWindow* parent); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index cab978284..ec5286abe 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -115,29 +115,21 @@ wxIcon ModelNode::get_bitmap(const wxString& color) wxBitmap ModelNode::get_bitmap(const wxString& color) #endif // __linux__ { - /* It's supposed that standard size of an icon is 48px*16px for 100% scaled display. - * So set sizes for solid_colored icons used for filament preset - * and scale them in respect to em_unit value - */ - const double em = em_unit(m_parent_win); - const int icon_width = lround(6.4 * em); - const int icon_height = lround(1.6 * em); - - BitmapCache bmp_cache; - ColorRGB rgb; - decode_color(into_u8(color), rgb); - // there is no need to scale created solid bitmap + wxBitmap bmp = get_solid_bmp_bundle(64, 16, into_u8(color))->GetBitmapFor(m_parent_win); + if (!m_toggle) + bmp = bmp.ConvertToDisabled(); #ifndef __linux__ - return bmp_cache.mksolid(icon_width, icon_height, rgb, true); + return bmp; #else wxIcon icon; - icon.CopyFromBitmap(bmp_cache.mksolid(icon_width, icon_height, rgb, true)); + icon.CopyFromBitmap(bmp); return icon; #endif // __linux__ } // option node ModelNode::ModelNode(ModelNode* parent, const wxString& text, const wxString& old_value, const wxString& new_value) : + m_parent_win(parent->m_parent_win), m_parent(parent), m_old_color(old_value.StartsWith("#") ? old_value : ""), m_new_color(new_value.StartsWith("#") ? new_value : ""), @@ -202,18 +194,22 @@ void ModelNode::UpdateIcons() { // update icons for the colors, if any exists if (!m_old_color.IsEmpty()) - m_old_color_bmp = get_bitmap(m_toggle ? m_old_color : wxString::FromUTF8(grey.c_str())); + m_old_color_bmp = get_bitmap(m_old_color); if (!m_new_color.IsEmpty()) - m_new_color_bmp = get_bitmap(m_toggle ? m_new_color : wxString::FromUTF8(grey.c_str())); + m_new_color_bmp = get_bitmap(m_new_color); // update main icon, if any exists if (m_icon_name.empty()) return; + wxBitmap bmp = get_bmp_bundle(m_icon_name)->GetBitmapFor(m_parent_win); + if (!m_toggle) + bmp = bmp.ConvertToDisabled(); + #ifdef __linux__ - m_icon.CopyFromBitmap(create_scaled_bitmap(m_icon_name, m_parent_win, 16, !m_toggle)); + m_icon.CopyFromBitmap(bmp); #else - m_icon = create_scaled_bitmap(m_icon_name, m_parent_win, 16, !m_toggle); + m_icon = bmp; #endif //__linux__ } @@ -838,7 +834,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ auto add_btn = [this, buttons, btn_font, dependent_presets](ScalableButton** btn, int& btn_id, const std::string& icon_name, Action close_act, const wxString& label, bool process_enable = true) { - *btn = new ScalableButton(this, btn_id = NewControlId(), icon_name, label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true, 24); + *btn = new ScalableButton(this, btn_id = NewControlId(), icon_name, label, wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, 24); buttons->Add(*btn, 1, wxLEFT, 5); (*btn)->SetFont(btn_font); @@ -876,7 +872,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ if (ActionButtons::SAVE & m_buttons) add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, _L("Save")); - ScalableButton* cancel_btn = new ScalableButton(this, wxID_CANCEL, "cross", _L("Cancel"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true, 24); + ScalableButton* cancel_btn = new ScalableButton(this, wxID_CANCEL, "cross", _L("Cancel"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, 24); buttons->Add(cancel_btn, 1, wxLEFT|wxRIGHT, 5); cancel_btn->SetFont(btn_font); cancel_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { this->EndModal(wxID_CANCEL); }); @@ -1307,8 +1303,6 @@ void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) int em = em_unit(); 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); @@ -1322,7 +1316,7 @@ void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) void UnsavedChangesDialog::on_sys_color_changed() { for (auto btn : { m_save_btn, m_transfer_btn, m_discard_btn } ) - btn->msw_rescale(); + btn->sys_color_changed(); // msw_rescale updates just icons, so use it m_tree->Rescale(); @@ -1720,10 +1714,16 @@ void DiffPresetDialog::on_dpi_changed(const wxRect&) const wxSize& size = wxSize(80 * em, 30 * em); SetMinSize(size); + auto rescale = [em](PresetComboBox* pcb) { + pcb->msw_rescale(); + wxSize sz = wxSize(35 * em, -1); + pcb->SetMinSize(sz); + pcb->SetSize(sz); + }; + for (auto preset_combos : m_preset_combos) { - preset_combos.presets_left->msw_rescale(); - preset_combos.equal_bmp->msw_rescale(); - preset_combos.presets_right->msw_rescale(); + rescale(preset_combos.presets_left); + rescale(preset_combos.presets_right); } m_tree->Rescale(em); @@ -1741,9 +1741,9 @@ void DiffPresetDialog::on_sys_color_changed() #endif for (auto preset_combos : m_preset_combos) { - preset_combos.presets_left->msw_rescale(); - preset_combos.equal_bmp->msw_rescale(); - preset_combos.presets_right->msw_rescale(); + preset_combos.presets_left->sys_color_changed(); + preset_combos.equal_bmp->sys_color_changed(); + preset_combos.presets_right->sys_color_changed(); } // msw_rescale updates just icons, so use it m_tree->Rescale(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 9326c9258..67890bac8 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -24,18 +24,15 @@ #ifndef __linux__ // msw_menuitem_bitmaps is used for MSW and OSX static std::map msw_menuitem_bitmaps; -#ifdef __WXMSW__ -void msw_rescale_menu(wxMenu* menu) +void sys_color_changed_menu(wxMenu* menu) { - return; struct update_icons { static void run(wxMenuItem* item) { const auto it = msw_menuitem_bitmaps.find(item->GetId()); if (it != msw_menuitem_bitmaps.end()) { -// const wxBitmap& item_icon = create_menu_bitmap(it->second); - const wxBitmapBundle& item_icon = create_menu_bitmap(it->second); - if (item_icon.IsOk()) - item->SetBitmap(item_icon); + wxBitmapBundle* item_icon = get_bmp_bundle(it->second); + if (item_icon->IsOk()) + item->SetBitmap(*item_icon); } if (item->IsSubMenu()) for (wxMenuItem *sub_item : item->GetSubMenu()->GetMenuItems()) @@ -46,36 +43,24 @@ void msw_rescale_menu(wxMenu* menu) for (wxMenuItem *item : menu->GetMenuItems()) update_icons::run(item); } -#endif /* __WXMSW__ */ -#endif /* no __WXGTK__ */ +#endif /* no __linux__ */ void enable_menu_item(wxUpdateUIEvent& evt, std::function const cb_condition, wxMenuItem* item, wxWindow* win) { const bool enable = cb_condition(); evt.Enable(enable); - -#ifdef __WXOSX__ - const auto it = msw_menuitem_bitmaps.find(item->GetId()); - if (it != msw_menuitem_bitmaps.end()) - { - const wxBitmap& item_icon = create_scaled_bitmap(it->second, win, 16, !enable); - if (item_icon.IsOk()) - item->SetBitmap(item_icon); - } -#endif // __WXOSX__ } wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, -// std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler, - std::function cb, const wxBitmapBundle& icon, wxEvtHandler* event_handler, + std::function cb, wxBitmapBundle* icon, wxEvtHandler* event_handler, std::function const cb_condition, wxWindow* parent, int insert_pos/* = wxNOT_FOUND*/) { if (id == wxID_ANY) id = wxNewId(); auto *item = new wxMenuItem(menu, id, string, description); - if (icon.IsOk()) { - item->SetBitmap(icon); + if (icon && icon->IsOk()) { + item->SetBitmap(*icon); } if (insert_pos == wxNOT_FOUND) menu->Append(item); @@ -104,14 +89,12 @@ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const if (id == wxID_ANY) id = wxNewId(); -// const wxBitmap& bmp = !icon.empty() ? create_menu_bitmap(icon) : wxNullBitmap; // FIXME: pass window ptr - const wxBitmapBundle& bmp = !icon.empty() ? create_menu_bitmap(icon) : wxNullBitmap; // FIXME: pass window ptr + wxBitmapBundle* bmp = icon.empty() ? nullptr : get_bmp_bundle(icon); -//#ifdef __WXMSW__ -#ifndef __WXGTK__ - if (bmp.IsOk()) +#ifndef __linux__ + if (bmp && bmp->IsOk()) msw_menuitem_bitmaps[id] = icon; -#endif /* __WXMSW__ */ +#endif /* no __linux__ */ return append_menu_item(menu, id, string, description, cb, bmp, event_handler, cb_condition, parent, insert_pos); } @@ -124,7 +107,7 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin wxMenuItem* item = new wxMenuItem(menu, id, string, description); if (!icon.empty()) { - item->SetBitmap(create_menu_bitmap(icon)); // FIXME: pass window ptr + item->SetBitmap(*get_bmp_bundle(icon)); //#ifdef __WXMSW__ #ifndef __WXGTK__ msw_menuitem_bitmaps[id] = icon; @@ -426,11 +409,34 @@ int mode_icon_px_size() #endif } -//wxBitmap create_menu_bitmap(const std::string& bmp_name) -wxBitmapBundle create_menu_bitmap(const std::string& bmp_name) +wxBitmapBundle* get_bmp_bundle(const std::string& bmp_name_in, int px_cnt/* = 16*/) { - // return create_scaled_bitmap(bmp_name, nullptr, 16, false, "", true); - return wxBitmapBundle::FromSVGFile(Slic3r::var(bmp_name + ".svg"), wxSize(16, 16)); + static Slic3r::GUI::BitmapCache cache; + + std::string bmp_name = bmp_name_in; + boost::replace_last(bmp_name, ".png", ""); + + // Try loading an SVG first, then PNG if SVG is not found: + wxBitmapBundle* bmp = cache.from_svg(bmp_name, px_cnt, px_cnt, Slic3r::GUI::wxGetApp().dark_mode()); + if (bmp == nullptr) { + bmp = cache.from_png(bmp_name, px_cnt, px_cnt); + if (!bmp) + // Neither SVG nor PNG has been found, raise error + throw Slic3r::RuntimeError("Could not load bitmap: " + bmp_name); + } + return bmp; +} + +wxBitmapBundle* get_empty_bmp_bundle(int width, int height) +{ + static Slic3r::GUI::BitmapCache cache; + return cache.mkclear_bndl(width, height); +} + +wxBitmapBundle* get_solid_bmp_bundle(int width, int height, const std::string& color ) +{ + static Slic3r::GUI::BitmapCache cache; + return cache.mksolid_bndl(width, height, color, 1, Slic3r::GUI::wxGetApp().dark_mode()); } // win is used to get a correct em_unit value @@ -471,41 +477,19 @@ wxBitmap create_scaled_bitmap( const std::string& bmp_name_in, return *bmp; } -std::vector get_extruder_color_icons(bool thin_icon/* = false*/) +std::vector get_extruder_color_icons(bool thin_icon/* = false*/) { - static Slic3r::GUI::BitmapCache bmp_cache; - // Create the bitmap with color bars. - std::vector bmps; + std::vector bmps; std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); if (colors.empty()) return bmps; - /* It's supposed that standard size of an icon is 36px*16px for 100% scaled display. - * So set sizes for solid_colored icons used for filament preset - * and scale them in respect to em_unit value - */ - const double em = Slic3r::GUI::wxGetApp().em_unit(); - const int icon_width = lround((thin_icon ? 1.6 : 3.2) * em); - const int icon_height = lround(1.6 * em); - bool dark_mode = Slic3r::GUI::wxGetApp().dark_mode(); for (const std::string& color : colors) - { - std::string bitmap_key = color + "-h" + std::to_string(icon_height) + "-w" + std::to_string(icon_width); - - wxBitmap* bitmap = bmp_cache.find(bitmap_key); - if (bitmap == nullptr) { - // Paint the color icon. - Slic3r::ColorRGB rgb; - Slic3r::decode_color(color, rgb); - // there is no neede to scale created solid bitmap - bitmap = bmp_cache.insert(bitmap_key, bmp_cache.mksolid(icon_width, icon_height, rgb, true, 1, dark_mode)); - } - bmps.emplace_back(bitmap); - } + bmps.emplace_back(get_solid_bmp_bundle(thin_icon ? 16 : 32, 16, color)); return bmps; } @@ -518,7 +502,7 @@ void apply_extruder_selector(Slic3r::GUI::BitmapComboBox** ctrl, wxSize size/* = wxDefaultSize*/, bool use_thin_icon/* = false*/) { - std::vector icons = get_extruder_color_icons(use_thin_icon); + std::vector icons = get_extruder_color_icons(use_thin_icon); if (!*ctrl) { *ctrl = new Slic3r::GUI::BitmapComboBox(parent, wxID_ANY, wxEmptyString, pos, size, 0, nullptr, wxCB_READONLY); @@ -544,7 +528,7 @@ void apply_extruder_selector(Slic3r::GUI::BitmapComboBox** ctrl, int i = 0; wxString str = _(L("Extruder")); - for (wxBitmap* bmp : icons) { + for (wxBitmapBundle* bmp : icons) { if (i == 0) { if (!first_item.empty()) (*ctrl)->Append(_(first_item), *bmp); @@ -578,7 +562,7 @@ LockButton::LockButton( wxWindow *parent, Slic3r::GUI::wxGetApp().UpdateDarkUI(this); SetBitmap(m_bmp_lock_open.bmp()); SetBitmapDisabled(m_bmp_lock_open.bmp()); - SetBitmapHover(m_bmp_lock_closed_f.bmp()); + SetBitmapCurrent(m_bmp_lock_closed_f.bmp()); //button events Bind(wxEVT_BUTTON, &LockButton::OnButton, this); @@ -607,13 +591,14 @@ void LockButton::SetLock(bool lock) } } -void LockButton::msw_rescale() +void LockButton::sys_color_changed() { - return; - m_bmp_lock_closed.msw_rescale(); - m_bmp_lock_closed_f.msw_rescale(); - m_bmp_lock_open.msw_rescale(); - m_bmp_lock_open_f.msw_rescale(); + Slic3r::GUI::wxGetApp().UpdateDarkUI(this); + + m_bmp_lock_closed.sys_color_changed(); + m_bmp_lock_closed_f.sys_color_changed(); + m_bmp_lock_open.sys_color_changed(); + m_bmp_lock_open_f.sys_color_changed(); update_button_bitmaps(); } @@ -621,7 +606,7 @@ void LockButton::msw_rescale() void LockButton::update_button_bitmaps() { SetBitmap(m_is_pushed ? m_bmp_lock_closed.bmp() : m_bmp_lock_open.bmp()); - SetBitmapHover(m_is_pushed ? m_bmp_lock_closed_f.bmp() : m_bmp_lock_open_f.bmp()); + SetBitmapCurrent(m_is_pushed ? m_bmp_lock_closed_f.bmp() : m_bmp_lock_open_f.bmp()); Refresh(); Update(); @@ -648,8 +633,7 @@ ModeButton::ModeButton( wxWindow* parent, const wxString& mode/* = wxEmptyString*/, const std::string& icon_name/* = ""*/, int px_cnt/* = 16*/) : -// ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, icon_name, px_cnt), mode, wxBU_EXACTFIT) - ScalableButton(parent, wxID_ANY, icon_name, mode, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT) + ScalableButton(parent, wxID_ANY, icon_name, mode, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT, px_cnt) { Init(mode); } @@ -759,11 +743,10 @@ void ModeSizer::set_items_border(int border) item->SetBorder(border); } -void ModeSizer::msw_rescale() +void ModeSizer::sys_color_changed() { - this->SetHGap(std::lround(m_hgap_unscaled * em_unit(m_parent))); for (size_t m = 0; m < m_mode_btns.size(); m++) - m_mode_btns[m]->msw_rescale(); + m_mode_btns[m]->sys_color_changed(); } // ---------------------------------------------------------------------------- @@ -803,40 +786,13 @@ ScalableBitmap::ScalableBitmap( wxWindow *parent, m_parent(parent), m_icon_name(icon_name), m_px_cnt(px_cnt) { - m_bmp = create_scaled_bitmap(icon_name, parent, px_cnt, grayscale); + m_bmp = *get_bmp_bundle(icon_name, px_cnt); + m_bitmap = m_bmp.GetBitmapFor(m_parent); } -wxSize ScalableBitmap::GetBmpSize() const +void ScalableBitmap::sys_color_changed() { -#ifdef __APPLE__ - return m_bmp.GetScaledSize(); -#else - return m_bmp.GetSize(); -#endif -} - -int ScalableBitmap::GetBmpWidth() const -{ -#ifdef __APPLE__ - return m_bmp.GetScaledWidth(); -#else - return m_bmp.GetWidth(); -#endif -} - -int ScalableBitmap::GetBmpHeight() const -{ -#ifdef __APPLE__ - return m_bmp.GetScaledHeight(); -#else - return m_bmp.GetHeight(); -#endif -} - - -void ScalableBitmap::msw_rescale() -{ - m_bmp = create_scaled_bitmap(m_icon_name, m_parent, m_px_cnt, m_grayscale); + m_bmp = *get_bmp_bundle(m_icon_name, m_px_cnt); } // ---------------------------------------------------------------------------- @@ -850,11 +806,9 @@ ScalableButton::ScalableButton( wxWindow * parent, const wxSize& size /* = wxDefaultSize*/, const wxPoint& pos /* = wxDefaultPosition*/, long style /*= wxBU_EXACTFIT | wxNO_BORDER*/, - bool use_default_disabled_bitmap/* = false*/, int bmp_px_cnt/* = 16*/) : m_parent(parent), m_current_icon_name(icon_name), - m_use_default_disabled_bitmap (use_default_disabled_bitmap), m_px_cnt(bmp_px_cnt), m_has_border(!(style & wxNO_BORDER)) { @@ -862,10 +816,7 @@ ScalableButton::ScalableButton( wxWindow * parent, Slic3r::GUI::wxGetApp().UpdateDarkUI(this); if (!icon_name.empty()) { -// SetBitmap(create_scaled_bitmap(icon_name, parent, m_px_cnt)); - SetBitmap(wxBitmapBundle::FromSVGFile(Slic3r::var(icon_name + ".svg"), wxSize(m_px_cnt, m_px_cnt))); - //if (m_use_default_disabled_bitmap) - // SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); + SetBitmap(*get_bmp_bundle(icon_name, m_px_cnt)); if (!label.empty()) SetBitmapMargins(int(0.5* em_unit(parent)), 0); } @@ -907,14 +858,12 @@ bool ScalableButton::SetBitmap_(const std::string& bmp_name) if (m_current_icon_name.empty()) return false; -// wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt); - wxBitmapBundle bmp = wxBitmapBundle::FromSVGFile(Slic3r::var(m_current_icon_name + ".svg"), wxSize(16, 16)); + wxBitmapBundle bmp = *get_bmp_bundle(m_current_icon_name, m_px_cnt); SetBitmap(bmp); SetBitmapCurrent(bmp); SetBitmapPressed(bmp); SetBitmapFocus(bmp); - if (m_use_default_disabled_bitmap) - SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); + SetBitmapDisabled(bmp); return true; } @@ -933,38 +882,21 @@ int ScalableButton::GetBitmapHeight() #endif } -void ScalableButton::UseDefaultBitmapDisabled() -{ - m_use_default_disabled_bitmap = true; - SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); -} - -void ScalableButton::msw_rescale() +void ScalableButton::sys_color_changed() { Slic3r::GUI::wxGetApp().UpdateDarkUI(this, m_has_border); -// if (!m_current_icon_name.empty()) { - if (0) { - wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt); - SetBitmap(bmp); - SetBitmapCurrent(bmp); - SetBitmapPressed(bmp); - SetBitmapFocus(bmp); - if (!m_disabled_icon_name.empty()) - SetBitmapDisabled(create_scaled_bitmap(m_disabled_icon_name, m_parent, m_px_cnt)); - else if (m_use_default_disabled_bitmap) - SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); - } - - if (m_width > 0 || m_height>0) - { - const int em = em_unit(m_parent); - wxSize size(m_width * em, m_height * em); - SetMinSize(size); - } + wxBitmapBundle bmp = *get_bmp_bundle(m_current_icon_name, m_px_cnt); + SetBitmap(bmp); + SetBitmapCurrent(bmp); + SetBitmapPressed(bmp); + SetBitmapFocus(bmp); + if (!m_disabled_icon_name.empty()) + SetBitmapDisabled(*get_bmp_bundle(m_disabled_icon_name, m_px_cnt)); + if (!GetLabelText().IsEmpty()) + SetBitmapMargins(int(0.5 * em_unit(m_parent)), 0); } - // ---------------------------------------------------------------------------- // BlinkingBitmap // ---------------------------------------------------------------------------- @@ -975,13 +907,6 @@ BlinkingBitmap::BlinkingBitmap(wxWindow* parent, const std::string& icon_name) : bmp = ScalableBitmap(parent, icon_name); } -void BlinkingBitmap::msw_rescale() -{ - bmp.msw_rescale(); - this->SetSize(bmp.GetBmpSize()); - this->SetMinSize(bmp.GetBmpSize()); -} - void BlinkingBitmap::invalidate() { this->SetBitmap(wxNullBitmap); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 8387551f2..a22622d39 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -16,15 +16,14 @@ #include -#ifdef __WXMSW__ -void msw_rescale_menu(wxMenu* menu); -#else /* __WXMSW__ */ -inline void msw_rescale_menu(wxMenu* /* menu */) {} -#endif /* __WXMSW__ */ +#ifndef __linux__ +void sys_color_changed_menu(wxMenu* menu); +#else +inline void sys_color_changed_menu(wxMenu* /* menu */) {} +#endif // no __linux__ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, -// std::function cb, const wxBitmap& icon, wxEvtHandler* event_handler = nullptr, - std::function cb, const wxBitmapBundle& icon, wxEvtHandler* event_handler = nullptr, + std::function cb, wxBitmapBundle* icon, wxEvtHandler* event_handler = nullptr, std::function const cb_condition = []() { return true;}, wxWindow* parent = nullptr, int insert_pos = wxNOT_FOUND); wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, const std::string& icon = "", wxEvtHandler* event_handler = nullptr, @@ -51,15 +50,16 @@ void msw_buttons_rescale(wxDialog* dlg, const int em_unit, const std::vector< int em_unit(wxWindow* win); int mode_icon_px_size(); -//wxBitmap create_menu_bitmap(const std::string& bmp_name); -wxBitmapBundle create_menu_bitmap(const std::string& bmp_name); +wxBitmapBundle* get_bmp_bundle(const std::string& bmp_name, int px_cnt = 16); +wxBitmapBundle* get_empty_bmp_bundle(int width, int height); +wxBitmapBundle* get_solid_bmp_bundle(int width, int height, const std::string& color); wxBitmap create_scaled_bitmap(const std::string& bmp_name, wxWindow *win = nullptr, const int px_cnt = 16, const bool grayscale = false, const std::string& new_color = std::string(), // color witch will used instead of orange const bool menu_bitmap = false); -std::vector get_extruder_color_icons(bool thin_icon = false); +std::vector get_extruder_color_icons(bool thin_icon = false); namespace Slic3r { namespace GUI { @@ -148,21 +148,28 @@ public: ~ScalableBitmap() {} - wxSize GetBmpSize() const; - int GetBmpWidth() const; - int GetBmpHeight() const; + void sys_color_changed(); - void msw_rescale(); + const wxBitmapBundle& bmp() const { return m_bmp; } + wxBitmap get_bitmap() { return m_bmp.GetBitmapFor(m_parent); } + wxWindow* parent() const { return m_parent;} + const std::string& name() const { return m_icon_name; } + int px_cnt() const { return m_px_cnt;} - const wxBitmap& bmp() const { return m_bmp; } - wxBitmap& bmp() { return m_bmp; } - const std::string& name() const{ return m_icon_name; } - - int px_cnt()const {return m_px_cnt;} + wxSize GetSize() const { +#ifdef __APPLE__ + return m_bmp.GetDefaultSize(); +#else + return m_bmp.GetPreferredBitmapSizeFor(m_parent); +#endif + } + int GetWidth() const { return GetSize().GetWidth(); } + int GetHeight() const { return GetSize().GetHeight(); } private: wxWindow* m_parent{ nullptr }; - wxBitmap m_bmp = wxBitmap(); + wxBitmapBundle m_bmp = wxBitmapBundle(); + wxBitmap m_bitmap = wxBitmap(); std::string m_icon_name = ""; int m_px_cnt {16}; bool m_grayscale {false}; @@ -192,7 +199,7 @@ public: void enable() { m_disabled = false; } void disable() { m_disabled = true; } - void msw_rescale(); + void sys_color_changed(); protected: void update_button_bitmaps(); @@ -224,7 +231,6 @@ public: const wxSize& size = wxDefaultSize, const wxPoint& pos = wxDefaultPosition, long style = wxBU_EXACTFIT | wxNO_BORDER, - bool use_default_disabled_bitmap = false, int bmp_px_cnt = 16); ScalableButton( @@ -240,9 +246,8 @@ public: bool SetBitmap_(const std::string& bmp_name); void SetBitmapDisabled_(const ScalableBitmap &bmp); int GetBitmapHeight(); - void UseDefaultBitmapDisabled(); - void msw_rescale(); + void sys_color_changed(); private: wxWindow* m_parent { nullptr }; @@ -251,8 +256,6 @@ private: int m_width {-1}; // should be multiplied to em_unit int m_height{-1}; // should be multiplied to em_unit - bool m_use_default_disabled_bitmap {false}; - // bitmap dimensions int m_px_cnt{ 16 }; bool m_has_border {false}; @@ -318,7 +321,7 @@ public: void set_items_flag(int flag); void set_items_border(int border); - void msw_rescale(); + void sys_color_changed(); const std::vector& get_btns() { return m_mode_btns; } private: @@ -366,12 +369,11 @@ public: ~BlinkingBitmap() {} - void msw_rescale(); void invalidate(); void activate(); void blink(); - const wxBitmap& get_bmp() const { return bmp.bmp(); } + const wxBitmapBundle& get_bmp() const { return bmp.bmp(); } private: ScalableBitmap bmp; From ae08819a2430e0764fab69cc0d03c50820e046a8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Fri, 3 Jun 2022 12:49:21 +0200 Subject: [PATCH 165/169] Using of wxWidgets 3.1.6 WIP: Linux/OSX specific fixes OSX specific: Fixed get_mouse_position_in_control(). + Use GetItemRect() to calculation of the position and size for Extruder selector Linux specific: * Use just 1.0 scale for wxBitmapComboboxes under GTK3 and gtk3 * GTK2 specific: use GTK2 doesn't suppost get_scale, so scale bitmap size form em_unit() --- src/slic3r/GUI/BitmapCache.cpp | 129 ++++------------------------ src/slic3r/GUI/BitmapCache.hpp | 7 -- src/slic3r/GUI/ExtraRenderers.cpp | 23 +++-- src/slic3r/GUI/GUI_ObjectList.cpp | 26 ++++-- src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- src/slic3r/GUI/OG_CustomCtrl.cpp | 6 +- src/slic3r/GUI/PresetComboBoxes.cpp | 9 +- src/slic3r/GUI/wxExtensions.cpp | 27 ++++-- src/slic3r/GUI/wxExtensions.hpp | 6 +- 9 files changed, 85 insertions(+), 150 deletions(-) diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index d2bf98b5b..147615277 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -65,6 +65,8 @@ wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vec wxVector bitmaps; std::set scales = {1.0}; +#ifndef __linux__ + #ifdef __APPLE__ scales.emplace(m_scale); #else @@ -73,12 +75,14 @@ wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vec scales.emplace(wxDisplay(disp).GetScaleFactor()); #endif +#endif // !__linux__ + for (double scale : scales) { size_t width = 0; size_t height = 0; for (const wxBitmapBundle* bmp_bndl : bmps) { #ifdef __APPLE__ - wxSize size = bmp_bndl->GetPreferredBitmapSizeAtScale(1.0); + wxSize size = bmp_bndl->GetDefaultSize(); #else wxSize size = bmp_bndl->GetPreferredBitmapSizeAtScale(scale); #endif @@ -98,7 +102,7 @@ wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vec memset(image.GetAlpha(), 0, width * height); size_t x = 0; for (const wxBitmapBundle* bmp_bndl : bmps) { - wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetPreferredBitmapSizeAtScale(scale)); + wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetDefaultSize()); if (bmp.GetWidth() > 0) { if (bmp.GetDepth() == 32) { wxAlphaPixelData data(bmp); @@ -138,7 +142,7 @@ wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vec } } } - x += bmp.GetWidth(); + x += bmp.GetScaledWidth(); } bitmaps.push_back(* this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)))); @@ -156,8 +160,8 @@ wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vec if (bmp.GetWidth() > 0) memDC.DrawBitmap(bmp, x, 0, true); -#ifdef __APPLE__ // we should "move" with step equal to non-scaled width +#ifdef __APPLE__ x += bmp.GetScaledWidth(); #else x += bmp.GetWidth(); @@ -262,110 +266,6 @@ wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp return bitmap; } -wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2) -{ - // Copying the wxBitmaps is cheap as the bitmap's content is reference counted. - const wxBitmap bmps[2] = { bmp, bmp2 }; - return this->insert(bitmap_key, bmps, bmps + 2); -} - -wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3) -{ - // Copying the wxBitmaps is cheap as the bitmap's content is reference counted. - const wxBitmap bmps[3] = { bmp, bmp2, bmp3 }; - return this->insert(bitmap_key, bmps, bmps + 3); -} - -wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *begin, const wxBitmap *end) -{ - size_t width = 0; - size_t height = 0; - for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { -#ifdef __APPLE__ - width += bmp->GetScaledWidth(); - height = std::max(height, bmp->GetScaledHeight()); -#else - width += bmp->GetWidth(); - height = std::max(height, bmp->GetHeight()); -#endif - } - -#ifdef __WXGTK2__ - // Broken alpha workaround - wxImage image(width, height); - image.InitAlpha(); - // Fill in with a white color. - memset(image.GetData(), 0x0ff, width * height * 3); - // Fill in with full transparency. - memset(image.GetAlpha(), 0, width * height); - size_t x = 0; - for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { - if (bmp->GetWidth() > 0) { - if (bmp->GetDepth() == 32) { - wxAlphaPixelData data(*const_cast(bmp)); - //FIXME The following method is missing from wxWidgets 3.1.1. - // It looks like the wxWidgets 3.0.3 called the wrapped bitmap's UseAlpha(). - //data.UseAlpha(); - if (data) { - for (int r = 0; r < bmp->GetHeight(); ++ r) { - wxAlphaPixelData::Iterator src(data); - src.Offset(data, 0, r); - unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3; - unsigned char *dst_alpha = image.GetAlpha() + x + r * width; - for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) { - *dst_pixels ++ = src.Red(); - *dst_pixels ++ = src.Green(); - *dst_pixels ++ = src.Blue(); - *dst_alpha ++ = src.Alpha(); - } - } - } - } else if (bmp->GetDepth() == 24) { - wxNativePixelData data(*const_cast(bmp)); - if (data) { - for (int r = 0; r < bmp->GetHeight(); ++ r) { - wxNativePixelData::Iterator src(data); - src.Offset(data, 0, r); - unsigned char *dst_pixels = image.GetData() + (x + r * width) * 3; - unsigned char *dst_alpha = image.GetAlpha() + x + r * width; - for (int c = 0; c < bmp->GetWidth(); ++ c, ++ src) { - *dst_pixels ++ = src.Red(); - *dst_pixels ++ = src.Green(); - *dst_pixels ++ = src.Blue(); - *dst_alpha ++ = wxALPHA_OPAQUE; - } - } - } - } - } - x += bmp->GetWidth(); - } - return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image))); - -#else - - wxBitmap *bitmap = this->insert(bitmap_key, width, height); - wxMemoryDC memDC; - memDC.SelectObject(*bitmap); - memDC.SetBackground(*wxTRANSPARENT_BRUSH); - memDC.Clear(); - size_t x = 0; - for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) { - if (bmp->GetWidth() > 0) - memDC.DrawBitmap(*bmp, x, 0, true); -#ifdef __APPLE__ - // we should "move" with step equal to non-scaled width - x += bmp->GetScaledWidth(); -#else - x += bmp->GetWidth(); -#endif - } - memDC.SelectObject(wxNullBitmap); - return bitmap; - -#endif -} - wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale/* = false*/) { wxImage image(width, height); @@ -492,7 +392,6 @@ wxBitmapBundle* BitmapCache::from_svg(const std::string& bitmap_name, unsigned t std::string bitmap_key = bitmap_name + (target_height != 0 ? "-h" + std::to_string(target_height) : "-w" + std::to_string(target_width)) -// + (m_scale != 1.0f ? "-s" + float_to_string_decimal_point(m_scale) : "") + (dark_mode ? "-dm" : "") + new_color; @@ -594,9 +493,9 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_ return this->insert_raw_rgba(bitmap_key, width, height, data.data(), grayscale); } - +/* //we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap -wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling/* = false*/, size_t border_width /*= 0*/, bool dark_mode/* = false*/) +wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling/* = false* /, size_t border_width /*= 0* /, bool dark_mode/* = false* /) { double scale = suppress_scaling ? 1.0f : m_scale; width *= scale; @@ -638,13 +537,15 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi return wxImage_to_wxBitmap_with_alpha(std::move(image), scale); } - +*/ //we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap wxBitmapBundle BitmapCache::mksolid(size_t width_in, size_t height_in, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, size_t border_width /*= 0*/, bool dark_mode/* = false*/) { wxVector bitmaps; std::set scales = { 1.0 }; +#ifndef __linux__ + #ifdef __APPLE__ scales.emplace(m_scale); #else @@ -653,6 +554,8 @@ wxBitmapBundle BitmapCache::mksolid(size_t width_in, size_t height_in, unsigned scales.emplace(wxDisplay(disp).GetScaleFactor()); #endif +#endif // !__linux__ + for (double scale : scales) { size_t width = width_in * scale; size_t height = height_in * scale; @@ -698,7 +601,7 @@ wxBitmapBundle BitmapCache::mksolid(size_t width_in, size_t height_in, unsigned wxBitmapBundle* BitmapCache::mksolid_bndl(size_t width, size_t height, const std::string& color, size_t border_width, bool dark_mode) { - std::string bitmap_key = (color.empty() ? "empty-w" : color) + "-h" + std::to_string(height) + "-w" + std::to_string(width) + (dark_mode ? "-dm" : ""); + std::string bitmap_key = (color.empty() ? "empty" : color) + "-h" + std::to_string(height) + "-w" + std::to_string(width) + (dark_mode ? "-dm" : ""); wxBitmapBundle* bndl = nullptr; auto it = m_bndl_map.find(bitmap_key); diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index 28058d94b..6ba9ae15b 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -37,10 +37,6 @@ public: wxBitmap* insert(const std::string &name, size_t width, size_t height, double scale = -1.0); wxBitmap* insert(const std::string &name, const wxBitmap &bmp); - wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2); - wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3); - wxBitmap* insert(const std::string &name, const std::vector &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); } - wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end); wxBitmap* insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale = false); // Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero. @@ -58,9 +54,6 @@ public: // Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width. wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::string& new_color = ""); - wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false); - wxBitmap mksolid(size_t width, size_t height, const ColorRGB& rgb, bool suppress_scaling = false, size_t border_width = 0, bool dark_mode = false) { return mksolid(width, height, rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar(), wxALPHA_OPAQUE, suppress_scaling, border_width, dark_mode); } - wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT, true, 0); } wxBitmapBundle mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, size_t border_width = 0, bool dark_mode = false); wxBitmapBundle* mksolid_bndl(size_t width, size_t height, const std::string& color = std::string(), size_t border_width = 0, bool dark_mode = false); wxBitmapBundle* mkclear_bndl(size_t width, size_t height) { return mksolid_bndl(width, height); } diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 9bccb6b63..0368a2fe2 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -33,6 +33,15 @@ wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject) IMPLEMENT_VARIANT_OBJECT(DataViewBitmapText) +static wxSize get_size(const wxBitmap& icon) +{ +#ifdef __WIN32__ + return icon.GetSize(); +#else + return icon.GetScaledSize(); +#endif +} + // --------------------------------------------------------- // BitmapTextRenderer // --------------------------------------------------------- @@ -124,11 +133,7 @@ bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) const wxBitmap& icon = m_value.GetBitmap(); if (icon.IsOk()) { -#ifdef __APPLE__ - wxSize icon_sz = icon.GetScaledSize(); -#else - wxSize icon_sz = icon.GetSize(); -#endif + wxSize icon_sz = get_size(icon); dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.y) / 2); xoffset = icon_sz.x + 4; } @@ -264,11 +269,13 @@ bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state) const wxBitmap& icon = m_value.GetBitmap(); if (icon.IsOk()) { - dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); - xoffset = icon.GetWidth() + 4; + wxSize icon_sz = get_size(icon); + + dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.GetHeight()) / 2); + xoffset = icon_sz.GetWidth() + 4; if (rect.height==0) - rect.height= icon.GetHeight(); + rect.height= icon_sz.GetHeight(); } #ifdef _WIN32 diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 1171149b6..da8f83388 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -971,12 +971,11 @@ void ObjectList::extruder_editing() if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject))) return; - const int column_width = GetColumn(colExtruder)->GetWidth() + wxSystemSettings::GetMetric(wxSYS_VSCROLL_X) + 5; - - wxPoint pos = this->get_mouse_position_in_control(); - wxSize size = wxSize(column_width, -1); - pos.x = GetColumn(colName)->GetWidth() + GetColumn(colPrint)->GetWidth() + 5; - pos.y -= GetTextExtent("m").y; + wxRect rect = this->GetItemRect(item, GetColumn(colExtruder)); + wxPoint pos = rect.GetPosition(); + pos.y -= 4; + wxSize size = rect.GetSize(); + size.SetWidth(size.GetWidth() + 8); apply_extruder_selector(&m_extruder_editor, this, L("default"), pos, size); @@ -2433,6 +2432,21 @@ bool ObjectList::can_merge_to_single_object() const return (*m_objects)[obj_idx]->volumes.size() > 1; } +wxPoint ObjectList::get_mouse_position_in_control() const +{ + wxPoint pt = wxGetMousePosition() - this->GetScreenPosition(); + +#ifdef __APPLE__ + // Workaround for OSX. From wxWidgets 3.1.6 Hittest doesn't respect to the header of wxDataViewCtrl + if (wxDataViewItem top_item = this->GetTopItem(); top_item.IsOk()) { + auto rect = this->GetItemRect(top_item, this->GetColumn(0)); + pt.y -= rect.y; + } +#endif // __APPLE__ + + return pt; +} + // NO_PARAMETERS function call means that changed object index will be determine from Selection() void ObjectList::changed_object(const int obj_idx/* = -1*/) const { diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index b9b816b7b..c838203ae 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -280,7 +280,7 @@ public: bool can_merge_to_multipart_object() const; bool can_merge_to_single_object() const; - wxPoint get_mouse_position_in_control() const { return wxGetMousePosition() - this->GetScreenPosition(); } + wxPoint get_mouse_position_in_control() const; wxBoxSizer* get_sizer() {return m_sizer;} int get_selected_obj_idx() const; ModelConfig& get_item_config(const wxDataViewItem& item) const; diff --git a/src/slic3r/GUI/OG_CustomCtrl.cpp b/src/slic3r/GUI/OG_CustomCtrl.cpp index c202de5e2..2c367d62a 100644 --- a/src/slic3r/GUI/OG_CustomCtrl.cpp +++ b/src/slic3r/GUI/OG_CustomCtrl.cpp @@ -21,10 +21,10 @@ static bool is_point_in_rect(const wxPoint& pt, const wxRect& rect) static wxSize get_bitmap_size(const wxBitmapBundle* bmp, wxWindow* parent) { -#ifdef __APPLE__ - return bmp->GetDefaultSize(); -#else +#ifdef __WIN32__ return bmp->GetBitmapFor(parent).GetSize(); +#else + return bmp->GetDefaultSize(); #endif } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 93a5fe433..597c30312 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -389,14 +389,14 @@ void PresetComboBox::sys_color_changed() void PresetComboBox::fill_width_height() { - icon_height = m_bitmapCompatible->GetPreferredBitmapSizeAtScale(1.0).GetHeight(); - norm_icon_width = m_bitmapCompatible->GetPreferredBitmapSizeAtScale(1.0).GetWidth(); - - null_icon_width = 2 * norm_icon_width; + icon_height = 16; + norm_icon_width = 16; thin_icon_width = 8; wide_icon_width = norm_icon_width + thin_icon_width; + null_icon_width = 2 * norm_icon_width; + space_icon_width = 2; thin_space_icon_width = 4; wide_space_icon_width = 6; @@ -484,6 +484,7 @@ wxBitmapBundle* PresetComboBox::get_bmp( std::string bitmap_key, const std::str wxBitmapBundle PresetComboBox::NullBitmapBndl() { + assert(null_icon_width > 0); return *get_empty_bmp_bundle(null_icon_width, icon_height); } diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 67890bac8..c397b4b39 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -108,10 +108,10 @@ wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxStrin wxMenuItem* item = new wxMenuItem(menu, id, string, description); if (!icon.empty()) { item->SetBitmap(*get_bmp_bundle(icon)); -//#ifdef __WXMSW__ -#ifndef __WXGTK__ + +#ifndef __linux__ msw_menuitem_bitmaps[id] = icon; -#endif /* __WXMSW__ */ +#endif // no __linux__ } item->SetSubMenu(sub_menu); @@ -409,8 +409,19 @@ int mode_icon_px_size() #endif } +#ifdef __WXGTK2__ +static int scale() +{ + return int(em_unit(nullptr) * 0.1f + 0.5f); +} +#endif // __WXGTK2__ + wxBitmapBundle* get_bmp_bundle(const std::string& bmp_name_in, int px_cnt/* = 16*/) { +#ifdef __WXGTK2__ + px_cnt *= scale(); +#endif // __WXGTK2__ + static Slic3r::GUI::BitmapCache cache; std::string bmp_name = bmp_name_in; @@ -430,13 +441,21 @@ wxBitmapBundle* get_bmp_bundle(const std::string& bmp_name_in, int px_cnt/* = 16 wxBitmapBundle* get_empty_bmp_bundle(int width, int height) { static Slic3r::GUI::BitmapCache cache; +#ifdef __WXGTK2__ + return cache.mkclear_bndl(width * scale(), height * scale()); +#else return cache.mkclear_bndl(width, height); +#endif // __WXGTK2__ } wxBitmapBundle* get_solid_bmp_bundle(int width, int height, const std::string& color ) { static Slic3r::GUI::BitmapCache cache; +#ifdef __WXGTK2__ + return cache.mksolid_bndl(width * scale(), height * scale(), color, 1, Slic3r::GUI::wxGetApp().dark_mode()); +#else return cache.mksolid_bndl(width, height, color, 1, Slic3r::GUI::wxGetApp().dark_mode()); +#endif // __WXGTK2__ } // win is used to get a correct em_unit value @@ -486,8 +505,6 @@ std::vector get_extruder_color_icons(bool thin_icon/* = false*/ if (colors.empty()) return bmps; - bool dark_mode = Slic3r::GUI::wxGetApp().dark_mode(); - for (const std::string& color : colors) bmps.emplace_back(get_solid_bmp_bundle(thin_icon ? 16 : 32, 16, color)); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index a22622d39..db05af9eb 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -157,10 +157,10 @@ public: int px_cnt() const { return m_px_cnt;} wxSize GetSize() const { -#ifdef __APPLE__ - return m_bmp.GetDefaultSize(); -#else +#ifdef __WIN32__ return m_bmp.GetPreferredBitmapSizeFor(m_parent); +#else + return m_bmp.GetDefaultSize(); #endif } int GetWidth() const { return GetSize().GetWidth(); } From b6e6be27c073ed5c316e6796e153b1c63916b179 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 22 Jun 2022 15:57:56 +0200 Subject: [PATCH 166/169] wxWidgets.cmake: Changed url and SHA to use next commit in our wxWidgets patch --- deps/wxWidgets/wxWidgets.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 4a0875d62..96d56c0bc 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -13,8 +13,8 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for endif() prusaslicer_add_cmake_project(wxWidgets - URL https://github.com/prusa3d/wxWidgets/archive/5412ac15586da3ecb6952fcc875d2a23366c998f.zip - URL_HASH SHA256=85a6e13152289fbf1ea51f221fbe1452e7914bbaa665b89536780810e93948a6 + URL https://github.com/prusa3d/wxWidgets/archive/e616df45b3a8aedc31beb5540df793dd1f0f3914.zip + URL_HASH SHA256=30bc6cad64dce5cdc755e3a4119cfeb24c12d43279e1e062d8ac350d3880f315 DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG CMAKE_ARGS -DwxBUILD_PRECOMP=ON From 40afbe6371b2057c16100f6e80b8bb7d6c6893ae Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 19 Jan 2022 13:24:37 +0100 Subject: [PATCH 167/169] Update to GLEW 2.2 to prevent initialization crash with wx >= 3.1.6 Revert "Revert to GLEW 2.1 as most Linux distros as using that" This reverts commit 46c8f82f24127dc992e904b9b3f1c75a719f0491. --- deps/GLEW/GLEW.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/GLEW/GLEW.cmake b/deps/GLEW/GLEW.cmake index ae2832577..76ffc0a8d 100644 --- a/deps/GLEW/GLEW.cmake +++ b/deps/GLEW/GLEW.cmake @@ -4,8 +4,8 @@ find_package(OpenGL QUIET REQUIRED) prusaslicer_add_cmake_project( GLEW - URL https://sourceforge.net/projects/glew/files/glew/2.1.0/glew-2.1.0.zip - URL_HASH SHA256=2700383d4de2455f06114fbaf872684f15529d4bdc5cdea69b5fb0e9aa7763f1 + URL https://sourceforge.net/projects/glew/files/glew/2.2.0/glew-2.2.0.zip + URL_HASH SHA256=a9046a913774395a095edcc0b0ac2d81c3aacca61787b39839b941e9be14e0d4 SOURCE_SUBDIR build/cmake CMAKE_ARGS -DBUILD_UTILS=OFF From 0bc707e540d77d576405bf799f3635973ce1d55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Mon, 27 Jun 2022 09:25:38 +0200 Subject: [PATCH 168/169] Tried to disable EGL. --- deps/wxWidgets/wxWidgets.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake index 96d56c0bc..e2d48ce65 100644 --- a/deps/wxWidgets/wxWidgets.cmake +++ b/deps/wxWidgets/wxWidgets.cmake @@ -37,6 +37,7 @@ prusaslicer_add_cmake_project(wxWidgets -DwxUSE_EXPAT=sys -DwxUSE_LIBSDL=OFF -DwxUSE_XTEST=OFF + -DwxUSE_GLCANVAS_EGL=OFF ) if (MSVC) From 3d03ef015f435852fd1ae1961471c2e8aade77c6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 27 Jun 2022 14:27:26 +0200 Subject: [PATCH 169/169] OSX specific: Fixed a warning --- src/slic3r/GUI/GUI_App.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 0412bca21..42e325de7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2088,7 +2088,15 @@ bool GUI_App::load_language(wxString language, bool initial) { // Allocating a temporary locale will switch the default wxTranslations to its internal wxTranslations instance. wxLocale temp_locale; +#ifdef __WXOSX__ + // ysFIXME - temporary workaround till it isn't fixed in wxWidgets: + // Use English as an initial language, because of under OSX it try to load "inappropriate" language for wxLANGUAGE_DEFAULT. + // For example in our case it's trying to load "en_CZ" and as a result PrusaSlicer catch warning message. + // But wxWidgets guys work on it. + temp_locale.Init(wxLANGUAGE_ENGLISH); +#else temp_locale.Init(); +#endif // __WXOSX__ // Set the current translation's language to default, otherwise GetBestTranslation() may not work (see the wxWidgets source code). wxTranslations::Get()->SetLanguage(wxLANGUAGE_DEFAULT); // Let the wxFileTranslationsLoader enumerate all translation dictionaries for PrusaSlicer