diff --git a/sandboxes/CMakeLists.txt b/sandboxes/CMakeLists.txt index a2bd13bb0..23c15f089 100644 --- a/sandboxes/CMakeLists.txt +++ b/sandboxes/CMakeLists.txt @@ -2,3 +2,4 @@ #add_subdirectory(openvdb) add_subdirectory(meshboolean) add_subdirectory(opencsg) +#add_subdirectory(aabb-evaluation) \ No newline at end of file diff --git a/sandboxes/aabb-evaluation/CMakeLists.txt b/sandboxes/aabb-evaluation/CMakeLists.txt new file mode 100644 index 000000000..20011e345 --- /dev/null +++ b/sandboxes/aabb-evaluation/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(aabb-evaluation aabb-evaluation.cpp) +target_link_libraries(aabb-evaluation libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES} ${CMAKE_DL_LIBS}) diff --git a/sandboxes/aabb-evaluation/aabb-evaluation.cpp b/sandboxes/aabb-evaluation/aabb-evaluation.cpp new file mode 100644 index 000000000..9ec7451e5 --- /dev/null +++ b/sandboxes/aabb-evaluation/aabb-evaluation.cpp @@ -0,0 +1,224 @@ +#include +#include +#include + +#include +#include +#include + +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif +#include +#include +#include +#include +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +const std::string USAGE_STR = { + "Usage: aabb-evaluation stlfilename.stl" +}; + +using namespace Slic3r; + +void profile(const TriangleMesh &mesh) +{ + Eigen::MatrixXd V; + Eigen::MatrixXi F; + Eigen::MatrixXd vertex_normals; + sla::to_eigen_mesh(mesh, V, F); + igl::per_vertex_normals(V, F, vertex_normals); + + static constexpr int num_samples = 100; + const int num_vertices = std::min(10000, int(mesh.its.vertices.size())); + const Eigen::MatrixXd dirs = igl::random_dir_stratified(num_samples).cast(); + + Eigen::MatrixXd occlusion_output0; + { + AABBTreeIndirect::Tree3f tree; + { + PROFILE_BLOCK(AABBIndirect_Init); + tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(mesh.its.vertices, mesh.its.indices); + } + { + PROFILE_BLOCK(EigenMesh3D_AABBIndirectF_AmbientOcclusion); + occlusion_output0.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = mesh.its.vertices[ivertex].template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABBTreeIndirect::intersect_ray_first_hit(mesh.its.vertices, mesh.its.indices, tree, (origin + 1e-4 * d).eval(), d, hit)) + ++ num_hits; + } + occlusion_output0(ivertex) = (double)num_hits/(double)num_samples; + } + } + + { + PROFILE_BLOCK(EigenMesh3D_AABBIndirectFF_AmbientOcclusion); + occlusion_output0.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = mesh.its.vertices[ivertex].template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABBTreeIndirect::intersect_ray_first_hit(mesh.its.vertices, mesh.its.indices, tree, + Eigen::Vector3f((origin + 1e-4 * d).template cast()), + Eigen::Vector3f(d.template cast()), hit)) + ++ num_hits; + } + occlusion_output0(ivertex) = (double)num_hits/(double)num_samples; + } + } + } + + Eigen::MatrixXd occlusion_output1; + { + std::vector vertices; + std::vector triangles; + for (int i = 0; i < V.rows(); ++ i) + vertices.emplace_back(V.row(i).transpose()); + for (int i = 0; i < F.rows(); ++ i) + triangles.emplace_back(F.row(i).transpose()); + AABBTreeIndirect::Tree3d tree; + { + PROFILE_BLOCK(AABBIndirectD_Init); + tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(vertices, triangles); + } + + { + PROFILE_BLOCK(EigenMesh3D_AABBIndirectD_AmbientOcclusion); + occlusion_output1.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = V.row(ivertex).template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABBTreeIndirect::intersect_ray_first_hit(vertices, triangles, tree, Eigen::Vector3d(origin + 1e-4 * d), d, hit)) + ++ num_hits; + } + occlusion_output1(ivertex) = (double)num_hits/(double)num_samples; + } + } + } + + // Build the AABB accelaration tree + + Eigen::MatrixXd occlusion_output2; + { + igl::AABB AABB; + { + PROFILE_BLOCK(EigenMesh3D_AABB_Init); + AABB.init(V, F); + } + { + PROFILE_BLOCK(EigenMesh3D_AABB_AmbientOcclusion); + occlusion_output2.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = V.row(ivertex).template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABB.intersect_ray(V, F, origin + 1e-4 * d, d, hit)) + ++ num_hits; + } + occlusion_output2(ivertex) = (double)num_hits/(double)num_samples; + } + } + } + + Eigen::MatrixXd occlusion_output3; + { + typedef Eigen::Map> MapMatrixXfUnaligned; + typedef Eigen::Map> MapMatrixXiUnaligned; + igl::AABB AABB; + auto vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), mesh.its.vertices.size(), 3); + auto faces = MapMatrixXiUnaligned(mesh.its.indices.front().data(), mesh.its.indices.size(), 3); + { + PROFILE_BLOCK(EigenMesh3D_AABBf_Init); + AABB.init( + vertices, + faces); + } + + { + PROFILE_BLOCK(EigenMesh3D_AABBf_AmbientOcclusion); + occlusion_output3.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = mesh.its.vertices[ivertex].template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABB.intersect_ray(vertices, faces, (origin + 1e-4 * d).eval().template cast(), d.template cast(), hit)) + ++ num_hits; + } + occlusion_output3(ivertex) = (double)num_hits/(double)num_samples; + } + } + } + + PROFILE_UPDATE(); + PROFILE_OUTPUT(nullptr); +} + +int main(const int argc, const char *argv[]) +{ + if(argc < 2) { + std::cout << USAGE_STR << std::endl; + return EXIT_SUCCESS; + } + + TriangleMesh mesh; + if (! mesh.ReadSTLFile(argv[1])) { + std::cerr << "Error loading " << argv[1] << std::endl; + return -1; + } + + mesh.repair(); + if (mesh.facets_count() == 0) { + std::cerr << "Error loading " << argv[1] << " . It is empty." << std::endl; + return -1; + } + + profile(mesh); + + return EXIT_SUCCESS; +} diff --git a/src/Shiny/ShinyOutput.c b/src/Shiny/ShinyOutput.c index ad02ea003..c2c624d58 100644 --- a/src/Shiny/ShinyOutput.c +++ b/src/Shiny/ShinyOutput.c @@ -40,8 +40,8 @@ THE SOFTWARE. /*---------------------------------------------------------------------------*/ #define OUTPUT_WIDTH_CALL 6 -#define OUTPUT_WIDTH_TIME 6 -#define OUTPUT_WIDTH_PERC 4 +#define OUTPUT_WIDTH_TIME (6+3) +#define OUTPUT_WIDTH_PERC (4+3) #define OUTPUT_WIDTH_SUM 120 #define OUTPUT_WIDTH_DATA (1+OUTPUT_WIDTH_CALL + 1 + 2*(OUTPUT_WIDTH_TIME+4+OUTPUT_WIDTH_PERC+1) + 1) @@ -70,7 +70,7 @@ SHINY_INLINE char* printData(char *output, const ShinyData *a_data, float a_tope const ShinyTimeUnit *totalUnit = ShinyGetTimeUnit(totalTicksAvg); snprintf(output, OUTPUT_WIDTH_DATA + TRAILING, - " %*.1f %*.0f %-2s %*.0f%% %*.0f %-2s %*.0f%%", + " %*.1f %*.2f %-2s %*.2f%% %*.2f %-2s %*.2f%%", OUTPUT_WIDTH_CALL, a_data->entryCount.avg, OUTPUT_WIDTH_TIME, a_data->selfTicks.avg * selfUnit->invTickFreq, selfUnit->suffix, OUTPUT_WIDTH_PERC, a_data->selfTicks.avg * a_topercent, diff --git a/src/clipper/CMakeLists.txt b/src/clipper/CMakeLists.txt index 412ab53c7..8a4e92852 100644 --- a/src/clipper/CMakeLists.txt +++ b/src/clipper/CMakeLists.txt @@ -7,3 +7,7 @@ add_library(clipper STATIC clipper_z.cpp clipper_z.hpp ) + +if(SLIC3R_PROFILE) + target_link_libraries(clipper Shiny) +endif() diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp new file mode 100644 index 000000000..ec9b14a7a --- /dev/null +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -0,0 +1,698 @@ +// AABB tree built upon external data set, referencing the external data by integer indices. +// The AABB tree balancing and traversal (ray casting, closest triangle of an indexed triangle mesh) +// were adapted from libigl AABB.{cpp,hpp} Copyright (C) 2015 Alec Jacobson +// while the implicit balanced tree representation and memory optimizations are Vojtech's. + +#ifndef slic3r_AABBTreeIndirect_hpp_ +#define slic3r_AABBTreeIndirect_hpp_ + +#include +#include +#include +#include + +#include "Utils.hpp" // for next_highest_power_of_2() + +extern "C" +{ +// Ray-Triangle Intersection Test Routines by Tomas Moller, May 2000 +#include +} +// Definition of the ray intersection hit structure. +#include + +namespace Slic3r { +namespace AABBTreeIndirect { + +// Static balanced AABB tree for raycasting and closest triangle search. +// The balanced tree is built over a single large std::vector of nodes, where the children of nodes +// are addressed implicitely using a power of two indexing rule. +// Memory for a full balanced tree is allocated, but not all nodes at the last level are used. +// This may seem like a waste of memory, but one saves memory for the node links and there is zero +// overhead of a memory allocator management (usually the memory allocator adds at least one pointer +// before the memory returned). However, allocating memory in a single vector is very fast even +// in multi-threaded environment and it is cache friendly. +// +// A balanced tree is built upon a vector of bounding boxes and their centroids, storing the reference +// to the source entity (a 3D triangle, a 2D segment etc, a 3D or 2D point etc). +// The source bounding boxes may have an epsilon applied to fight numeric rounding errors when +// traversing the AABB tree. +template +class Tree +{ +public: + static constexpr int NumDimensions = ANumDimensions; + using CoordType = ACoordType; + using VectorType = Eigen::Matrix; + using BoundingBox = Eigen::AlignedBox; + // Following could be static constexpr size_t, but that would not link in C++11 + enum : size_t { + // Node is not used. + npos = size_t(-1), + // Inner node (not leaf). + inner = size_t(-2) + }; + + // Single node of the implicit balanced AABB tree. There are no links to the children nodes, + // as these links are calculated implicitely using a power of two rule. + struct Node { + // Index of the external source entity, for which this AABB tree was built, npos for internal nodes. + size_t idx = npos; + // Bounding box around this entity, possibly with epsilons applied to fight numeric rounding errors + // when traversing the AABB tree. + BoundingBox bbox; + + bool is_valid() const { return this->idx != npos; } + bool is_inner() const { return this->idx == inner; } + bool is_leaf() const { return ! this->is_inner(); } + + template + void set(const SourceNode &rhs) { + this->idx = rhs.idx(); + this->bbox = rhs.bbox(); + } + }; + + void clear() { m_nodes.clear(); } + + // SourceNode shall implement + // size_t SourceNode::idx() const + // - Index to the outside entity (triangle, edge, point etc). + // const VectorType& SourceNode::centroid() const + // - Centroid of this node. The centroid is used for balancing the tree. + // const BoundingBox& SourceNode::bbox() const + // - Bounding box of this node, likely expanded with epsilon to account for numeric rounding during tree traversal. + // Union of bounding boxes at a single level of the AABB tree is used for deciding the longest axis aligned dimension + // to split around. + template + void build(std::vector &&input) + { + if (input.empty()) + clear(); + else { + // Allocate enough memory for a full binary tree. + m_nodes.assign(next_highest_power_of_2(input.size()) * 2 - 1, Node()); + build_recursive(input, 0, 0, input.size() - 1); + } + input.clear(); + } + + const std::vector& nodes() const { return m_nodes; } + const Node& node(size_t idx) const { return m_nodes[idx]; } + bool empty() const { return m_nodes.empty(); } + + // Addressing the child nodes using the power of two rule. + static size_t left_child_idx(size_t idx) { return idx * 2 + 1; } + static size_t right_child_idx(size_t idx) { return left_child_idx(idx) + 1; } + const Node& left_child(size_t idx) const { return m_nodes[left_child_idx(idx)]; } + const Node& right_child(size_t idx) const { return m_nodes[right_child_idx(idx)]; } + + template + void build(const std::vector &input) + { + std::vector copy(input); + this->build(std::move(copy)); + } + +private: + // Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension. + template + void build_recursive(std::vector &input, size_t node, const size_t left, const size_t right) + { + assert(node < m_nodes.size()); + assert(left <= right); + + if (left == right) { + // Insert a node into the balanced tree. + m_nodes[node].set(input[left]); + return; + } + + // Calculate bounding box of the input. + BoundingBox bbox(input[left].bbox()); + for (size_t i = left + 1; i <= right; ++ i) + bbox.extend(input[i].bbox()); + int dimension = -1; + bbox.diagonal().maxCoeff(&dimension); + + // Partition the input to left / right pieces of the same length to produce a balanced tree. + size_t center = (left + right) / 2; + partition_input(input, size_t(dimension), left, right, center); + // Insert an inner node into the tree. Inner node does not reference any input entity (triangle, line segment etc). + m_nodes[node].idx = inner; + m_nodes[node].bbox = bbox; + build_recursive(input, node * 2 + 1, left, center); + build_recursive(input, node * 2 + 2, center + 1, right); + } + + // Partition the input m_nodes at "k" and "dimension" using the QuickSelect method: + // https://en.wikipedia.org/wiki/Quickselect + // Items left of the k'th item are lower than the k'th item in the "dimension", + // items right of the k'th item are higher than the k'th item in the "dimension", + template + void partition_input(std::vector &input, const size_t dimension, size_t left, size_t right, const size_t k) const + { + while (left < right) { + size_t center = (left + right) / 2; + CoordType pivot; + { + // Bubble sort the input[left], input[center], input[right], so that a median of the three values + // will end up in input[center]. + CoordType left_value = input[left ].centroid()(dimension); + CoordType center_value = input[center].centroid()(dimension); + CoordType right_value = input[right ].centroid()(dimension); + if (left_value > center_value) { + std::swap(input[left], input[center]); + std::swap(left_value, center_value); + } + if (left_value > right_value) { + std::swap(input[left], input[right]); + right_value = left_value; + } + if (center_value > right_value) { + std::swap(input[center], input[right]); + center_value = right_value; + } + pivot = center_value; + } + if (right <= left + 2) + // The interval is already sorted. + break; + size_t i = left; + size_t j = right - 1; + std::swap(input[center], input[j]); + // Partition the set based on the pivot. + for (;;) { + // Skip left points that are already at correct positions. + // Search will certainly stop at position (right - 1), which stores the pivot. + while (input[++ i].centroid()(dimension) < pivot) ; + // Skip right points that are already at correct positions. + while (input[-- j].centroid()(dimension) > pivot && i < j) ; + if (i >= j) + break; + std::swap(input[i], input[j]); + } + // Restore pivot to the center of the sequence. + std::swap(input[i], input[right - 1]); + // Which side the kth element is in? + if (k < i) + right = i - 1; + else if (k == i) + // Sequence is partitioned, kth element is at its place. + break; + else + left = i + 1; + } + } + + // The balanced tree storage. + std::vector m_nodes; +}; + +using Tree2f = Tree<2, float>; +using Tree3f = Tree<3, float>; +using Tree2d = Tree<2, double>; +using Tree3d = Tree<3, double>; + +namespace detail { + template + struct RayIntersector { + using VertexType = AVertexType; + using IndexedFaceType = AIndexedFaceType; + using TreeType = ATreeType; + using VectorType = AVectorType; + + const std::vector &vertices; + const std::vector &faces; + const TreeType &tree; + + const VectorType origin; + const VectorType dir; + const VectorType invdir; + }; + + template + struct RayIntersectorHits : RayIntersector { + std::vector hits; + }; + + //FIXME implement SSE for float AABB trees with float ray queries. + // SSE/SSE2 is supported by any Intel/AMD x64 processor. + // SSE support requires 16 byte alignment of the AABB nodes, representing the bounding boxes with 4+4 floats, + // storing the node index as the 4th element of the bounding box min value etc. + // https://www.flipcode.com/archives/SSE_RayBox_Intersection_Test.shtml + template + inline bool ray_box_intersect_invdir( + const Eigen::MatrixBase &origin, + const Eigen::MatrixBase &inv_dir, + Eigen::AlignedBox box, + const Scalar &t0, + const Scalar &t1) { + // http://people.csail.mit.edu/amy/papers/box-jgt.pdf + // "An Efficient and Robust Ray–Box Intersection Algorithm" + if (inv_dir.x() < 0) + std::swap(box.min().x(), box.max().x()); + if (inv_dir.y() < 0) + std::swap(box.min().y(), box.max().y()); + Scalar tmin = (box.min().x() - origin.x()) * inv_dir.x(); + Scalar tymax = (box.max().y() - origin.y()) * inv_dir.y(); + if (tmin > tymax) + return false; + Scalar tmax = (box.max().x() - origin.x()) * inv_dir.x(); + Scalar tymin = (box.min().y() - origin.y()) * inv_dir.y(); + if (tymin > tmax) + return false; + if (tymin > tmin) + tmin = tymin; + if (tymax < tmax) + tmax = tymax; + if (inv_dir.z() < 0) + std::swap(box.min().z(), box.max().z()); + Scalar tzmin = (box.min().z() - origin.z()) * inv_dir.z(); + if (tzmin > tmax) + return false; + Scalar tzmax = (box.max().z() - origin.z()) * inv_dir.z(); + if (tmin > tzmax) + return false; + if (tzmin > tmin) + tmin = tzmin; + if (tzmax < tmax) + tmax = tzmax; + return tmin < t1 && tmax > t0; + } + + template + std::enable_if_t::value && std::is_same::value, bool> + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), + const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), + &t, &u, &v); + } + + template + std::enable_if_t::value && !std::is_same::value, bool> + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + using Vector = Eigen::Matrix; + Vector w0 = v0.template cast(); + Vector w1 = v1.template cast(); + Vector w2 = v2.template cast(); + return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), + w0.data(), w1.data(), w2.data(), &t, &u, &v); + } + + template + std::enable_if_t::value && std::is_same::value, bool> + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + using Vector = Eigen::Matrix; + Vector o = origin.template cast(); + Vector d = dir.template cast(); + return intersect_triangle1(o.data(), d.data(), const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), &t, &u, &v); + } + + template + std::enable_if_t::value && ! std::is_same::value, bool> + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + using Vector = Eigen::Matrix; + Vector o = origin.template cast(); + Vector d = dir.template cast(); + Vector w0 = v0.template cast(); + Vector w1 = v1.template cast(); + Vector w2 = v2.template cast(); + return intersect_triangle1(o.data(), d.data(), w0.data(), w1.data(), w2.data(), &t, &u, &v); + } + + template + static inline bool intersect_ray_recursive_first_hit( + RayIntersectorType &ray_intersector, + size_t node_idx, + Scalar min_t, + igl::Hit &hit) + { + const auto &node = ray_intersector.tree.node(node_idx); + assert(node.is_valid()); + + if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast(), Scalar(0), min_t)) + return false; + + if (node.is_leaf()) { + // shoot ray, record hit + auto face = ray_intersector.faces[node.idx]; + double t, u, v; + if (intersect_triangle( + ray_intersector.origin, ray_intersector.dir, + ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)], + t, u, v) + && t > 0.) { + hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) }; + return true; + } else + return false; + } else { + // Left / right child node index. + size_t left = node_idx * 2 + 1; + size_t right = left + 1; + igl::Hit left_hit; + igl::Hit right_hit; + bool left_ret = intersect_ray_recursive_first_hit(ray_intersector, left, min_t, left_hit); + if (left_ret && left_hit.t < min_t) { + min_t = left_hit.t; + hit = left_hit; + } else + left_ret = false; + bool right_ret = intersect_ray_recursive_first_hit(ray_intersector, right, min_t, right_hit); + if (right_ret && right_hit.t < min_t) + hit = right_hit; + else + right_ret = false; + return left_ret || right_ret; + } + } + + template + static inline void intersect_ray_recursive_all_hits(RayIntersectorType &ray_intersector, size_t node_idx) + { + using Scalar = typename RayIntersectorType::VectorType::Scalar; + + const auto &node = ray_intersector.tree.node(node_idx); + assert(node.is_valid()); + + if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast(), + Scalar(0), std::numeric_limits::infinity())) + return; + + if (node.is_leaf()) { + auto face = ray_intersector.faces[node.idx]; + double t, u, v; + if (intersect_triangle( + ray_intersector.origin, ray_intersector.dir, + ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)], + t, u, v) + && t > 0.) { + ray_intersector.hits.emplace_back(igl::Hit{ int(node.idx), -1, float(u), float(v), float(t) }); + } + } else { + // Left / right child node index. + size_t left = node_idx * 2 + 1; + size_t right = left + 1; + intersect_ray_recursive_all_hits(ray_intersector, left); + intersect_ray_recursive_all_hits(ray_intersector, right); + } + } + + // Nothing to do with COVID-19 social distancing. + template + struct IndexedTriangleSetDistancer { + using VertexType = AVertexType; + using IndexedFaceType = AIndexedFaceType; + using TreeType = ATreeType; + using VectorType = AVectorType; + + const std::vector &vertices; + const std::vector &faces; + const TreeType &tree; + + const VectorType origin; + }; + + // 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, + size_t node_idx, + Scalar low_sqr_d, + Scalar up_sqr_d, + size_t &i, + Eigen::PlainObjectBase &c) + { + using Vector = typename IndexedTriangleSetDistancerType::VectorType; + + if (low_sqr_d > up_sqr_d) + return low_sqr_d; + + // Save the best achieved hit. + auto set_min = [&i, &c, &up_sqr_d](const Scalar sqr_d_candidate, const size_t i_candidate, const Vector &c_candidate) { + if (sqr_d_candidate < up_sqr_d) { + i = i_candidate; + c = c_candidate; + up_sqr_d = sqr_d_candidate; + } + }; + + const auto &node = distancer.tree.node(node_idx); + 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); + } + else + { + size_t left_node_idx = node_idx * 2 + 1; + size_t right_node_idx = left_node_idx + 1; + const auto &node_left = distancer.tree.node(left_node_idx); + const auto &node_right = distancer.tree.node(right_node_idx); + assert(node_left.is_valid()); + assert(node_right.is_valid()); + + bool looked_left = false; + bool looked_right = false; + const auto &look_left = [&]() + { + 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); + set_min(sqr_d_left, i_left, c_left); + looked_left = true; + }; + const auto &look_right = [&]() + { + 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); + 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; + if (node_left.bbox.contains(distancer.origin.template cast())) + look_left(); + if (node_right.bbox.contains(distancer.origin.template cast())) + look_right(); + // if haven't looked left and could be less than current min, then look + Scalar left_up_sqr_d = node_left.bbox.squaredExteriorDistance(distancer.origin); + Scalar right_up_sqr_d = node_right.bbox.squaredExteriorDistance(distancer.origin); + if (left_up_sqr_d < right_up_sqr_d) { + if (! looked_left && left_up_sqr_d < up_sqr_d) + look_left(); + if (! looked_right && right_up_sqr_d < up_sqr_d) + look_right(); + } else { + if (! looked_right && right_up_sqr_d < up_sqr_d) + look_right(); + if (! looked_left && left_up_sqr_d < up_sqr_d) + look_left(); + } + } + return up_sqr_d; + } + +} // namespace detail + +// Build a balanced AABB Tree over an indexed triangles set, balancing the tree +// on centroids of the triangles. +// Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies +// during tree traversal. +template +inline Tree<3, typename VertexType::Scalar> build_aabb_tree_over_indexed_triangle_set( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + //FIXME do we want to apply an epsilon? + const typename VertexType::Scalar eps = 0) +{ + using TreeType = Tree<3, typename VertexType::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(faces.size()); + const VectorType veps(eps, eps, eps); + for (size_t i = 0; i < faces.size(); ++ i) { + const IndexedFaceType &face = faces[i]; + const VertexType &v1 = vertices[face(0)]; + const VertexType &v2 = vertices[face(1)]; + const VertexType &v3 = vertices[face(2)]; + InputType n; + n.m_idx = i; + n.m_centroid = (1./3.) * (v1 + v2 + v3); + n.m_bbox = BoundingBox(v1, v1); + n.m_bbox.extend(v2); + n.m_bbox.extend(v3); + n.m_bbox.min() -= veps; + n.m_bbox.max() += veps; + input.emplace_back(n); + } + + TreeType out; + out.build(std::move(input)); + return out; +} + +// Find a first intersection of a ray with indexed triangle set. +// Intersection test is calculated with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. +template +inline bool intersect_ray_first_hit( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. + const TreeType &tree, + // Origin of the ray. + const VectorType &origin, + // Direction of the ray. + const VectorType &dir, + // First intersection of the ray with the indexed triangle set. + igl::Hit &hit) +{ + using Scalar = typename VectorType::Scalar; + auto ray_intersector = detail::RayIntersector { + vertices, faces, tree, + origin, dir, VectorType(dir.cwiseInverse()) + }; + return ! tree.empty() && detail::intersect_ray_recursive_first_hit( + ray_intersector, size_t(0), std::numeric_limits::infinity(), hit); +} + +// Find all intersections of a ray with indexed triangle set. +// Intersection test is calculated with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. +// The output hits are sorted by the ray parameter. +// If the ray intersects a shared edge of two triangles, hits for both triangles are returned. +template +inline bool intersect_ray_all_hits( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. + const TreeType &tree, + // Origin of the ray. + const VectorType &origin, + // Direction of the ray. + const VectorType &dir, + // All intersections of the ray with the indexed triangle set, sorted by parameter t. + std::vector &hits) +{ + auto ray_intersector = detail::RayIntersectorHits { + vertices, faces, tree, + origin, dir, VectorType(dir.cwiseInverse()) + }; + if (! tree.empty()) { + ray_intersector.hits.reserve(8); + detail::intersect_ray_recursive_all_hits(ray_intersector, 0); + std::swap(hits, ray_intersector.hits); + std::sort(hits.begin(), hits.end(), [](const auto &l, const auto &r) { return l.t < r.t; }); + } + return ! hits.empty(); +} + +// Finding a closest triangle, its closest point and squared distance to the closest point +// on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree. +// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. +// Returns squared distance to the closest point or -1 if the input is empty. +template +inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. + const TreeType &tree, + // Point to which the closest point on the indexed triangle set is searched for. + const VectorType &point, + // Index of the closest triangle in faces. + size_t &hit_idx_out, + // Position of the closest point on the indexed triangle set. + Eigen::PlainObjectBase &hit_point_out) +{ + using Scalar = typename VectorType::Scalar; + 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); +} + +} // namespace AABBTreeIndirect +} // namespace Slic3r + +#endif /* slic3r_AABBTreeIndirect_hpp_ */ diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 5b048b0ff..3f535db86 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -51,8 +51,8 @@ template struct NfpImpl namespace Slic3r { template, int...EigenArgs> -inline SLIC3R_CONSTEXPR Eigen::Matrix unscaled( - const ClipperLib::IntPoint &v) SLIC3R_NOEXCEPT +inline constexpr Eigen::Matrix unscaled( + const ClipperLib::IntPoint &v) noexcept { return Eigen::Matrix{unscaled(v.X), unscaled(v.Y)}; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index dad4935b8..83ae32d39 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -306,7 +306,7 @@ if(WIN32) endif() if(SLIC3R_PROFILE) - target_link_libraries(slic3r Shiny) + target_link_libraries(libslic3r Shiny) endif() if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index e511a6316..b818cd8be 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -369,7 +369,7 @@ namespace boost { namespace polygon { typedef coord_t coordinate_type; static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) { - return (orient == HORIZONTAL) ? (coordinate_type)point(0) : (coordinate_type)point(1); + return (coordinate_type)point((orient == HORIZONTAL) ? 0 : 1); } }; @@ -377,16 +377,10 @@ namespace boost { namespace polygon { struct point_mutable_traits { typedef coord_t coordinate_type; static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) { - if (orient == HORIZONTAL) - point(0) = value; - else - point(1) = value; + point((orient == HORIZONTAL) ? 0 : 1) = value; } static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) { - Slic3r::Point retval; - retval(0) = x_value; - retval(1) = y_value; - return retval; + return Slic3r::Point(x_value, y_value); } }; } } diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 814ee0807..db375ec14 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -24,37 +24,37 @@ #if 1 // Saves around 32% RAM after slicing step, 6.7% after G-code export (tested on PrusaSlicer 2.2.0 final). -typedef int32_t coord_t; +using coord_t = int32_t; #else //FIXME At least FillRectilinear2 requires coord_t to be 32bit. typedef int64_t coord_t; #endif -typedef double coordf_t; +using coordf_t = double; //FIXME This epsilon value is used for many non-related purposes: // For a threshold of a squared Euclidean distance, // for a trheshold in a difference of radians, // for a threshold of a cross product of two non-normalized vectors etc. -#define EPSILON 1e-4 +static constexpr double EPSILON = 1e-4; // Scaling factor for a conversion from coord_t to coordf_t: 10e-6 // This scaling generates a following fixed point representation with for a 32bit integer: // 0..4294mm with 1nm resolution // int32_t fits an interval of (-2147.48mm, +2147.48mm) // with int64_t we don't have to worry anymore about the size of the int. -#define SCALING_FACTOR 0.000001 +static constexpr double SCALING_FACTOR = 0.000001; // RESOLUTION, SCALED_RESOLUTION: Used as an error threshold for a Douglas-Peucker polyline simplification algorithm. -#define RESOLUTION 0.0125 -#define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR) -#define PI 3.141592653589793238 +static constexpr double RESOLUTION = 0.0125; +#define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR) +static constexpr double PI = 3.141592653589793238; // When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam. -#define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15 +static constexpr double LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER = 0.15; // Maximum perimeter length for the loop to apply the small perimeter speed. -#define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI -#define INSET_OVERLAP_TOLERANCE 0.4 +#define SMALL_PERIMETER_LENGTH ((6.5 / SCALING_FACTOR) * 2 * PI) +static constexpr double INSET_OVERLAP_TOLERANCE = 0.4; // 3mm ring around the top / bottom / bridging areas. //FIXME This is quite a lot. -#define EXTERNAL_INFILL_MARGIN 3. +static constexpr double EXTERNAL_INFILL_MARGIN = 3.; //FIXME Better to use an inline function with an explicit return type. //inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); } #define scale_(val) ((val) / SCALING_FACTOR) @@ -63,14 +63,6 @@ typedef double coordf_t; #define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/" -#if defined(_MSC_VER) && _MSC_VER < 1900 -# define SLIC3R_CONSTEXPR -# define SLIC3R_NOEXCEPT -#else -#define SLIC3R_CONSTEXPR constexpr -#define SLIC3R_NOEXCEPT noexcept -#endif - inline std::string debug_out_path(const char *name, ...) { char buffer[2048]; @@ -92,11 +84,6 @@ inline std::string debug_out_path(const char *name, ...) #define UNUSED(x) (void)(x) #endif /* UNUSED */ -// Detect whether the compiler supports C++11 noexcept exception specifications. -#if defined(_MSC_VER) && _MSC_VER < 1900 - #define noexcept throw() -#endif - // Write slices as SVG images into out directory during the 2D processing of the slices. // #define SLIC3R_DEBUG_SLICE_PROCESSING diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index d7f0a37b0..a0df4c659 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -310,7 +310,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field("standby_temperature_delta", have_ooze_prevention); bool have_wipe_tower = config->opt_bool("wipe_tower"); - for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging" }) + for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", + "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 419f64567..35ec2e485 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4029,6 +4029,26 @@ void ObjectList::msw_rescale() Layout(); } +void ObjectList::sys_color_changed() +{ + // msw_rescale_icons() updates icons, so use it + msw_rescale_icons(); + + // update existing items with bitmaps + m_objects_model->Rescale(); + + // msw_rescale_menu updates just icons, so use it + for (MenuWithSeparators* menu : { &m_menu_object, + &m_menu_part, + &m_menu_sla_object, + &m_menu_instance, + &m_menu_layer, + &m_menu_default}) + msw_rescale_menu(menu); + + Layout(); +} + void ObjectList::ItemValueChanged(wxDataViewEvent &event) { if (event.GetColumn() == colName) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index a0370bd9d..0924d4216 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -386,6 +386,7 @@ public: void paste_objects_into_list(const std::vector& object_idxs); void msw_rescale(); + void sys_color_changed(); void update_after_undo_redo(); //update printable state for item from objects model diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 471369a00..2179a9559 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -981,6 +981,23 @@ void ObjectManipulation::msw_rescale() get_og()->msw_rescale(); } +void ObjectManipulation::sys_color_changed() +{ + // 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(); + 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(); + + get_og()->msw_rescale(); +} + 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 64a59ac9b..f002491f0 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -173,6 +173,7 @@ public: void update_item_name(const wxString &item_name); void update_warning_icon_state(const wxString& tooltip); void msw_rescale(); + void sys_color_changed(); void on_change(const std::string& opt_key, int axis, double new_value); void set_focused_editor(ManipulationEditor* focused_editor) { #ifndef __APPLE__ diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 057c72b1d..654b7b7db 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -124,6 +124,12 @@ public: // set value to _true_ in purpose of possibility of a display dpi changing from System Settings m_can_rescale = true; }); + + this->Bind(wxEVT_SYS_COLOUR_CHANGED, [this](wxSysColourChangedEvent& event) + { + event.Skip(); + on_sys_color_changed(); + }); } virtual ~DPIAware() {} @@ -137,6 +143,7 @@ public: protected: virtual void on_dpi_changed(const wxRect &suggested_rect) = 0; + virtual void on_sys_color_changed() {}; private: float m_scale_factor; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 53c309abd..51a9a6d4e 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -805,14 +805,6 @@ static const ImWchar ranges_keyboard_shortcuts[] = std::vector ImGuiWrapper::load_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height) { -#ifdef __APPLE__ - // Note: win->GetContentScaleFactor() is not used anymore here because it tends to - // return bogus results quite often (such as 1.0 on Retina or even 0.0). - // We're using the max scaling factor across all screens because it's very likely to be good enough. - double scale = mac_max_scaling_factor(); -#else - double scale = 1.0; -#endif std::vector empty_vector; #ifdef __WXMSW__ @@ -827,8 +819,6 @@ std::vector ImGuiWrapper::load_svg(const std::string& bitmap_name if (image == nullptr) return empty_vector; - target_height != 0 ? target_height *= scale : target_width *= scale; - float svg_scale = target_height != 0 ? (float)target_height / image->height : target_width != 0 ? (float)target_width / image->width : 1; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 8c268ed00..65c212d11 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -190,6 +190,29 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S event.Skip(); }); + /* + Bind(wxEVT_SYS_COLOUR_CHANGED, [this](wxSysColourChangedEvent& event) + { + bool recreate_gui = false; + { + // the dialog needs to be destroyed before the call to recreate_gui() + // or sometimes the application crashes into wxDialogBase() destructor + // so we put it into an inner scope + wxMessageDialog dialog(nullptr, + _L("System color mode was changed. " + "It is possible to update the Slicer in respect to the system mode.") + "\n" + + _L("You will lose content of the plater.") + "\n\n" + + _L("Do you want to proceed?"), + wxString(SLIC3R_APP_NAME) + " - " + _L("Switching system color mode"), + wxICON_QUESTION | wxOK | wxCANCEL); + recreate_gui = dialog.ShowModal() == wxID_OK; + } + if (recreate_gui) + wxGetApp().recreate_GUI(_L("Changing of an application in respect to the system mode") + dots); + event.Skip(); + }); + */ + wxGetApp().persist_window_geometry(this, true); update_ui_from_settings(); // FIXME (?) @@ -554,6 +577,28 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) this->Maximize(is_maximized); } +void MainFrame::on_sys_color_changed() +{ + wxBusyCursor wait; + + // update label colors in respect to the system mode + wxGetApp().init_label_colours(); + + wxGetApp().preset_bundle->load_default_preset_bitmaps(); + + // update Plater + wxGetApp().plater()->sys_color_changed(); + + // update Tabs + for (auto tab : wxGetApp().tabs_list) + tab->sys_color_changed(); + + // msw_rescale_menu updates just icons, so use it + wxMenuBar* menu_bar = this->GetMenuBar(); + for (size_t id = 0; id < menu_bar->GetMenuCount(); id++) + msw_rescale_menu(menu_bar->GetMenu(id)); +} + void MainFrame::init_menubar() { #ifdef __APPLE__ diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 219f68319..43375d344 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -121,6 +121,7 @@ class MainFrame : public DPIFrame protected: virtual void on_dpi_changed(const wxRect &suggested_rect); + virtual void on_sys_color_changed() override; public: MainFrame(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4ca92a1cb..d752a92e7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1092,6 +1092,34 @@ void Sidebar::msw_rescale() p->scrolled->Layout(); } +void Sidebar::sys_color_changed() +{ + // Update preset comboboxes in respect to the system color ... + // combo->msw_rescale() updates icon on button, so use it + for (PresetComboBox* combo : std::vector{ p->combo_print, + p->combo_sla_print, + p->combo_sla_material, + p->combo_printer }) + combo->msw_rescale(); + for (PresetComboBox* combo : p->combos_filament) + combo->msw_rescale(); + + // ... then refill them and set min size to correct layout of the sidebar + update_all_preset_comboboxes(); + + p->object_list->sys_color_changed(); + p->object_manipulation->sys_color_changed(); +// p->object_settings->msw_rescale(); +// p->object_layers->msw_rescale(); + + // btn...->msw_rescale() updates icon on button, so use it + p->btn_send_gcode->msw_rescale(); + p->btn_remove_device->msw_rescale(); + p->btn_export_gcode_removable->msw_rescale(); + + p->scrolled->Layout(); +} + void Sidebar::search() { p->searcher.search(); @@ -5514,6 +5542,17 @@ void Plater::msw_rescale() GetParent()->Layout(); } +void Plater::sys_color_changed() +{ + p->sidebar->sys_color_changed(); + + // msw_rescale_menu updates just icons, so use it + p->msw_rescale_object_menu(); + + Layout(); + GetParent()->Layout(); +} + bool Plater::init_view_toolbar() { return p->init_view_toolbar(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 9381ed163..1c1e939d9 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -105,6 +105,7 @@ public: void update_mode_sizer() const; void update_reslice_btn_tooltip() const; void msw_rescale(); + void sys_color_changed(); void search(); void jump_to_option(size_t selected); @@ -308,6 +309,7 @@ public: bool can_reload_from_disk() const; void msw_rescale(); + void sys_color_changed(); bool init_view_toolbar(); diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 967f6c537..a94b776a1 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -668,6 +668,14 @@ void SearchDialog::on_dpi_changed(const wxRect& suggested_rect) Refresh(); } +void SearchDialog::on_sys_color_changed() +{ + // msw_rescale updates just icons, so use it + search_list_model->msw_rescale(); + + Refresh(); +} + // ---------------------------------------------------------------------------- // SearchListModel // ---------------------------------------------------------------------------- diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 533ecf8f3..45a96034a 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -198,6 +198,7 @@ public: protected: void on_dpi_changed(const wxRect& suggested_rect) override; + virtual void on_sys_color_changed() override; }; diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 9e7d1bad6..74b94e1eb 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -6,6 +6,8 @@ #include +#include + #include #include #include "GUI_App.hpp" @@ -145,11 +147,11 @@ SysInfoDialog::SysInfoDialog() "" "" "", bgr_clr_str, text_clr_str, text_clr_str, - get_mem_info(true) + "
" + wxGetApp().get_gl_info(true, true)); + get_mem_info(true) + "
" + wxGetApp().get_gl_info(true, true) + "
Eigen vectorization supported: " + Eigen::SimdInstructionSetsInUse()); m_opengl_info_html->SetPage(text); main_sizer->Add(m_opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15); } - + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK); m_btn_copy_to_clipboard = new wxButton(this, wxID_ANY, _(L("Copy to Clipboard")), wxDefaultPosition, wxDefaultSize); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ae0877763..49fa03618 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -590,6 +590,18 @@ void TabPrinter::msw_rescale() Layout(); } +void TabPrinter::sys_color_changed() +{ + Tab::sys_color_changed(); + + // update missed options_groups + const std::vector& pages = m_printer_technology == ptFFF ? m_pages_sla : m_pages_fff; + for (auto page : pages) + page->msw_rescale(); + + Layout(); +} + void TabSLAMaterial::init_options_list() { if (!m_options_list.empty()) @@ -869,6 +881,41 @@ void Tab::msw_rescale() Layout(); } +void Tab::sys_color_changed() +{ + update_tab_ui(); + + // update buttons and cached bitmaps + for (const auto btn : m_scaled_buttons) + btn->msw_rescale(); + for (const auto bmp : m_scaled_bitmaps) + bmp->msw_rescale(); + for (ScalableBitmap& bmp : m_mode_bitmap_cache) + bmp.msw_rescale(); + + // 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); + + + // Colors for ui "decoration" + m_sys_label_clr = wxGetApp().get_label_clr_sys(); + m_modified_label_clr = wxGetApp().get_label_clr_modified(); + update_labels_colour(); + + // update options_groups + for (auto page : m_pages) + page->msw_rescale(); + + Layout(); +} + Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const { Field* field = nullptr; diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index a13d13f2d..affe8c2c8 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -318,6 +318,7 @@ public: void update_mode(); void update_visibility(); virtual void msw_rescale(); + virtual void sys_color_changed(); Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1); bool set_value(const t_config_option_key& opt_key, const boost::any& value); @@ -436,6 +437,7 @@ public: void on_preset_loaded() override; void init_options_list() override; void msw_rescale() override; + void sys_color_changed() override; bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; } wxSizer* create_bed_shape_widget(wxWindow* parent); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index b41dbf8ba..7f86144cd 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -3,6 +3,7 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests.cpp test_3mf.cpp + test_aabbindirect.cpp test_clipper_offset.cpp test_clipper_utils.cpp test_config.cpp diff --git a/tests/libslic3r/test_aabbindirect.cpp b/tests/libslic3r/test_aabbindirect.cpp new file mode 100644 index 000000000..c0792a943 --- /dev/null +++ b/tests/libslic3r/test_aabbindirect.cpp @@ -0,0 +1,61 @@ +#include +#include + +#include +#include + +using namespace Slic3r; + +TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndirect]") +{ + TriangleMesh tmesh = make_cube(1., 1., 1.); + tmesh.repair(); + + auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(tmesh.its.vertices, tmesh.its.indices); + REQUIRE(! tree.empty()); + + igl::Hit hit; + bool intersected = AABBTreeIndirect::intersect_ray_first_hit( + tmesh.its.vertices, tmesh.its.indices, + tree, + Vec3d(0.5, 0.5, -5.), + Vec3d(0., 0., 1.), + hit); + + REQUIRE(intersected); + REQUIRE(hit.t == Approx(5.)); + + std::vector hits; + bool intersected2 = AABBTreeIndirect::intersect_ray_all_hits( + tmesh.its.vertices, tmesh.its.indices, + tree, + Vec3d(0.3, 0.5, -5.), + Vec3d(0., 0., 1.), + hits); + REQUIRE(intersected2); + REQUIRE(hits.size() == 2); + REQUIRE(hits.front().t == Approx(5.)); + REQUIRE(hits.back().t == Approx(6.)); + + size_t hit_idx; + Vec3d closest_point; + double squared_distance = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( + tmesh.its.vertices, tmesh.its.indices, + tree, + Vec3d(0.3, 0.5, -5.), + hit_idx, closest_point); + REQUIRE(squared_distance == Approx(5. * 5.)); + REQUIRE(closest_point.x() == Approx(0.3)); + REQUIRE(closest_point.y() == Approx(0.5)); + REQUIRE(closest_point.z() == Approx(0.)); + + squared_distance = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( + tmesh.its.vertices, tmesh.its.indices, + tree, + Vec3d(0.3, 0.5, 5.), + hit_idx, closest_point); + REQUIRE(squared_distance == Approx(4. * 4.)); + REQUIRE(closest_point.x() == Approx(0.3)); + REQUIRE(closest_point.y() == Approx(0.5)); + REQUIRE(closest_point.z() == Approx(1.)); +}