diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index d8c8d1e069..6784f6055b 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -1,4 +1,4 @@ -#/|/ Copyright (c) Prusa Research 2018 - 2023 Tomáš Mészáros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak, Lukáš Hejl @hejllukas, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, David Kocík @kocikdav, Vojtěch Král @vojtechkral +#/|/ Copyright (c) Prusa Research 2018 - 2023 Tom� M�sz�ros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Vojt�ch Bubn�k @bubnikv, Pavel Miku� @Godrak, Luk� Hejl @hejllukas, Luk� Mat�na @lukasmatena, Filip Sykala @Jony01, David Koc�k @kocikdav, Vojt�ch Kr�l @vojtechkral #/|/ Copyright (c) BambuStudio 2023 manch1n @manch1n #/|/ Copyright (c) 2023 Mimoja @Mimoja #/|/ Copyright (c) 2022 ole00 @ole00 @@ -452,7 +452,21 @@ set(SLIC3R_SOURCES SLA/BranchingTreeSLA.hpp SLA/BranchingTreeSLA.cpp SLA/ZCorrection.hpp - SLA/ZCorrection.cpp + SLA/ZCorrection.cpp + SLA/SupportIslands/EvaluateNeighbor.cpp + SLA/SupportIslands/EvaluateNeighbor.hpp + SLA/SupportIslands/ExpandNeighbor.cpp + SLA/SupportIslands/ExpandNeighbor.hpp + SLA/SupportIslands/IStackFunction.hpp + SLA/SupportIslands/NodeDataWithResult.hpp + SLA/SupportIslands/PostProcessNeighbor.cpp + SLA/SupportIslands/PostProcessNeighbor.hpp + SLA/SupportIslands/PostProcessNeighbors.cpp + SLA/SupportIslands/PostProcessNeighbors.hpp + SLA/SupportIslands/SampleConfig.hpp + SLA/SupportIslands/VoronoiGraph.hpp + SLA/SupportIslands/VoronoiGraphUtils.cpp + SLA/SupportIslands/VoronoiGraphUtils.hpp BranchingTree/BranchingTree.cpp BranchingTree/BranchingTree.hpp BranchingTree/PointCloud.cpp @@ -524,6 +538,9 @@ foreach(_source IN ITEMS ${SLIC3R_SOURCES}) source_group("${_group_path}" FILES "${_source}") endforeach() +# Create the source groups for source tree with root at CMAKE_CURRENT_SOURCE_DIR. +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCE_LIST_SLA}) + if (SLIC3R_STATIC) set(CGAL_Boost_USE_STATIC_LIBS ON CACHE BOOL "" FORCE) endif () diff --git a/src/libslic3r/SLA/SupportIslands/EvaluateNeighbor.cpp b/src/libslic3r/SLA/SupportIslands/EvaluateNeighbor.cpp new file mode 100644 index 0000000000..d6f9fcee2b --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/EvaluateNeighbor.cpp @@ -0,0 +1,23 @@ +#include "EvaluateNeighbor.hpp" +#include "ExpandNeighbor.hpp" + +using namespace Slic3r::sla; + +EvaluateNeighbor::EvaluateNeighbor(VoronoiGraph::ExPath & result, + const VoronoiGraph::Node *node, + double distance_to_node, + const VoronoiGraph::Path &prev_path) + : post_process_neighbor( + std::make_unique(result, + node, + distance_to_node, + prev_path)) +{} + +void EvaluateNeighbor::process(CallStack &call_stack) +{ + NodeDataWithResult &data = *post_process_neighbor; + call_stack.emplace(std::move(post_process_neighbor)); + for (const VoronoiGraph::Node::Neighbor &neighbor : data.node->neighbors) + call_stack.emplace(std::make_unique(data, neighbor)); +} \ No newline at end of file diff --git a/src/libslic3r/SLA/SupportIslands/EvaluateNeighbor.hpp b/src/libslic3r/SLA/SupportIslands/EvaluateNeighbor.hpp new file mode 100644 index 0000000000..b233ccb612 --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/EvaluateNeighbor.hpp @@ -0,0 +1,36 @@ +#ifndef slic3r_SLA_SuppotstIslands_EvaluateNeighbor_hpp_ +#define slic3r_SLA_SuppotstIslands_EvaluateNeighbor_hpp_ + +#include + +#include "IStackFunction.hpp" +#include "PostProcessNeighbors.hpp" +#include "VoronoiGraph.hpp" + +namespace Slic3r::sla { + +/// +/// create on stack +/// 1 * PostProcessNeighbors +/// N * ExpandNode +/// +class EvaluateNeighbor : public IStackFunction +{ + std::unique_ptr post_process_neighbor; +public: + EvaluateNeighbor( + VoronoiGraph::ExPath & result, + const VoronoiGraph::Node *node, + double distance_to_node = 0., + const VoronoiGraph::Path &prev_path = VoronoiGraph::Path({}, 0.)); + + /// + /// create on stack + /// 1 * PostProcessNeighbors + /// N * ExpandNode + /// + virtual void process(CallStack &call_stack); +}; + +} // namespace Slic3r::sla +#endif // slic3r_SLA_SuppotstIslands_EvaluateNeighbor_hpp_ diff --git a/src/libslic3r/SLA/SupportIslands/ExpandNeighbor.cpp b/src/libslic3r/SLA/SupportIslands/ExpandNeighbor.cpp new file mode 100644 index 0000000000..4410ecb347 --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/ExpandNeighbor.cpp @@ -0,0 +1,44 @@ +#include "ExpandNeighbor.hpp" +#include "VoronoiGraphUtils.hpp" + +using namespace Slic3r::sla; + +ExpandNeighbor::ExpandNeighbor( + NodeDataWithResult & data, + const VoronoiGraph::Node::Neighbor &neighbor) + : data(data) + , neighbor(neighbor) +{} + +void ExpandNeighbor::process(CallStack &call_stack) +{ + if (data.skip_nodes.find(neighbor.node) != data.skip_nodes.end()) return; + + // detection of circle + auto circle_opt = VoronoiGraphUtils::create_circle(data.act_path, + neighbor); + if (circle_opt.has_value()) { + size_t circle_index = data.result.circles.size(); + data.circle_indexes.push_back(circle_index); + data.result.circles.emplace_back(std::move(circle_opt.value())); + return; + } + + // create copy of path(not circles, not side_branches) + const VoronoiGraph::Node &next_node = *neighbor.node; + // is next node leaf ? + if (next_node.neighbors.size() == 1) { + VoronoiGraph::Path side_branch({&next_node}, neighbor.edge_length); + data.side_branches.push(std::move(side_branch)); + return; + } + + auto post_process_neighbor = std::make_unique(data); + VoronoiGraph::ExPath &neighbor_path = post_process_neighbor->neighbor_path; + + call_stack.emplace(std::move(post_process_neighbor)); + call_stack.emplace( + std::make_unique(neighbor_path, neighbor.node, + neighbor.edge_length, + data.act_path)); +} \ No newline at end of file diff --git a/src/libslic3r/SLA/SupportIslands/ExpandNeighbor.hpp b/src/libslic3r/SLA/SupportIslands/ExpandNeighbor.hpp new file mode 100644 index 0000000000..061963c46f --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/ExpandNeighbor.hpp @@ -0,0 +1,35 @@ +#ifndef slic3r_SLA_SuppotstIslands_ExpandNeighbor_hpp_ +#define slic3r_SLA_SuppotstIslands_ExpandNeighbor_hpp_ + +#include "IStackFunction.hpp" +#include "VoronoiGraph.hpp" +#include "PostProcessNeighbor.hpp" +#include "EvaluateNeighbor.hpp" + +namespace Slic3r::sla { + +/// +/// Expand neighbor to +/// - PostProcessNeighbor +/// - EvaluateNeighbor +/// +class ExpandNeighbor : public IStackFunction +{ + NodeDataWithResult & data; + const VoronoiGraph::Node::Neighbor &neighbor; + +public: + ExpandNeighbor(NodeDataWithResult & data, + const VoronoiGraph::Node::Neighbor &neighbor); + + /// + /// Expand neighbor to + /// - PostProcessNeighbor + /// - EvaluateNeighbor + /// + /// Output callStack + virtual void process(CallStack &call_stack); +}; + +} // namespace Slic3r::sla +#endif // slic3r_SLA_SuppotstIslands_ExpandNeighbor_hpp_ diff --git a/src/libslic3r/SLA/SupportIslands/IStackFunction.hpp b/src/libslic3r/SLA/SupportIslands/IStackFunction.hpp new file mode 100644 index 0000000000..6763116a70 --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/IStackFunction.hpp @@ -0,0 +1,23 @@ +#ifndef slic3r_SLA_SuppotstIslands_IStackFunction_hpp_ +#define slic3r_SLA_SuppotstIslands_IStackFunction_hpp_ + +#include +#include + +namespace Slic3r::sla { + +/// +/// Interface for objects inside of CallStack. +/// It is way to prevent stack overflow inside recurrent functions. +/// +class IStackFunction +{ +public: + virtual ~IStackFunction() = default; + virtual void process(std::stack> &call_stack) = 0; +}; + +using CallStack = std::stack>; + +} // namespace Slic3r::sla +#endif // slic3r_SLA_SuppotstIslands_IStackFunction_hpp_ diff --git a/src/libslic3r/SLA/SupportIslands/NodeDataWithResult.hpp b/src/libslic3r/SLA/SupportIslands/NodeDataWithResult.hpp new file mode 100644 index 0000000000..ddd2f76fb7 --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/NodeDataWithResult.hpp @@ -0,0 +1,72 @@ +#ifndef slic3r_SLA_SuppotstIslands_NodeDataWithResult_hpp_ +#define slic3r_SLA_SuppotstIslands_NodeDataWithResult_hpp_ + +#include +#include +#include "VoronoiGraph.hpp" + +namespace Slic3r::sla { + +/// +/// DTO for process node during depth search +/// which create longest path in voronoi graph +/// +struct NodeDataWithResult +{ + // result for this node + VoronoiGraph::ExPath &result; + + // actual proccessed node + const VoronoiGraph::Node *node; + // distance to this node from input node + double distance_to_node; + + // path from start point to this node + // last one is actual node + VoronoiGraph::Path act_path; + + // skip node when circle start - must end at this node + // set --> multiple cirle could start at same node + // previous node should be skiped to so it is initialized with it + std::set skip_nodes; // circle + + // store all circle indexes this node is lay on + // used to create connected circles structure + std::vector circle_indexes; + // When circle_indexes is not empty node lays on circle + // and in this node is not searching for longest path only store side + // branches(not part of circle) + + // indexes of circle ending in this node(could be more than one) + std::vector end_circle_indexes; + // When end_circle_indexes == circle_indexes + // than it is end of circle (multi circle structure) and it is processed + + // contain possible continue path + // possible empty + VoronoiGraph::ExPath::SideBranches side_branches; + +public: + // append node to act path + NodeDataWithResult( + VoronoiGraph::ExPath & result, + const VoronoiGraph::Node *node, + double distance_to_node, + const VoronoiGraph::Path &prev_path) + : result(result) + , node(node) + , distance_to_node(distance_to_node) + { + // TODO: process it before constructor or in factory + const VoronoiGraph::Node *prev_node = (prev_path.path.size() >= 1) ? + prev_path.path.back() : + nullptr; + skip_nodes = {prev_node}; + // append actual node + act_path = prev_path; // copy + act_path.append(node, distance_to_node); // increase path + } +}; + +} // namespace Slic3r::sla +#endif // slic3r_SLA_SuppotstIslands_NodeDataWithResult_hpp_ diff --git a/src/libslic3r/SLA/SupportIslands/PostProcessNeighbor.cpp b/src/libslic3r/SLA/SupportIslands/PostProcessNeighbor.cpp new file mode 100644 index 0000000000..14af5c190b --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/PostProcessNeighbor.cpp @@ -0,0 +1,40 @@ +#include "PostProcessNeighbor.hpp" + +#include "VoronoiGraphUtils.hpp" + +using namespace Slic3r::sla; + +void PostProcessNeighbor::process() +{ + bool is_circle_neighbor = false; + if (neighbor_path.path.empty()) { // neighbor node is on circle + for (VoronoiGraph::Circle &circle : neighbor_path.circles) { + const auto &circle_item = std::find(circle.path.begin(), + circle.path.end(), data.node); + if (circle_item == circle.path.end()) + continue; // node is NOT on circle + + size_t next_circle_index = &circle - + &neighbor_path.circles.front(); + size_t circle_index = data.result.circles.size() + + next_circle_index; + data.circle_indexes.push_back(circle_index); + + // check if this node is end of circle + if (circle_item == circle.path.begin()) { + data.end_circle_indexes.push_back(circle_index); + + // !! this FIX circle lenght because at detection of + // circle it will cost time to calculate it + circle.length -= data.act_path.length; + + // skip twice checking of circle + data.skip_nodes.insert(circle.path.back()); + } + is_circle_neighbor = true; + } + } + VoronoiGraphUtils::append_neighbor_branch(data.result, neighbor_path); + if (!is_circle_neighbor) + data.side_branches.push(std::move(neighbor_path)); +} \ No newline at end of file diff --git a/src/libslic3r/SLA/SupportIslands/PostProcessNeighbor.hpp b/src/libslic3r/SLA/SupportIslands/PostProcessNeighbor.hpp new file mode 100644 index 0000000000..fbd63a8b53 --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/PostProcessNeighbor.hpp @@ -0,0 +1,36 @@ +#ifndef slic3r_SLA_SuppotstIslands_PostProcessNeighbor_hpp_ +#define slic3r_SLA_SuppotstIslands_PostProcessNeighbor_hpp_ + +#include "IStackFunction.hpp" +#include "NodeDataWithResult.hpp" +#include "VoronoiGraph.hpp" +#include "NodeDataWithResult.hpp" + +namespace Slic3r::sla { + +/// +/// Decimate data from Ex path to path +/// Done after ONE neighbor is procceessed. +/// Check if node is on circle. +/// Remember ended circle +/// Merge side branches and circle information into result +/// +class PostProcessNeighbor : public IStackFunction +{ + NodeDataWithResult &data; + +public: + VoronoiGraph::ExPath neighbor_path; // data filled in EvaluateNeighbor + PostProcessNeighbor(NodeDataWithResult &data) : data(data) {} + + virtual void process([[maybe_unused]] CallStack &call_stack) + { + process(); + } + +private: + void process(); +}; + +} // namespace Slic3r::sla +#endif // slic3r_SLA_SuppotstIslands_PostProcessNeighbor_hpp_ diff --git a/src/libslic3r/SLA/SupportIslands/PostProcessNeighbors.cpp b/src/libslic3r/SLA/SupportIslands/PostProcessNeighbors.cpp new file mode 100644 index 0000000000..979e516bce --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/PostProcessNeighbors.cpp @@ -0,0 +1,50 @@ +#include "PostProcessNeighbors.hpp" + +#include "VoronoiGraphUtils.hpp" + +using namespace Slic3r::sla; + +void PostProcessNeighbors::process() +{ + // remember connected circle + if (circle_indexes.size() > 1) { + for (size_t circle_index : circle_indexes) { + for (size_t circle_index2 : circle_indexes) { + if (circle_index == circle_index2) continue; + result.connected_circle[circle_index].insert(circle_index2); + } + } + } + + // detect end of circles in this node + if (!end_circle_indexes.empty() && + end_circle_indexes.size() == circle_indexes.size()) { + size_t circle_index = circle_indexes.front(); // possible any of them + side_branches.push( + VoronoiGraphUtils::find_longest_path_on_circles(*node, + circle_index, + result)); + + circle_indexes.clear(); // resolved circles + } + + // simple node on circle --> only input and output neighbor + if (side_branches.empty()) return; + + // is node on unresolved circle? + if (!circle_indexes.empty()) { + // not search for longest path, it will eval on end of circle + result.side_branches[node] = side_branches; + return; + } + + // create result longest path from longest side branch + VoronoiGraph::Path longest_path(std::move(side_branches.top())); + side_branches.pop(); + if (!side_branches.empty()) { + result.side_branches[node] = side_branches; + } + longest_path.path.insert(longest_path.path.begin(), node); + result.path = std::move(longest_path.path); + result.length = distance_to_node + longest_path.length; +} \ No newline at end of file diff --git a/src/libslic3r/SLA/SupportIslands/PostProcessNeighbors.hpp b/src/libslic3r/SLA/SupportIslands/PostProcessNeighbors.hpp new file mode 100644 index 0000000000..1fdfa24b60 --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/PostProcessNeighbors.hpp @@ -0,0 +1,35 @@ +#ifndef slic3r_SLA_SuppotstIslands_PostProcessNeighbors_hpp_ +#define slic3r_SLA_SuppotstIslands_PostProcessNeighbors_hpp_ + +#include "IStackFunction.hpp" +#include "VoronoiGraph.hpp" +#include "NodeDataWithResult.hpp" + +namespace Slic3r::sla { + +/// +/// call after all neighbors are processed +/// +class PostProcessNeighbors : public NodeDataWithResult, public IStackFunction +{ +public: + PostProcessNeighbors(VoronoiGraph::ExPath & result, + const VoronoiGraph::Node *node, + double distance_to_node = 0., + const VoronoiGraph::Path &prev_path = + VoronoiGraph::Path({}, 0.) // make copy + ) + : NodeDataWithResult(result, node, distance_to_node, prev_path) + {} + + virtual void process([[maybe_unused]] CallStack &call_stack) + { + process(); + } + +private: + void process(); +}; + +} // namespace Slic3r::sla +#endif // slic3r_SLA_SuppotstIslands_PostProcessNeighbors_hpp_ diff --git a/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp b/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp new file mode 100644 index 0000000000..4eabd4a8c9 --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp @@ -0,0 +1,28 @@ +#ifndef slic3r_SLA_SuppotstIslands_SampleConfig_hpp_ +#define slic3r_SLA_SuppotstIslands_SampleConfig_hpp_ + +namespace Slic3r::sla { + +/// +/// Configuration fro sampling voronoi diagram for support point generator +/// +struct SampleConfig +{ + // Maximal distance from edge + double max_distance = 1.; // must be bigger than zero + // Maximal distance between samples on skeleton + double sample_size = 1.; // must be bigger than zero + // distance from edge of skeleton + double start_distance = 0; // support head diameter + + // maximal length of longest path in voronoi diagram to be island + // supported only by one single support point this point will be in center + // of path suggestion: smaller than 2* SampleConfig.start_distance + double max_length_for_one_support_point = 1.; + + // each curve is sampled by this value to test distance to edge of island + double curve_sample = 1.; // must be bigger than zero +}; + +} // namespace Slic3r::sla +#endif // slic3r_SLA_SuppotstIslands_SampleConfig_hpp_ diff --git a/src/libslic3r/SLA/SupportIslands/VoronoiGraph.hpp b/src/libslic3r/SLA/SupportIslands/VoronoiGraph.hpp new file mode 100644 index 0000000000..d9dc59948e --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/VoronoiGraph.hpp @@ -0,0 +1,144 @@ +#ifndef slic3r_SLA_SuppotstIslands_VoronoiGraph_hpp_ +#define slic3r_SLA_SuppotstIslands_VoronoiGraph_hpp_ + +#include +#include +#include + +namespace Slic3r::sla { + +/// +/// DTO store skeleton With longest path +/// +struct VoronoiGraph +{ + using VD = Slic3r::Geometry::VoronoiDiagram; + struct Node; + using Nodes = std::vector; + struct Path; + struct ExPath; + using Circle = Path; + std::map data; +}; + +/// +/// Node data structure for Voronoi Graph. +/// Extend information about Voronoi vertex. +/// +struct VoronoiGraph::Node +{ + // reference to Voronoi diagram VertexCategory::Inside OR + // VertexCategory::OnContour but NOT VertexCategory::Outside + const VD::vertex_type *vertex; + // longest distance to edge sum of line segment size (no euclid because of shape U) + double longestDistance; + + // actual distance to edge + double distance; + + struct Neighbor; + std::vector neighbors; + + // constructor + Node(const VD::vertex_type *vertex, double distance) + : vertex(vertex), longestDistance(0.), distance(distance), neighbors() + {} +}; + +/// +/// Surrond GraphNode data type. +/// Extend information about voronoi edge. +/// +struct VoronoiGraph::Node::Neighbor +{ + const VD::edge_type *edge; + // length edge between vertices + double edge_length; + // pointer on graph node structure + Node *node; +public: + Neighbor(const VD::edge_type *edge, double edge_length, Node *node) + : edge(edge), edge_length(edge_length), node(node) + {} +}; + + +/// +/// DTO represents path over nodes of VoronoiGraph +/// store queue of Nodes +/// store length of path +/// +struct VoronoiGraph::Path +{ + // row of neighbor Nodes + VoronoiGraph::Nodes path; // TODO: rename to nodes + + // length of path + // when circle contain length from back to front; + double length; + +public: + Path() : path(), length(0.) {} + Path(const VoronoiGraph::Node *node) : path({node}), length(0.) {} + Path(VoronoiGraph::Nodes nodes, double length) + : path(std::move(nodes)), length(length) + {} + + void append(const VoronoiGraph::Node *node, double length) + { + path.push_back(node); + this->length += length; + } + + struct OrderLengthFromShortest{ + bool operator()(const VoronoiGraph::Path &path1, + const VoronoiGraph::Path &path2){ + return path1.length > path2.length; + } + }; + + struct OrderLengthFromLongest{ + bool operator()(const VoronoiGraph::Path &path1, + const VoronoiGraph::Path &path2){ + return path1.length < path2.length; + } + }; +}; + + +/// +/// DTO +/// extends path with side branches and circles(connection of circles) +/// +struct VoronoiGraph::ExPath : public VoronoiGraph::Path +{ + // not main path is stored in secondary paths + // key is pointer to source node + using SideBranches = std::priority_queue, + OrderLengthFromLongest>; + using SideBranchesMap = std::map; + + // All side branches in Graph under node + // Map contains only node, which has side branche(s) + // There is NOT empty SideBranches in map !!! + SideBranchesMap side_branches; + + // All circles in Graph under node + std::vector circles; + + // alone circle does'n have record in connected_circle + // every connected circle have at least two records(firs to second & + // second to first) EXAMPLE with 3 circles(index to circles stored in + // this->circles are: c1, c2 and c3) connected together + // connected_circle[c1] = {c2, c3}; connected_circle[c2] = {c1, c3}; + // connected_circle[c3] = {c1, c2}; + using ConnectedCircles = std::map>; + ConnectedCircles connected_circle; + +public: + ExPath() = default; +}; + +} // namespace Slic3r::sla +#endif // slic3r_SLA_SuppotstIslands_VoronoiGraph_hpp_ diff --git a/src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.cpp b/src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.cpp new file mode 100644 index 0000000000..03dcfeac04 --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.cpp @@ -0,0 +1,584 @@ +#include "VoronoiGraphUtils.hpp" + +#include +#include "IStackFunction.hpp" +#include "EvaluateNeighbor.hpp" + +using namespace Slic3r::sla; + +VoronoiGraph::Node *VoronoiGraphUtils::getNode(VoronoiGraph & graph, + const VD::vertex_type *vertex, + const VD::edge_type * edge, + const Lines & lines) +{ + std::map &data = graph.data; + auto &mapItem = data.find(vertex); + // return when exists + if (mapItem != data.end()) return &mapItem->second; + + // is new vertex (first edge to this vertex) + // calculate distance to islad border + fill item0 + const VD::cell_type *cell = edge->cell(); + // const VD::cell_type * cell2 = edge.twin()->cell(); + const Line &line = lines[cell->source_index()]; + // const Line & line1 = lines[cell2->source_index()]; + Point point(vertex->x(), vertex->y()); + double distance = line.distance_to(point); + + auto &[iterator, + success] = data.emplace(vertex, + VoronoiGraph::Node(vertex, distance)); + assert(success); + return &iterator->second; +} + +VoronoiGraph VoronoiGraphUtils::getSkeleton(const VD &vd, const Lines &lines) +{ + // vd should be annotated. + // assert(Voronoi::debug::verify_inside_outside_annotations(vd)); + + VoronoiGraph skeleton; + const VD::edge_type *first_edge = &vd.edges().front(); + for (const VD::edge_type &edge : vd.edges()) { + size_t edge_idx = &edge - first_edge; + if ( + // Ignore secondary and unbounded edges, they shall never be part + // of the skeleton. + edge.is_secondary() || edge.is_infinite() || + // Skip the twin edge of an edge, that has already been processed. + &edge > edge.twin() || + // Ignore outer edges. + (Voronoi::edge_category(edge) != + Voronoi::EdgeCategory::PointsInside && + Voronoi::edge_category(edge.twin()) != + Voronoi::EdgeCategory::PointsInside)) + continue; + + const VD::vertex_type * v0 = edge.vertex0(); + const VD::vertex_type * v1 = edge.vertex1(); + Voronoi::VertexCategory category0 = Voronoi::vertex_category(*v0); + Voronoi::VertexCategory category1 = Voronoi::vertex_category(*v1); + if (category0 == Voronoi::VertexCategory::Outside || + category1 == Voronoi::VertexCategory::Outside) + continue; + // only debug check annotation + if (category0 == Voronoi::VertexCategory::Unknown || + category1 == Voronoi::VertexCategory::Unknown) + return {}; // vd must be annotated + + double length = 0; + if (edge.is_linear()) { + double diffX = v0->x() - v1->x(); + double diffY = v0->y() - v1->y(); + length = sqrt(diffX * diffX + diffY * diffY); + } else { // if (edge.is_curved()) + // TODO: len of parabola + length = 1.0; + } + + VoronoiGraph::Node *node0 = getNode(skeleton, v0, &edge, lines); + VoronoiGraph::Node *node1 = getNode(skeleton, v1, &edge, lines); + + // add extended Edge to graph, both side + VoronoiGraph::Node::Neighbor neighbor0(&edge, length, node1); + node0->neighbors.push_back(neighbor0); + VoronoiGraph::Node::Neighbor neighbor1(edge.twin(), length, node0); + node1->neighbors.push_back(neighbor1); + } + return skeleton; +} + +Slic3r::Point VoronoiGraphUtils::get_offseted_point( + const VoronoiGraph::Node &node, + double padding) +{ + assert(node.neighbors.size() == 1); + const VoronoiGraph::Node::Neighbor &neighbor = node.neighbors.front(); + const VD::edge_type & edge = *neighbor.edge; + const VD::vertex_type & v0 = *edge.vertex0(); + const VD::vertex_type & v1 = *edge.vertex1(); + Point dir(v0.x() - v1.x(), v0.y() - v1.y()); + if (node.vertex == &v0) + dir *= -1; + else + assert(node.vertex == &v1); + + double size = neighbor.edge_length / padding; + Point move(dir[0] / size, dir[1] / size); + return Point(node.vertex->x() + move[0], node.vertex->y() + move[1]); +} + +const VoronoiGraph::Node::Neighbor *VoronoiGraphUtils::get_neighbor( + const VoronoiGraph::Node *from, const VoronoiGraph::Node *to) +{ + for (const VoronoiGraph::Node::Neighbor &neighbor : from->neighbors) + if (neighbor.node == to) return &neighbor; + return nullptr; +} + +double VoronoiGraphUtils::get_neighbor_distance(const VoronoiGraph::Node *from, + const VoronoiGraph::Node *to) +{ + const VoronoiGraph::Node::Neighbor *neighbor = get_neighbor(from, to); + assert(neighbor != nullptr); + return neighbor->edge_length; +} + +VoronoiGraph::Path VoronoiGraphUtils::find_longest_path_on_circle( + const VoronoiGraph::Circle & circle, + const VoronoiGraph::ExPath::SideBranchesMap &side_branches) +{ + double half_circle_length = circle.length / 2.; + double distance_on_circle = 0; + + bool is_longest_revers_direction = false; + const VoronoiGraph::Node *longest_circle_node = nullptr; + const VoronoiGraph::Path *longest_circle_branch = nullptr; + double longest_branch_length = 0; + + bool is_short_revers_direction = false; + // find longest side branch + const VoronoiGraph::Node *prev_circle_node = nullptr; + for (const VoronoiGraph::Node *circle_node : circle.path) { + if (prev_circle_node != nullptr) + distance_on_circle += get_neighbor_distance(circle_node, + prev_circle_node); + prev_circle_node = circle_node; + + auto side_branches_item = side_branches.find(circle_node); + if (side_branches_item != side_branches.end()) { + // side_branches should be sorted by length + if (distance_on_circle > half_circle_length) + is_short_revers_direction = true; + const auto &longest_node_branch = side_branches_item->second.top(); + double circle_branch_length = longest_node_branch.length + + ((is_short_revers_direction) ? + (circle.length - + distance_on_circle) : + distance_on_circle); + if (longest_branch_length < circle_branch_length) { + longest_branch_length = circle_branch_length; + is_longest_revers_direction = is_short_revers_direction; + longest_circle_node = circle_node; + longest_circle_branch = &longest_node_branch; + } + } + } + assert(longest_circle_node != + nullptr); // only circle with no side branches + assert(longest_circle_branch != nullptr); + // almost same - double preccission + // distance_on_circle += get_neighbor_distance(circle.path.back(), + // circle.path.front()); assert(distance_on_circle == circle.length); + + // circlePath + auto circle_iterator = std::find(circle.path.begin(), circle.path.end(), + longest_circle_node); + VoronoiGraph::Nodes circle_path; + if (is_longest_revers_direction) { + circle_path = VoronoiGraph::Nodes(circle_iterator, circle.path.end()); + std::reverse(circle_path.begin(), circle_path.end()); + } else { + if (longest_circle_node != circle.path.front()) + circle_path = VoronoiGraph::Nodes(circle.path.begin() + 1, + circle_iterator + 1); + } + // append longest side branch + circle_path.insert(circle_path.end(), longest_circle_branch->path.begin(), + longest_circle_branch->path.end()); + return {circle_path, longest_branch_length}; +} + +VoronoiGraph::Path VoronoiGraphUtils::find_longest_path_on_circles( + const VoronoiGraph::Node & input_node, + size_t finished_circle_index, + const VoronoiGraph::ExPath &ex_path) +{ + const std::vector &circles = ex_path.circles; + const auto &circle = circles[finished_circle_index]; + auto connected_circle_item = ex_path.connected_circle.find( + finished_circle_index); + // is only one circle + if (connected_circle_item == ex_path.connected_circle.end()) { + // find longest path over circle and store it into next_path + return find_longest_path_on_circle(circle, ex_path.side_branches); + } + + // multi circle + // find longest path over circles + const std::set &connected_circles = connected_circle_item->second; + + // collect all circle ndoes + std::set nodes; + nodes.insert(circle.path.begin(), circle.path.end()); + for (size_t circle_index : connected_circles) { + const auto &circle = circles[circle_index]; + nodes.insert(circle.path.begin(), circle.path.end()); + } + + // nodes are path throw circles + // length is sum path throw circles PLUS length of longest side_branch + VoronoiGraph::Path longest_path; + + // wide search by shortest distance for path over circle's node + // !! Do NOT use recursion, may cause stack overflow + std::set done; // all ready checked + // on top is shortest path + std::priority_queue, + VoronoiGraph::Path::OrderLengthFromShortest> + search_queue; + VoronoiGraph::Path start_path({&input_node}, 0.); + search_queue.emplace(start_path); + while (!search_queue.empty()) { + // shortest path from input_node + VoronoiGraph::Path path(std::move(search_queue.top())); + search_queue.pop(); + const VoronoiGraph::Node &node = *path.path.back(); + if (done.find(&node) != done.end()) { // already checked + continue; + } + done.insert(&node); + for (const VoronoiGraph::Node::Neighbor &neighbor : node.neighbors) { + if (nodes.find(neighbor.node) == nodes.end()) + continue; // out of circles + if (done.find(neighbor.node) != done.end()) continue; + VoronoiGraph::Path neighbor_path = path; // make copy + neighbor_path.append(neighbor.node, neighbor.edge_length); + search_queue.push(neighbor_path); + + auto branches_item = ex_path.side_branches.find(neighbor.node); + // exist side from this neighbor node ? + if (branches_item == ex_path.side_branches.end()) continue; + const VoronoiGraph::Path &longest_branch = branches_item->second + .top(); + double length = longest_branch.length + neighbor_path.length; + if (longest_path.length < length) { + longest_path.length = length; + longest_path.path = neighbor_path.path; // copy path + } + } + } + + // create result path + assert(!longest_path.path.empty()); + longest_path.path.erase(longest_path.path.begin()); // remove input_node + assert(!longest_path.path.empty()); + auto branches_item = ex_path.side_branches.find(longest_path.path.back()); + if (branches_item == ex_path.side_branches.end()) { + // longest path ends on circle + return longest_path; + } + const VoronoiGraph::Path &longest_branch = branches_item->second.top(); + longest_path.path.insert(longest_path.path.end(), + longest_branch.path.begin(), + longest_branch.path.end()); + return longest_path; +} + +std::optional VoronoiGraphUtils::create_circle( + const VoronoiGraph::Path & path, + const VoronoiGraph::Node::Neighbor &neighbor) +{ + VoronoiGraph::Nodes passed_nodes = path.path; + // detection of circle + // not neccesary to check last one in path + auto end_find = passed_nodes.end() - 1; + const auto &path_item = std::find(passed_nodes.begin(), end_find, + neighbor.node); + if (path_item == end_find) return {}; // circle not detected + // separate Circle: + VoronoiGraph::Nodes circle_path(path_item, passed_nodes.end()); + // !!! Real circle lenght is calculated on detection of end circle + // now circle_length contain also lenght of path before circle + double circle_length = path.length + neighbor.edge_length; + // solve of branch length will be at begin of cirlce + return VoronoiGraph::Circle(std::move(circle_path), circle_length); +}; + +void VoronoiGraphUtils::merge_connected_circle( + VoronoiGraph::ExPath::ConnectedCircles &dst, + VoronoiGraph::ExPath::ConnectedCircles &src, + size_t dst_circle_count) +{ + std::set done; + for (const auto &item : src) { + size_t dst_index = dst_circle_count + item.first; + if (done.find(dst_index) != done.end()) continue; + done.insert(dst_index); + + std::set connected_circle; + for (const size_t &src_index : item.second) + connected_circle.insert(dst_circle_count + src_index); + + auto &dst_set = dst[dst_index]; + dst_set.merge(connected_circle); + + // write same information into connected circles + connected_circle = dst_set; // copy + connected_circle.insert(dst_index); + for (size_t prev_connection_idx : dst_set) { + done.insert(prev_connection_idx); + for (size_t connected_circle_idx : connected_circle) { + if (connected_circle_idx == prev_connection_idx) continue; + dst[prev_connection_idx].insert(connected_circle_idx); + } + } + } +} + +void VoronoiGraphUtils::append_neighbor_branch( + VoronoiGraph::ExPath &dst, VoronoiGraph::ExPath &src) +{ + // move side branches + if (!src.side_branches.empty()) + dst.side_branches + .insert(std::make_move_iterator(src.side_branches.begin()), + std::make_move_iterator(src.side_branches.end())); + + // move circles + if (!src.circles.empty()) { + // copy connected circles indexes + if (!src.connected_circle.empty()) { + merge_connected_circle(dst.connected_circle, src.connected_circle, + dst.circles.size()); + } + dst.circles.insert(dst.circles.end(), + std::make_move_iterator(src.circles.begin()), + std::make_move_iterator(src.circles.end())); + } +} + +void VoronoiGraphUtils::reshape_longest_path(VoronoiGraph::ExPath &path) +{ + assert(path.path.size() >= 1); + + double actual_length = 0.; + const VoronoiGraph::Node *prev_node = nullptr; + VoronoiGraph::Nodes origin_path = path.path; // make copy + // index to path + size_t path_index = 0; + for (const VoronoiGraph::Node *node : origin_path) { + if (prev_node != nullptr) { + ++path_index; + actual_length += get_neighbor_distance(prev_node, node); + } + prev_node = node; + // increase actual length + + auto side_branches_item = path.side_branches.find(node); + if (side_branches_item == path.side_branches.end()) + continue; // no side branches + VoronoiGraph::ExPath::SideBranches &branches = side_branches_item + ->second; + if (actual_length >= branches.top().length) + continue; // no longer branch + + auto end_path = path.path.begin() + path_index; + VoronoiGraph::Path side_branch({path.path.begin(), end_path}, + actual_length); + std::reverse(side_branch.path.begin(), side_branch.path.end()); + VoronoiGraph::Path new_main_branch(std::move(branches.top())); + branches.pop(); + std::reverse(new_main_branch.path.begin(), new_main_branch.path.end()); + // add old main path store into side branches - may be it is not neccessary + branches.push(std::move(side_branch)); + + // swap side branch with main branch + path.path.erase(path.path.begin(), end_path); + path.path.insert(path.path.begin(), new_main_branch.path.begin(), + new_main_branch.path.end()); + + path.length += new_main_branch.length; + path.length -= actual_length; + path_index = new_main_branch.path.size(); + actual_length = new_main_branch.length; + } +} + +VoronoiGraph::ExPath VoronoiGraphUtils::create_longest_path( + const VoronoiGraph::Node *start_node) +{ + VoronoiGraph::ExPath longest_path; + CallStack call_stack; + call_stack.emplace( + std::make_unique(longest_path, start_node)); + + // depth search for longest path in graph + while (!call_stack.empty()) { + std::unique_ptr stack_function = std::move( + call_stack.top()); + call_stack.pop(); + stack_function->process(call_stack); + // stack function deleted + } + + reshape_longest_path(longest_path); + // after reshape it shoud be longest path for whole Voronoi Graph + return longest_path; +} + +Slic3r::Point VoronoiGraphUtils::get_edge_point(const VD::edge_type *edge, + double ratio) +{ + const VD::vertex_type *v0 = edge->vertex0(); + const VD::vertex_type *v1 = edge->vertex1(); + if (ratio <= std::numeric_limits::epsilon()) + return Point(v0->x(), v0->y()); + if (ratio >= 1. - std::numeric_limits::epsilon()) + return Point(v1->x(), v1->y()); + + if (edge->is_linear()) { + Point dir(v1->x() - v0->x(), v1->y() - v0->y()); + // normalize + dir *= ratio; + return Point(v0->x() + dir.x(), v0->y() + dir.y()); + } + + assert(edge->is_curved()); + // TODO: distance on curve + return Point(v0->x(), v0->y()); +} + +Slic3r::Point VoronoiGraphUtils::get_center_of_path( + const VoronoiGraph::Nodes &path, + double path_length) +{ + const VoronoiGraph::Node *prev_node = nullptr; + double half_path_length = path_length / 2.; + double distance = 0.; + for (const VoronoiGraph::Node *node : path) { + if (prev_node == nullptr) { // first call + prev_node = node; + continue; + } + const VoronoiGraph::Node::Neighbor *neighbor = get_neighbor(prev_node, + node); + distance += neighbor->edge_length; + if (distance >= half_path_length) { + // over half point is on + double ratio = 1. - (distance - half_path_length) / + neighbor->edge_length; + return get_edge_point(neighbor->edge, ratio); + } + prev_node = node; + } + // half_path_length must be inside path + // this means bad input params + assert(false); + return Point(0, 0); +} + +std::vector VoronoiGraphUtils::sample_voronoi_graph( + const VoronoiGraph & graph, + const SampleConfig & config, + VoronoiGraph::ExPath &longest_path) +{ + // first vertex on contour: + const VoronoiGraph::Node *start_node = nullptr; + for (const auto &[key, value] : graph.data) { + const VD::vertex_type & vertex = *key; + Voronoi::VertexCategory category = Voronoi::vertex_category(vertex); + if (category == Voronoi::VertexCategory::OnContour) { + start_node = &value; + break; + } + } + // every island has to have a point on contour + assert(start_node != nullptr); + + longest_path = create_longest_path(start_node); + // longest_path = create_longest_path_recursive(start_node); + if (longest_path.length < + config.max_length_for_one_support_point) { // create only one + // point in center + // sample in center of voronoi + return {get_center_of_path(longest_path.path, longest_path.length)}; + } + + std::vector points; + points.push_back(get_offseted_point(*start_node, config.start_distance)); + + return points; +} + +void VoronoiGraphUtils::draw(SVG &svg, const VoronoiGraph &graph, coord_t width) +{ + for (const auto &[key, value] : graph.data) { + svg.draw(Point(key->x(), key->y()), "lightgray", width); + for (const auto &n : value.neighbors) { + if (n.edge->vertex0() > n.edge->vertex1()) continue; + auto v0 = *n.edge->vertex0(); + Point from(v0.x(), v0.y()); + auto v1 = *n.edge->vertex1(); + Point to(v1.x(), v1.y()); + svg.draw(Line(from, to), "gray", width); + + Point center = from + to; + center *= .5; + // svg.draw_text(center, + // (std::to_string(std::round(n.edge_length/3e5)/100.)).c_str(), "gray"); + } + } +} + +void VoronoiGraphUtils::draw(SVG & svg, + const VoronoiGraph::Nodes &path, + coord_t width, + const char * color, + bool finish) +{ + const VoronoiGraph::Node *prev_node = (finish) ? path.back() : nullptr; + int index = 0; + for (auto &node : path) { + ++index; + if (prev_node == nullptr) { + prev_node = node; + continue; + } + Point from(prev_node->vertex->x(), prev_node->vertex->y()); + Point to(node->vertex->x(), node->vertex->y()); + svg.draw(Line(from, to), color, width); + + svg.draw_text(from, std::to_string(index - 1).c_str(), color); + svg.draw_text(to, std::to_string(index).c_str(), color); + prev_node = node; + } +} + +void VoronoiGraphUtils::draw(SVG & svg, + const VoronoiGraph::ExPath &path, + coord_t width) +{ + const char *circlePathColor = "green"; + const char *sideBranchesColor = "blue"; + const char *mainPathColor = "red"; + + for (auto &circle : path.circles) { + draw(svg, circle.path, width, circlePathColor, true); + Point center(0, 0); + for (auto p : circle.path) { + center.x() += p->vertex->x(); + center.y() += p->vertex->y(); + } + center.x() /= circle.path.size(); + center.y() /= circle.path.size(); + + svg.draw_text(center, + ("C" + std::to_string(&circle - &path.circles.front())) + .c_str(), + circlePathColor); + } + + for (const auto &branches : path.side_branches) { + auto tmp = branches.second; // copy + while (!tmp.empty()) { + const auto &branch = tmp.top(); + auto path = branch.path; + path.insert(path.begin(), branches.first); + draw(svg, path, width, sideBranchesColor); + tmp.pop(); + } + } + + draw(svg, path.path, width, mainPathColor); +} diff --git a/src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.hpp b/src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.hpp new file mode 100644 index 0000000000..d24f17ff96 --- /dev/null +++ b/src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.hpp @@ -0,0 +1,186 @@ +#ifndef slic3r_SLA_SuppotstIslands_VoronoiGraphUtils_hpp_ +#define slic3r_SLA_SuppotstIslands_VoronoiGraphUtils_hpp_ + +#include +#include +#include +#include +#include +#include "VoronoiGraph.hpp" +#include "SampleConfig.hpp" + +// for debug draw purpose +#include "SVG.hpp" + +namespace Slic3r::sla { + +/// +/// Class which contain collection of static function +/// for work with Voronoi Graph. +/// +class VoronoiGraphUtils { + using VD = Slic3r::Geometry::VoronoiDiagram; +public: + VoronoiGraphUtils() = delete; + + // return node from graph by vertex, when no exists create one + static VoronoiGraph::Node *getNode( + VoronoiGraph & graph, + const VD::vertex_type *vertex, + const VD::edge_type * edge, + const Lines & lines + ); + + /// + /// calculate distances to border of island and length on skeleton + /// + /// Input anotated voronoi diagram + /// (use function Slic3r::Voronoi::annotate_inside_outside) + /// Source lines for voronoi diagram + /// Extended voronoi graph by distances and length + static VoronoiGraph getSkeleton(const VD &vd, const Lines &lines); + + /// + /// For generating support point in distance from node + /// + /// Node lay on outline with only one neighbor + /// Distance from outline + /// + static Slic3r::Point get_offseted_point(const VoronoiGraph::Node &node, + double padding); + + /// + /// find neighbor and return distance between nodes + /// + /// source of neighborse + /// neighbor node + /// When neighbor return distance between neighbor Nodes + /// otherwise no value + static const VoronoiGraph::Node::Neighbor *get_neighbor( + const VoronoiGraph::Node *from, const VoronoiGraph::Node *to); + + /// + /// use function get_neighbor + /// when not neighbor assert + /// + /// source Node + /// destination Node + /// distance between Nodes or Assert when not neighbor + static double get_neighbor_distance(const VoronoiGraph::Node *from, + const VoronoiGraph::Node *to); + + /// + /// Create longest node path over circle together with side branches + /// + /// Source circle, can't be connected with another circle + /// Circle side branches from nodes of circle + /// Path before circle - defince input point to circle + /// Longest nodes path and its length + static VoronoiGraph::Path find_longest_path_on_circle( + const VoronoiGraph::Circle & circle, + const VoronoiGraph::ExPath::SideBranchesMap &side_branches); + + /// + /// Serach longest path from input_node throw Nodes in connected circles, + /// when circle is alone call find_longest_path_on_circle. + /// + /// Node on circle + /// index of circle with input node + /// Hold Circles, connection of circles and Side branches + /// Longest path from input node + static VoronoiGraph::Path find_longest_path_on_circles( + const VoronoiGraph::Node & input_node, + size_t finished_circle_index, + const VoronoiGraph::ExPath &ex_path); + + /// + /// Function for detection circle in passed path. + /// + /// Already passed path in Graph + /// Actual neighbor possible created circle + /// Circle when exists + static std::optional create_circle( + const VoronoiGraph::Path & path, + const VoronoiGraph::Node::Neighbor &neighbor); + + /// + /// Move source connected circles into destination + /// + /// In/Out param + /// Input possible modified, do not use it after this function + /// Count of destination circles before + /// merge Source circle are append afted destination, therfore all src + /// indexes must be increased by destination circle count + static void merge_connected_circle( + VoronoiGraph::ExPath::ConnectedCircles &dst, + VoronoiGraph::ExPath::ConnectedCircles &src, + size_t dst_circle_count); + + /// + /// move data from source to destination + /// side_branches + circles + connected_circle + /// + /// destination extended path - append data from source + /// source extended path - data will be moved to dst + static void append_neighbor_branch(VoronoiGraph::ExPath &dst, + VoronoiGraph::ExPath &src); + + /// + /// Heal starting from random point. + /// Compare length of all starting path with side branches + /// when side branch is longer than swap with start path + /// + /// IN/OUT path to be fixed after creating longest path + /// from one point + static void reshape_longest_path(VoronoiGraph::ExPath &path); + + /// + /// Extract the longest path from voronoi graph + /// by own function call stack(IStackFunction). + /// Restructuralize path by branch created from random point. + /// + /// Random point from outline. + static VoronoiGraph::ExPath create_longest_path( + const VoronoiGraph::Node *start_node); + + /// + /// Create point on edge defined by neighbor + /// in distance defined by edge length ratio + /// + static Point get_edge_point(const VD::edge_type *edge, double ratio); + + /// + /// Find point lay in center of path + /// Distance from this point to front of path + /// is same as distance to back of path + /// + /// Queue of neighbor nodes.(must be neighbor) + /// length of path + /// Point laying on voronoi diagram + static Point get_center_of_path(const VoronoiGraph::Nodes &path, + double path_length); + + /// + /// Sample voronoi skeleton + /// + /// Inside skeleton of island + /// Params for sampling + /// OUTPUT: longest path in graph + /// Vector of sampled points or Empty when distance from edge is + /// bigger than max_distance + static std::vector sample_voronoi_graph(const VoronoiGraph & graph, + const SampleConfig & config, + VoronoiGraph::ExPath &longest_path); + +public: //draw function for debug + static void draw(SVG &svg, const VoronoiGraph &graph, coord_t width); + static void draw(SVG & svg, + const VoronoiGraph::Nodes &path, + coord_t width, + const char * color, + bool finish = false); + static void draw(SVG &svg, const VoronoiGraph::ExPath &path, coord_t width); +}; + +} // namespace Slic3r::sla +#endif // slic3r_SLA_SuppotstIslands_VoronoiGraphUtils_hpp_ diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index 1caaf7e955..ce87071997 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -2283,3 +2283,4 @@ TEST_CASE("Voronoi cell doesn't contain a source point - SPE-2298", "[VoronoiCel REQUIRE(vd.is_valid()); } +// */ diff --git a/tests/sla_print/sla_supptgen_tests.cpp b/tests/sla_print/sla_supptgen_tests.cpp index 67d7c69f8f..249e666d36 100644 --- a/tests/sla_print/sla_supptgen_tests.cpp +++ b/tests/sla_print/sla_supptgen_tests.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "sla_test_utils.hpp" namespace Slic3r { namespace sla { @@ -132,4 +134,128 @@ TEST_CASE("Two parallel plates should be supported", "[SupGen][Hollowed]") REQUIRE(!pts.empty()); } +ExPolygons createTestIslands(double size) +{ + ExPolygon triangle( + Polygon{{.0, .0}, + {size, .0}, + {size / 2., sqrt(size * size - size * size / 4)}}); + ExPolygon sharp_triangle( + Polygon{{.0, size / 2}, {.0, .0}, {2 * size, .0}}); + ExPolygon triangle_with_hole({{.0, .0}, + {size, .0}, + {size / 2., + sqrt(size * size - size * size / 4)}}, + {{size / 4, size / 4}, + {size / 2, size / 2}, + {size / 2, size / 4}}); + ExPolygon square(Polygon{{.0, size}, {.0, .0}, {size, .0}, {size, size}}); + ExPolygon rect( + Polygon{{.0, size}, {.0, .0}, {2 * size, .0}, {2 * size, size}}); + ExPolygon rect_with_hole({{-size, size}, // rect CounterClockWise + {-size, -size}, + {size, -size}, + {size, size}}, + {{0., size / 2}, // inside rect ClockWise + {size / 2, 0.}, + {0., -size / 2}, + {-size / 2, 0.}}); + // need post reorganization of longest path + ExPolygon mountains({{0., 0.}, + {size, 0.}, + {5 * size / 6, size}, + {4 * size / 6, size / 6}, + {3 * size / 7, 2 * size}, + {2 * size / 7, size / 6}, + {size / 7, size}}); + ExPolygon rect_with_4_hole(Polygon{{0., size}, // rect CounterClockWise + {0., 0.}, + {size, 0.}, + {size, size}}); + // inside rects ClockWise + double size5 = size / 5.; + rect_with_4_hole.holes = Polygons{{{size5, 4 * size5}, + {2 * size5, 4 * size5}, + {2 * size5, 3 * size5}, + {size5, 3 * size5}}, + {{3 * size5, 4 * size5}, + {4 * size5, 4 * size5}, + {4 * size5, 3 * size5}, + {3 * size5, 3 * size5}}, + {{size5, 2 * size5}, + {2 * size5, 2 * size5}, + {2 * size5, size5}, + {size5, size5}}, + {{3 * size5, 2 * size5}, + {4 * size5, 2 * size5}, + {4 * size5, size5}, + {3 * size5, size5}}}; + + size_t count_cirlce_lines = 1000; // test stack overfrow + double r_CCW = size / 2; + double r_CW = r_CCW - size / 6; + // CCW: couter clock wise, CW: clock wise + Points circle_CCW, circle_CW; + circle_CCW.reserve(count_cirlce_lines); + circle_CW.reserve(count_cirlce_lines); + for (size_t i = 0; i < count_cirlce_lines; ++i) { + double alpha = (2 * M_PI * i) / count_cirlce_lines; + double sina = sin(alpha); + double cosa = cos(alpha); + circle_CCW.emplace_back(-r_CCW * sina, r_CCW * cosa); + circle_CW.emplace_back(r_CW * sina, r_CW * cosa); + } + ExPolygon double_circle(circle_CCW, circle_CW); + + TriangleMesh mesh = load_model("frog_legs.obj"); + TriangleMeshSlicer slicer{&mesh}; + std::vector grid({0.1f}); + std::vector slices; + slicer.slice(grid, SlicingMode::Regular, 0.05f, &slices, [] {}); + ExPolygon frog_leg = slices.front()[1]; // + + return { + triangle, square, + sharp_triangle, rect, + rect_with_hole, triangle_with_hole, + rect_with_4_hole, mountains, + double_circle + //, frog_leg + }; +} + +std::vector test_island_sampling(const ExPolygon & island, + const SampleConfig &config) +{ + auto points = SupportPointGenerator::uniform_cover_island(island, config); + CHECK(!points.empty()); + + // all points must be inside of island + for (const auto &point : points) { CHECK(island.contains(point)); } + return points; +} + +TEST_CASE("Small islands should be supported in center", "[SupGen][VoronoiSkeleton]") +{ + double size = 3e7; + SampleConfig cfg; + cfg.max_distance = size + 0.1; + cfg.sample_size = size / 5; + cfg.start_distance = 0.2 * size; // radius of support head + cfg.curve_sample = 0.1 * size; + cfg.max_length_for_one_support_point = 3 * size; + + ExPolygons islands = createTestIslands(size); + for (auto &island : islands) { + auto points = test_island_sampling(island, cfg); + double angle = 3.14 / 3; // cca 60 degree + + island.rotate(angle); + auto pointsR = test_island_sampling(island, cfg); + for (Point &p : pointsR) p.rotate(-angle); + + // points should be equal to pointsR + } +} + }} // namespace Slic3r::sla