mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-11 22:09:02 +08:00
separate Voronoi graf and searching longest path into own files
This commit is contained in:
parent
fb083cc610
commit
7c4b711aeb
@ -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<EFBFBD> M<EFBFBD>sz<EFBFBD>ros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Vojt<EFBFBD>ch Bubn<EFBFBD>k @bubnikv, Pavel Miku<EFBFBD> @Godrak, Luk<EFBFBD> Hejl @hejllukas, Luk<EFBFBD> Mat<EFBFBD>na @lukasmatena, Filip Sykala @Jony01, David Koc<EFBFBD>k @kocikdav, Vojt<EFBFBD>ch Kr<EFBFBD>l @vojtechkral
|
||||
#/|/ Copyright (c) BambuStudio 2023 manch1n @manch1n
|
||||
#/|/ Copyright (c) 2023 Mimoja @Mimoja
|
||||
#/|/ Copyright (c) 2022 ole00 @ole00
|
||||
@ -453,6 +453,20 @@ set(SLIC3R_SOURCES
|
||||
SLA/BranchingTreeSLA.cpp
|
||||
SLA/ZCorrection.hpp
|
||||
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 ()
|
||||
|
23
src/libslic3r/SLA/SupportIslands/EvaluateNeighbor.cpp
Normal file
23
src/libslic3r/SLA/SupportIslands/EvaluateNeighbor.cpp
Normal file
@ -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<PostProcessNeighbors>(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<ExpandNeighbor>(data, neighbor));
|
||||
}
|
36
src/libslic3r/SLA/SupportIslands/EvaluateNeighbor.hpp
Normal file
36
src/libslic3r/SLA/SupportIslands/EvaluateNeighbor.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef slic3r_SLA_SuppotstIslands_EvaluateNeighbor_hpp_
|
||||
#define slic3r_SLA_SuppotstIslands_EvaluateNeighbor_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "IStackFunction.hpp"
|
||||
#include "PostProcessNeighbors.hpp"
|
||||
#include "VoronoiGraph.hpp"
|
||||
|
||||
namespace Slic3r::sla {
|
||||
|
||||
/// <summary>
|
||||
/// create on stack
|
||||
/// 1 * PostProcessNeighbors
|
||||
/// N * ExpandNode
|
||||
/// </summary>
|
||||
class EvaluateNeighbor : public IStackFunction
|
||||
{
|
||||
std::unique_ptr<PostProcessNeighbors> 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.));
|
||||
|
||||
/// <summary>
|
||||
/// create on stack
|
||||
/// 1 * PostProcessNeighbors
|
||||
/// N * ExpandNode
|
||||
/// </summary>
|
||||
virtual void process(CallStack &call_stack);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::sla
|
||||
#endif // slic3r_SLA_SuppotstIslands_EvaluateNeighbor_hpp_
|
44
src/libslic3r/SLA/SupportIslands/ExpandNeighbor.cpp
Normal file
44
src/libslic3r/SLA/SupportIslands/ExpandNeighbor.cpp
Normal file
@ -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<PostProcessNeighbor>(data);
|
||||
VoronoiGraph::ExPath &neighbor_path = post_process_neighbor->neighbor_path;
|
||||
|
||||
call_stack.emplace(std::move(post_process_neighbor));
|
||||
call_stack.emplace(
|
||||
std::make_unique<EvaluateNeighbor>(neighbor_path, neighbor.node,
|
||||
neighbor.edge_length,
|
||||
data.act_path));
|
||||
}
|
35
src/libslic3r/SLA/SupportIslands/ExpandNeighbor.hpp
Normal file
35
src/libslic3r/SLA/SupportIslands/ExpandNeighbor.hpp
Normal file
@ -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 {
|
||||
|
||||
/// <summary>
|
||||
/// Expand neighbor to
|
||||
/// - PostProcessNeighbor
|
||||
/// - EvaluateNeighbor
|
||||
/// </summary>
|
||||
class ExpandNeighbor : public IStackFunction
|
||||
{
|
||||
NodeDataWithResult & data;
|
||||
const VoronoiGraph::Node::Neighbor &neighbor;
|
||||
|
||||
public:
|
||||
ExpandNeighbor(NodeDataWithResult & data,
|
||||
const VoronoiGraph::Node::Neighbor &neighbor);
|
||||
|
||||
/// <summary>
|
||||
/// Expand neighbor to
|
||||
/// - PostProcessNeighbor
|
||||
/// - EvaluateNeighbor
|
||||
/// </summary>
|
||||
/// <param name="call_stack">Output callStack</param>
|
||||
virtual void process(CallStack &call_stack);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::sla
|
||||
#endif // slic3r_SLA_SuppotstIslands_ExpandNeighbor_hpp_
|
23
src/libslic3r/SLA/SupportIslands/IStackFunction.hpp
Normal file
23
src/libslic3r/SLA/SupportIslands/IStackFunction.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
#ifndef slic3r_SLA_SuppotstIslands_IStackFunction_hpp_
|
||||
#define slic3r_SLA_SuppotstIslands_IStackFunction_hpp_
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
|
||||
namespace Slic3r::sla {
|
||||
|
||||
/// <summary>
|
||||
/// Interface for objects inside of CallStack.
|
||||
/// It is way to prevent stack overflow inside recurrent functions.
|
||||
/// </summary>
|
||||
class IStackFunction
|
||||
{
|
||||
public:
|
||||
virtual ~IStackFunction() = default;
|
||||
virtual void process(std::stack<std::unique_ptr<IStackFunction>> &call_stack) = 0;
|
||||
};
|
||||
|
||||
using CallStack = std::stack<std::unique_ptr<IStackFunction>>;
|
||||
|
||||
} // namespace Slic3r::sla
|
||||
#endif // slic3r_SLA_SuppotstIslands_IStackFunction_hpp_
|
72
src/libslic3r/SLA/SupportIslands/NodeDataWithResult.hpp
Normal file
72
src/libslic3r/SLA/SupportIslands/NodeDataWithResult.hpp
Normal file
@ -0,0 +1,72 @@
|
||||
#ifndef slic3r_SLA_SuppotstIslands_NodeDataWithResult_hpp_
|
||||
#define slic3r_SLA_SuppotstIslands_NodeDataWithResult_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include "VoronoiGraph.hpp"
|
||||
|
||||
namespace Slic3r::sla {
|
||||
|
||||
/// <summary>
|
||||
/// DTO for process node during depth search
|
||||
/// which create longest path in voronoi graph
|
||||
/// </summary>
|
||||
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<const VoronoiGraph::Node *> skip_nodes; // circle
|
||||
|
||||
// store all circle indexes this node is lay on
|
||||
// used to create connected circles structure
|
||||
std::vector<size_t> 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<size_t> 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_
|
40
src/libslic3r/SLA/SupportIslands/PostProcessNeighbor.cpp
Normal file
40
src/libslic3r/SLA/SupportIslands/PostProcessNeighbor.cpp
Normal file
@ -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));
|
||||
}
|
36
src/libslic3r/SLA/SupportIslands/PostProcessNeighbor.hpp
Normal file
36
src/libslic3r/SLA/SupportIslands/PostProcessNeighbor.hpp
Normal file
@ -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 {
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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_
|
50
src/libslic3r/SLA/SupportIslands/PostProcessNeighbors.cpp
Normal file
50
src/libslic3r/SLA/SupportIslands/PostProcessNeighbors.cpp
Normal file
@ -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;
|
||||
}
|
35
src/libslic3r/SLA/SupportIslands/PostProcessNeighbors.hpp
Normal file
35
src/libslic3r/SLA/SupportIslands/PostProcessNeighbors.hpp
Normal file
@ -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 {
|
||||
|
||||
/// <summary>
|
||||
/// call after all neighbors are processed
|
||||
/// </summary>
|
||||
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_
|
28
src/libslic3r/SLA/SupportIslands/SampleConfig.hpp
Normal file
28
src/libslic3r/SLA/SupportIslands/SampleConfig.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef slic3r_SLA_SuppotstIslands_SampleConfig_hpp_
|
||||
#define slic3r_SLA_SuppotstIslands_SampleConfig_hpp_
|
||||
|
||||
namespace Slic3r::sla {
|
||||
|
||||
/// <summary>
|
||||
/// Configuration fro sampling voronoi diagram for support point generator
|
||||
/// </summary>
|
||||
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_
|
144
src/libslic3r/SLA/SupportIslands/VoronoiGraph.hpp
Normal file
144
src/libslic3r/SLA/SupportIslands/VoronoiGraph.hpp
Normal file
@ -0,0 +1,144 @@
|
||||
#ifndef slic3r_SLA_SuppotstIslands_VoronoiGraph_hpp_
|
||||
#define slic3r_SLA_SuppotstIslands_VoronoiGraph_hpp_
|
||||
|
||||
#include <map>
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
#include <numeric>
|
||||
|
||||
namespace Slic3r::sla {
|
||||
|
||||
/// <summary>
|
||||
/// DTO store skeleton With longest path
|
||||
/// </summary>
|
||||
struct VoronoiGraph
|
||||
{
|
||||
using VD = Slic3r::Geometry::VoronoiDiagram;
|
||||
struct Node;
|
||||
using Nodes = std::vector<const Node *>;
|
||||
struct Path;
|
||||
struct ExPath;
|
||||
using Circle = Path;
|
||||
std::map<const VD::vertex_type *, Node> data;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Node data structure for Voronoi Graph.
|
||||
/// Extend information about Voronoi vertex.
|
||||
/// </summary>
|
||||
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<Neighbor> neighbors;
|
||||
|
||||
// constructor
|
||||
Node(const VD::vertex_type *vertex, double distance)
|
||||
: vertex(vertex), longestDistance(0.), distance(distance), neighbors()
|
||||
{}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Surrond GraphNode data type.
|
||||
/// Extend information about voronoi edge.
|
||||
/// </summary>
|
||||
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)
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DTO represents path over nodes of VoronoiGraph
|
||||
/// store queue of Nodes
|
||||
/// store length of path
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// DTO
|
||||
/// extends path with side branches and circles(connection of circles)
|
||||
/// </summary>
|
||||
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<VoronoiGraph::Path,
|
||||
std::vector<VoronoiGraph::Path>,
|
||||
OrderLengthFromLongest>;
|
||||
using SideBranchesMap = std::map<const VoronoiGraph::Node *, SideBranches>;
|
||||
|
||||
// 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<VoronoiGraph::Circle> 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<size_t, std::set<size_t>>;
|
||||
ConnectedCircles connected_circle;
|
||||
|
||||
public:
|
||||
ExPath() = default;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::sla
|
||||
#endif // slic3r_SLA_SuppotstIslands_VoronoiGraph_hpp_
|
584
src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.cpp
Normal file
584
src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.cpp
Normal file
@ -0,0 +1,584 @@
|
||||
#include "VoronoiGraphUtils.hpp"
|
||||
|
||||
#include <libslic3r/VoronoiOffset.hpp>
|
||||
#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<const VD::vertex_type *, VoronoiGraph::Node> &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<VoronoiGraph::Circle> &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<size_t> &connected_circles = connected_circle_item->second;
|
||||
|
||||
// collect all circle ndoes
|
||||
std::set<const VoronoiGraph::Node *> 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<const VoronoiGraph::Node *> done; // all ready checked
|
||||
// on top is shortest path
|
||||
std::priority_queue<VoronoiGraph::Path, std::vector<VoronoiGraph::Path>,
|
||||
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<VoronoiGraph::Circle> 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<size_t> 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<size_t> 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<EvaluateNeighbor>(longest_path, start_node));
|
||||
|
||||
// depth search for longest path in graph
|
||||
while (!call_stack.empty()) {
|
||||
std::unique_ptr<IStackFunction> 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<double>::epsilon())
|
||||
return Point(v0->x(), v0->y());
|
||||
if (ratio >= 1. - std::numeric_limits<double>::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<Slic3r::Point> 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<Point> 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);
|
||||
}
|
186
src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.hpp
Normal file
186
src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.hpp
Normal file
@ -0,0 +1,186 @@
|
||||
#ifndef slic3r_SLA_SuppotstIslands_VoronoiGraphUtils_hpp_
|
||||
#define slic3r_SLA_SuppotstIslands_VoronoiGraphUtils_hpp_
|
||||
|
||||
#include <optional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include "VoronoiGraph.hpp"
|
||||
#include "SampleConfig.hpp"
|
||||
|
||||
// for debug draw purpose
|
||||
#include "SVG.hpp"
|
||||
|
||||
namespace Slic3r::sla {
|
||||
|
||||
/// <summary>
|
||||
/// Class which contain collection of static function
|
||||
/// for work with Voronoi Graph.
|
||||
/// </summary>
|
||||
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
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// calculate distances to border of island and length on skeleton
|
||||
/// </summary>
|
||||
/// <param name="voronoi_diagram">Input anotated voronoi diagram
|
||||
/// (use function Slic3r::Voronoi::annotate_inside_outside)</param>
|
||||
/// <param name="lines">Source lines for voronoi diagram</param>
|
||||
/// <returns>Extended voronoi graph by distances and length</returns>
|
||||
static VoronoiGraph getSkeleton(const VD &vd, const Lines &lines);
|
||||
|
||||
/// <summary>
|
||||
/// For generating support point in distance from node
|
||||
/// </summary>
|
||||
/// <param name="node">Node lay on outline with only one neighbor</param>
|
||||
/// <param name="padding">Distance from outline</param>
|
||||
/// <returns></returns>
|
||||
static Slic3r::Point get_offseted_point(const VoronoiGraph::Node &node,
|
||||
double padding);
|
||||
|
||||
/// <summary>
|
||||
/// find neighbor and return distance between nodes
|
||||
/// </summary>
|
||||
/// <param name="from">source of neighborse</param>
|
||||
/// <param name="to">neighbor node</param>
|
||||
/// <returns>When neighbor return distance between neighbor Nodes
|
||||
/// otherwise no value</returns>
|
||||
static const VoronoiGraph::Node::Neighbor *get_neighbor(
|
||||
const VoronoiGraph::Node *from, const VoronoiGraph::Node *to);
|
||||
|
||||
/// <summary>
|
||||
/// use function get_neighbor
|
||||
/// when not neighbor assert
|
||||
/// </summary>
|
||||
/// <param name="from">source Node</param>
|
||||
/// <param name="to">destination Node</param>
|
||||
/// <returns>distance between Nodes or Assert when not neighbor</returns>
|
||||
static double get_neighbor_distance(const VoronoiGraph::Node *from,
|
||||
const VoronoiGraph::Node *to);
|
||||
|
||||
/// <summary>
|
||||
/// Create longest node path over circle together with side branches
|
||||
/// </summary>
|
||||
/// <param name="circle">Source circle, can't be connected with another circle</param>
|
||||
/// <param name="side_branches">Circle side branches from nodes of circle</param>
|
||||
/// <param name="start_path">Path before circle - defince input point to circle</param>
|
||||
/// <returns>Longest nodes path and its length</returns>
|
||||
static VoronoiGraph::Path find_longest_path_on_circle(
|
||||
const VoronoiGraph::Circle & circle,
|
||||
const VoronoiGraph::ExPath::SideBranchesMap &side_branches);
|
||||
|
||||
/// <summary>
|
||||
/// Serach longest path from input_node throw Nodes in connected circles,
|
||||
/// when circle is alone call find_longest_path_on_circle.
|
||||
/// </summary>
|
||||
/// <param name="input_node">Node on circle</param>
|
||||
/// <param name="finished_circle_index">index of circle with input node</param>
|
||||
/// <param name="ex_path">Hold Circles, connection of circles and Side branches</param>
|
||||
/// <returns>Longest path from input node</returns>
|
||||
static VoronoiGraph::Path find_longest_path_on_circles(
|
||||
const VoronoiGraph::Node & input_node,
|
||||
size_t finished_circle_index,
|
||||
const VoronoiGraph::ExPath &ex_path);
|
||||
|
||||
/// <summary>
|
||||
/// Function for detection circle in passed path.
|
||||
/// </summary>
|
||||
/// <param name="path">Already passed path in Graph</param>
|
||||
/// <param name="neighbor">Actual neighbor possible created circle</param>
|
||||
/// <returns>Circle when exists</returns>
|
||||
static std::optional<VoronoiGraph::Circle> create_circle(
|
||||
const VoronoiGraph::Path & path,
|
||||
const VoronoiGraph::Node::Neighbor &neighbor);
|
||||
|
||||
/// <summary>
|
||||
/// Move source connected circles into destination
|
||||
/// </summary>
|
||||
/// <param name="dst">In/Out param</param>
|
||||
/// <param name="src">Input possible modified, do not use it after this function</param>
|
||||
/// <param name="dst_circle_count">Count of destination circles before
|
||||
/// merge Source circle are append afted destination, therfore all src
|
||||
/// indexes must be increased by destination circle count</param>
|
||||
static void merge_connected_circle(
|
||||
VoronoiGraph::ExPath::ConnectedCircles &dst,
|
||||
VoronoiGraph::ExPath::ConnectedCircles &src,
|
||||
size_t dst_circle_count);
|
||||
|
||||
/// <summary>
|
||||
/// move data from source to destination
|
||||
/// side_branches + circles + connected_circle
|
||||
/// </summary>
|
||||
/// <param name="dst">destination extended path - append data from source</param>
|
||||
/// <param name="src">source extended path - data will be moved to dst</param>
|
||||
static void append_neighbor_branch(VoronoiGraph::ExPath &dst,
|
||||
VoronoiGraph::ExPath &src);
|
||||
|
||||
/// <summary>
|
||||
/// Heal starting from random point.
|
||||
/// Compare length of all starting path with side branches
|
||||
/// when side branch is longer than swap with start path
|
||||
/// </summary>
|
||||
/// <param name="path">IN/OUT path to be fixed after creating longest path
|
||||
/// from one point</param>
|
||||
static void reshape_longest_path(VoronoiGraph::ExPath &path);
|
||||
|
||||
/// <summary>
|
||||
/// Extract the longest path from voronoi graph
|
||||
/// by own function call stack(IStackFunction).
|
||||
/// Restructuralize path by branch created from random point.
|
||||
/// </summary>
|
||||
/// <param name="start_node">Random point from outline.</param>
|
||||
static VoronoiGraph::ExPath create_longest_path(
|
||||
const VoronoiGraph::Node *start_node);
|
||||
|
||||
/// <summary>
|
||||
/// Create point on edge defined by neighbor
|
||||
/// in distance defined by edge length ratio
|
||||
/// </summary>
|
||||
static Point get_edge_point(const VD::edge_type *edge, double ratio);
|
||||
|
||||
/// <summary>
|
||||
/// Find point lay in center of path
|
||||
/// Distance from this point to front of path
|
||||
/// is same as distance to back of path
|
||||
/// </summary>
|
||||
/// <param name="path">Queue of neighbor nodes.(must be neighbor)</param>
|
||||
/// <param name="path_length">length of path</param>
|
||||
/// <returns>Point laying on voronoi diagram</returns>
|
||||
static Point get_center_of_path(const VoronoiGraph::Nodes &path,
|
||||
double path_length);
|
||||
|
||||
/// <summary>
|
||||
/// Sample voronoi skeleton
|
||||
/// </summary>
|
||||
/// <param name="graph">Inside skeleton of island</param>
|
||||
/// <param name="config">Params for sampling</param>
|
||||
/// <param name="longest_path">OUTPUT: longest path in graph</param>
|
||||
/// <returns>Vector of sampled points or Empty when distance from edge is
|
||||
/// bigger than max_distance</returns>
|
||||
static std::vector<Point> 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_
|
@ -2283,3 +2283,4 @@ TEST_CASE("Voronoi cell doesn't contain a source point - SPE-2298", "[VoronoiCel
|
||||
|
||||
REQUIRE(vd.is_valid());
|
||||
}
|
||||
// */
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
|
||||
#include <libslic3r/SLA/SupportIslands/SampleConfig.hpp>
|
||||
|
||||
#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<float> grid({0.1f});
|
||||
std::vector<ExPolygons> 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<Point> 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user