mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-01-31 00:48:41 +03:00
update
This commit is contained in:
322
src/libslic3r/AABBMesh.cpp
Normal file
322
src/libslic3r/AABBMesh.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
#include "AABBMesh.hpp"
|
||||
#include <Execution/ExecutionTBB.hpp>
|
||||
|
||||
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AABBMesh::AABBImpl {
|
||||
private:
|
||||
AABBTreeIndirect::Tree3f m_tree;
|
||||
double m_triangle_ray_epsilon;
|
||||
|
||||
public:
|
||||
void init(const indexed_triangle_set &its, bool calculate_epsilon)
|
||||
{
|
||||
m_triangle_ray_epsilon = 0.000001;
|
||||
if (calculate_epsilon) {
|
||||
// Calculate epsilon from average triangle edge length.
|
||||
double l = its_average_edge_length(its);
|
||||
if (l > 0)
|
||||
m_triangle_ray_epsilon = 0.000001 * l * l;
|
||||
}
|
||||
m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||
its.vertices, its.indices);
|
||||
}
|
||||
|
||||
void intersect_ray(const indexed_triangle_set &its,
|
||||
const Vec3d & s,
|
||||
const Vec3d & dir,
|
||||
igl::Hit & hit)
|
||||
{
|
||||
AABBTreeIndirect::intersect_ray_first_hit(its.vertices, its.indices,
|
||||
m_tree, s, dir, hit, m_triangle_ray_epsilon);
|
||||
}
|
||||
|
||||
void intersect_ray(const indexed_triangle_set &its,
|
||||
const Vec3d & s,
|
||||
const Vec3d & dir,
|
||||
std::vector<igl::Hit> & hits)
|
||||
{
|
||||
AABBTreeIndirect::intersect_ray_all_hits(its.vertices, its.indices,
|
||||
m_tree, s, dir, hits, m_triangle_ray_epsilon);
|
||||
}
|
||||
|
||||
double squared_distance(const indexed_triangle_set & its,
|
||||
const Vec3d & point,
|
||||
int & i,
|
||||
Eigen::Matrix<double, 1, 3> &closest)
|
||||
{
|
||||
size_t idx_unsigned = 0;
|
||||
Vec3d closest_vec3d(closest);
|
||||
double dist =
|
||||
AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
its.vertices, its.indices, m_tree, point, idx_unsigned,
|
||||
closest_vec3d);
|
||||
i = int(idx_unsigned);
|
||||
closest = closest_vec3d;
|
||||
return dist;
|
||||
}
|
||||
};
|
||||
|
||||
template<class M> void AABBMesh::init(const M &mesh, bool calculate_epsilon)
|
||||
{
|
||||
// Build the AABB accelaration tree
|
||||
m_aabb->init(*m_tm, calculate_epsilon);
|
||||
}
|
||||
|
||||
AABBMesh::AABBMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon)
|
||||
: m_tm(&tmesh)
|
||||
, m_aabb(new AABBImpl())
|
||||
, m_vfidx{tmesh}
|
||||
, m_fnidx{its_face_neighbors(tmesh)}
|
||||
{
|
||||
init(tmesh, calculate_epsilon);
|
||||
}
|
||||
|
||||
AABBMesh::AABBMesh(const TriangleMesh &mesh, bool calculate_epsilon)
|
||||
: m_tm(&mesh.its)
|
||||
, m_aabb(new AABBImpl())
|
||||
, m_vfidx{mesh.its}
|
||||
, m_fnidx{its_face_neighbors(mesh.its)}
|
||||
{
|
||||
init(mesh, calculate_epsilon);
|
||||
}
|
||||
|
||||
AABBMesh::~AABBMesh() {}
|
||||
|
||||
AABBMesh::AABBMesh(const AABBMesh &other)
|
||||
: m_tm(other.m_tm)
|
||||
, m_aabb(new AABBImpl(*other.m_aabb))
|
||||
, m_vfidx{other.m_vfidx}
|
||||
, m_fnidx{other.m_fnidx}
|
||||
{}
|
||||
|
||||
AABBMesh &AABBMesh::operator=(const AABBMesh &other)
|
||||
{
|
||||
m_tm = other.m_tm;
|
||||
m_aabb.reset(new AABBImpl(*other.m_aabb));
|
||||
m_vfidx = other.m_vfidx;
|
||||
m_fnidx = other.m_fnidx;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
AABBMesh &AABBMesh::operator=(AABBMesh &&other) = default;
|
||||
|
||||
AABBMesh::AABBMesh(AABBMesh &&other) = default;
|
||||
|
||||
|
||||
|
||||
const std::vector<Vec3f>& AABBMesh::vertices() const
|
||||
{
|
||||
return m_tm->vertices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const std::vector<Vec3i>& AABBMesh::indices() const
|
||||
{
|
||||
return m_tm->indices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Vec3f& AABBMesh::vertices(size_t idx) const
|
||||
{
|
||||
return m_tm->vertices[idx];
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Vec3i& AABBMesh::indices(size_t idx) const
|
||||
{
|
||||
return m_tm->indices[idx];
|
||||
}
|
||||
|
||||
|
||||
Vec3d AABBMesh::normal_by_face_id(int face_id) const {
|
||||
|
||||
return its_unnormalized_normal(*m_tm, face_id).cast<double>().normalized();
|
||||
}
|
||||
|
||||
|
||||
AABBMesh::hit_result
|
||||
AABBMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
assert(is_approx(dir.norm(), 1.));
|
||||
igl::Hit hit{-1, -1, 0.f, 0.f, 0.f};
|
||||
hit.t = std::numeric_limits<float>::infinity();
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
if (! m_holes.empty()) {
|
||||
|
||||
// If there are holes, the hit_results will be made by
|
||||
// query_ray_hits (object) and filter_hits (holes):
|
||||
return filter_hits(query_ray_hits(s, dir));
|
||||
}
|
||||
#endif
|
||||
|
||||
m_aabb->intersect_ray(*m_tm, s, dir, hit);
|
||||
hit_result ret(*this);
|
||||
ret.m_t = double(hit.t);
|
||||
ret.m_dir = dir;
|
||||
ret.m_source = s;
|
||||
if(!std::isinf(hit.t) && !std::isnan(hit.t)) {
|
||||
ret.m_normal = this->normal_by_face_id(hit.id);
|
||||
ret.m_face_id = hit.id;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<AABBMesh::hit_result>
|
||||
AABBMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
std::vector<AABBMesh::hit_result> outs;
|
||||
std::vector<igl::Hit> hits;
|
||||
m_aabb->intersect_ray(*m_tm, s, dir, hits);
|
||||
|
||||
// The sort is necessary, the hits are not always sorted.
|
||||
std::sort(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
||||
|
||||
// Remove duplicates. They sometimes appear, for example when the ray is cast
|
||||
// along an axis of a cube due to floating-point approximations in igl (?)
|
||||
hits.erase(std::unique(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b)
|
||||
{ return a.t == b.t; }),
|
||||
hits.end());
|
||||
|
||||
// Convert the igl::Hit into hit_result
|
||||
outs.reserve(hits.size());
|
||||
for (const igl::Hit& hit : hits) {
|
||||
outs.emplace_back(AABBMesh::hit_result(*this));
|
||||
outs.back().m_t = double(hit.t);
|
||||
outs.back().m_dir = dir;
|
||||
outs.back().m_source = s;
|
||||
if(!std::isinf(hit.t) && !std::isnan(hit.t)) {
|
||||
outs.back().m_normal = this->normal_by_face_id(hit.id);
|
||||
outs.back().m_face_id = hit.id;
|
||||
}
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
AABBMesh::hit_result IndexedMesh::filter_hits(
|
||||
const std::vector<AABBMesh::hit_result>& object_hits) const
|
||||
{
|
||||
assert(! m_holes.empty());
|
||||
hit_result out(*this);
|
||||
|
||||
if (object_hits.empty())
|
||||
return out;
|
||||
|
||||
const Vec3d& s = object_hits.front().source();
|
||||
const Vec3d& dir = object_hits.front().direction();
|
||||
|
||||
// A helper struct to save an intersetion with a hole
|
||||
struct HoleHit {
|
||||
HoleHit(float t_p, const Vec3d& normal_p, bool entry_p) :
|
||||
t(t_p), normal(normal_p), entry(entry_p) {}
|
||||
float t;
|
||||
Vec3d normal;
|
||||
bool entry;
|
||||
};
|
||||
std::vector<HoleHit> hole_isects;
|
||||
hole_isects.reserve(m_holes.size());
|
||||
|
||||
auto sf = s.cast<float>();
|
||||
auto dirf = dir.cast<float>();
|
||||
|
||||
// Collect hits on all holes, preserve information about entry/exit
|
||||
for (const sla::DrainHole& hole : m_holes) {
|
||||
std::array<std::pair<float, Vec3d>, 2> isects;
|
||||
if (hole.get_intersections(sf, dirf, isects)) {
|
||||
// Ignore hole hits behind the source
|
||||
if (isects[0].first > 0.f) hole_isects.emplace_back(isects[0].first, isects[0].second, true);
|
||||
if (isects[1].first > 0.f) hole_isects.emplace_back(isects[1].first, isects[1].second, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Holes can intersect each other, sort the hits by t
|
||||
std::sort(hole_isects.begin(), hole_isects.end(),
|
||||
[](const HoleHit& a, const HoleHit& b) { return a.t < b.t; });
|
||||
|
||||
// Now inspect the intersections with object and holes, in the order of
|
||||
// increasing distance. Keep track how deep are we nested in mesh/holes and
|
||||
// pick the correct intersection.
|
||||
// This needs to be done twice - first to find out how deep in the structure
|
||||
// the source is, then to pick the correct intersection.
|
||||
int hole_nested = 0;
|
||||
int object_nested = 0;
|
||||
for (int dry_run=1; dry_run>=0; --dry_run) {
|
||||
hole_nested = -hole_nested;
|
||||
object_nested = -object_nested;
|
||||
|
||||
bool is_hole = false;
|
||||
bool is_entry = false;
|
||||
const HoleHit* next_hole_hit = hole_isects.empty() ? nullptr : &hole_isects.front();
|
||||
const hit_result* next_mesh_hit = &object_hits.front();
|
||||
|
||||
while (next_hole_hit || next_mesh_hit) {
|
||||
if (next_hole_hit && next_mesh_hit) // still have hole and obj hits
|
||||
is_hole = (next_hole_hit->t < next_mesh_hit->m_t);
|
||||
else
|
||||
is_hole = next_hole_hit; // one or the other ran out
|
||||
|
||||
// Is this entry or exit hit?
|
||||
is_entry = is_hole ? next_hole_hit->entry : ! next_mesh_hit->is_inside();
|
||||
|
||||
if (! dry_run) {
|
||||
if (! is_hole && hole_nested == 0) {
|
||||
// This is a valid object hit
|
||||
return *next_mesh_hit;
|
||||
}
|
||||
if (is_hole && ! is_entry && object_nested != 0) {
|
||||
// This holehit is the one we seek
|
||||
out.m_t = next_hole_hit->t;
|
||||
out.m_normal = next_hole_hit->normal;
|
||||
out.m_source = s;
|
||||
out.m_dir = dir;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
// Increase/decrease the counter
|
||||
(is_hole ? hole_nested : object_nested) += (is_entry ? 1 : -1);
|
||||
|
||||
// Advance the respective pointer
|
||||
if (is_hole && next_hole_hit++ == &hole_isects.back())
|
||||
next_hole_hit = nullptr;
|
||||
if (! is_hole && next_mesh_hit++ == &object_hits.back())
|
||||
next_mesh_hit = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here, the ray ended up in infinity
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
double AABBMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
||||
double sqdst = 0;
|
||||
Eigen::Matrix<double, 1, 3> pp = p;
|
||||
Eigen::Matrix<double, 1, 3> cc;
|
||||
sqdst = m_aabb->squared_distance(*m_tm, pp, i, cc);
|
||||
c = cc;
|
||||
return sqdst;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
142
src/libslic3r/AABBMesh.hpp
Normal file
142
src/libslic3r/AABBMesh.hpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#ifndef PRUSASLICER_AABBMESH_H
|
||||
#define PRUSASLICER_AABBMESH_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
// There is an implementation of a hole-aware raycaster that was eventually
|
||||
// not used in production version. It is now hidden under following define
|
||||
// for possible future use.
|
||||
// #define SLIC3R_HOLE_RAYCASTER
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
#endif
|
||||
|
||||
struct indexed_triangle_set;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
// An index-triangle structure coupled with an AABB index to support ray
|
||||
// casting and other higher level operations.
|
||||
class AABBMesh {
|
||||
class AABBImpl;
|
||||
|
||||
const indexed_triangle_set* m_tm;
|
||||
|
||||
std::unique_ptr<AABBImpl> m_aabb;
|
||||
VertexFaceIndex m_vfidx; // vertex-face index
|
||||
std::vector<Vec3i> m_fnidx; // face-neighbor index
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
// This holds a copy of holes in the mesh. Initialized externally
|
||||
// by load_mesh setter.
|
||||
std::vector<sla::DrainHole> m_holes;
|
||||
#endif
|
||||
|
||||
template<class M> void init(const M &mesh, bool calculate_epsilon);
|
||||
|
||||
public:
|
||||
|
||||
// calculate_epsilon ... calculate epsilon for triangle-ray intersection from an average triangle edge length.
|
||||
// If set to false, a default epsilon is used, which works for "reasonable" meshes.
|
||||
explicit AABBMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon = false);
|
||||
explicit AABBMesh(const TriangleMesh &mesh, bool calculate_epsilon = false);
|
||||
|
||||
AABBMesh(const AABBMesh& other);
|
||||
AABBMesh& operator=(const AABBMesh&);
|
||||
|
||||
AABBMesh(AABBMesh &&other);
|
||||
AABBMesh& operator=(AABBMesh &&other);
|
||||
|
||||
~AABBMesh();
|
||||
|
||||
const std::vector<Vec3f>& vertices() const;
|
||||
const std::vector<Vec3i>& indices() const;
|
||||
const Vec3f& vertices(size_t idx) const;
|
||||
const Vec3i& indices(size_t idx) const;
|
||||
|
||||
// Result of a raycast
|
||||
class hit_result {
|
||||
// m_t holds a distance from m_source to the intersection.
|
||||
double m_t = infty();
|
||||
int m_face_id = -1;
|
||||
const AABBMesh *m_mesh = nullptr;
|
||||
Vec3d m_dir = Vec3d::Zero();
|
||||
Vec3d m_source = Vec3d::Zero();
|
||||
Vec3d m_normal = Vec3d::Zero();
|
||||
friend class AABBMesh;
|
||||
|
||||
// A valid object of this class can only be obtained from
|
||||
// IndexedMesh::query_ray_hit method.
|
||||
explicit inline hit_result(const AABBMesh& em): m_mesh(&em) {}
|
||||
public:
|
||||
// This denotes no hit on the mesh.
|
||||
static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); }
|
||||
|
||||
explicit inline hit_result(double val = infty()) : m_t(val) {}
|
||||
|
||||
inline double distance() const { return m_t; }
|
||||
inline const Vec3d& direction() const { return m_dir; }
|
||||
inline const Vec3d& source() const { return m_source; }
|
||||
inline Vec3d position() const { return m_source + m_dir * m_t; }
|
||||
inline int face() const { return m_face_id; }
|
||||
inline bool is_valid() const { return m_mesh != nullptr; }
|
||||
inline bool is_hit() const { return m_face_id >= 0 && !std::isinf(m_t); }
|
||||
|
||||
inline const Vec3d& normal() const {
|
||||
assert(is_valid());
|
||||
return m_normal;
|
||||
}
|
||||
|
||||
inline bool is_inside() const {
|
||||
return is_hit() && normal().dot(m_dir) > 0;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
// Inform the object about location of holes
|
||||
// creates internal copy of the vector
|
||||
void load_holes(const std::vector<sla::DrainHole>& holes) {
|
||||
m_holes = holes;
|
||||
}
|
||||
|
||||
// Iterates over hits and holes and returns the true hit, possibly
|
||||
// on the inside of a hole.
|
||||
// This function is currently not used anywhere, it was written when the
|
||||
// holes were subtracted on slices, that is, before we started using CGAL
|
||||
// to actually cut the holes into the mesh.
|
||||
hit_result filter_hits(const std::vector<AABBMesh::hit_result>& obj_hits) const;
|
||||
#endif
|
||||
|
||||
// Casting a ray on the mesh, returns the distance where the hit occures.
|
||||
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
|
||||
|
||||
// Casts a ray on the mesh and returns all hits
|
||||
std::vector<hit_result> query_ray_hits(const Vec3d &s, const Vec3d &dir) const;
|
||||
|
||||
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
|
||||
inline double squared_distance(const Vec3d &p) const
|
||||
{
|
||||
int i;
|
||||
Vec3d c;
|
||||
return squared_distance(p, i, c);
|
||||
}
|
||||
|
||||
Vec3d normal_by_face_id(int face_id) const;
|
||||
|
||||
const indexed_triangle_set * get_triangle_mesh() const { return m_tm; }
|
||||
|
||||
const VertexFaceIndex &vertex_face_index() const { return m_vfidx; }
|
||||
const std::vector<Vec3i> &face_neighbor_index() const { return m_fnidx; }
|
||||
};
|
||||
|
||||
|
||||
} // namespace Slic3r::sla
|
||||
|
||||
#endif // INDEXEDMESH_H
|
||||
992
src/libslic3r/AABBTreeIndirect.hpp
Normal file
992
src/libslic3r/AABBTreeIndirect.hpp
Normal file
@@ -0,0 +1,992 @@
|
||||
// AABB tree built upon external data set, referencing the external data by integer indices.
|
||||
// The AABB tree balancing and traversal (ray casting, closest triangle of an indexed triangle mesh)
|
||||
// were adapted from libigl AABB.{cpp,hpp} Copyright (C) 2015 Alec Jacobson <alecjacobson@gmail.com>
|
||||
// while the implicit balanced tree representation and memory optimizations are Vojtech's.
|
||||
|
||||
#ifndef slic3r_AABBTreeIndirect_hpp_
|
||||
#define slic3r_AABBTreeIndirect_hpp_
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Utils.hpp" // for next_highest_power_of_2()
|
||||
|
||||
// Definition of the ray intersection hit structure.
|
||||
#include <igl/Hit.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace AABBTreeIndirect {
|
||||
|
||||
// Static balanced AABB tree for raycasting and closest triangle search.
|
||||
// The balanced tree is built over a single large std::vector of nodes, where the children of nodes
|
||||
// are addressed implicitely using a power of two indexing rule.
|
||||
// Memory for a full balanced tree is allocated, but not all nodes at the last level are used.
|
||||
// This may seem like a waste of memory, but one saves memory for the node links and there is zero
|
||||
// overhead of a memory allocator management (usually the memory allocator adds at least one pointer
|
||||
// before the memory returned). However, allocating memory in a single vector is very fast even
|
||||
// in multi-threaded environment and it is cache friendly.
|
||||
//
|
||||
// A balanced tree is built upon a vector of bounding boxes and their centroids, storing the reference
|
||||
// to the source entity (a 3D triangle, a 2D segment etc, a 3D or 2D point etc).
|
||||
// The source bounding boxes may have an epsilon applied to fight numeric rounding errors when
|
||||
// traversing the AABB tree.
|
||||
template<int ANumDimensions, typename ACoordType>
|
||||
class Tree
|
||||
{
|
||||
public:
|
||||
static constexpr int NumDimensions = ANumDimensions;
|
||||
using CoordType = ACoordType;
|
||||
using VectorType = Eigen::Matrix<CoordType, NumDimensions, 1, Eigen::DontAlign>;
|
||||
using BoundingBox = Eigen::AlignedBox<CoordType, NumDimensions>;
|
||||
// Following could be static constexpr size_t, but that would not link in C++11
|
||||
enum : size_t {
|
||||
// Node is not used.
|
||||
npos = size_t(-1),
|
||||
// Inner node (not leaf).
|
||||
inner = size_t(-2)
|
||||
};
|
||||
|
||||
// Single node of the implicit balanced AABB tree. There are no links to the children nodes,
|
||||
// as these links are calculated implicitely using a power of two rule.
|
||||
struct Node {
|
||||
// Index of the external source entity, for which this AABB tree was built, npos for internal nodes.
|
||||
size_t idx = npos;
|
||||
// Bounding box around this entity, possibly with epsilons applied to fight numeric rounding errors
|
||||
// when traversing the AABB tree.
|
||||
BoundingBox bbox;
|
||||
|
||||
bool is_valid() const { return this->idx != npos; }
|
||||
bool is_inner() const { return this->idx == inner; }
|
||||
bool is_leaf() const { return ! this->is_inner(); }
|
||||
|
||||
template<typename SourceNode>
|
||||
void set(const SourceNode &rhs) {
|
||||
this->idx = rhs.idx();
|
||||
this->bbox = rhs.bbox();
|
||||
}
|
||||
};
|
||||
|
||||
void clear() { m_nodes.clear(); }
|
||||
|
||||
// SourceNode shall implement
|
||||
// size_t SourceNode::idx() const
|
||||
// - Index to the outside entity (triangle, edge, point etc).
|
||||
// const VectorType& SourceNode::centroid() const
|
||||
// - Centroid of this node. The centroid is used for balancing the tree.
|
||||
// const BoundingBox& SourceNode::bbox() const
|
||||
// - Bounding box of this node, likely expanded with epsilon to account for numeric rounding during tree traversal.
|
||||
// Union of bounding boxes at a single level of the AABB tree is used for deciding the longest axis aligned dimension
|
||||
// to split around.
|
||||
template<typename SourceNode>
|
||||
void build(std::vector<SourceNode> &&input)
|
||||
{
|
||||
this->build_modify_input(input);
|
||||
input.clear();
|
||||
}
|
||||
|
||||
template<typename SourceNode>
|
||||
void build_modify_input(std::vector<SourceNode> &input)
|
||||
{
|
||||
if (input.empty())
|
||||
clear();
|
||||
else {
|
||||
// Allocate enough memory for a full binary tree.
|
||||
m_nodes.assign(next_highest_power_of_2(input.size()) * 2 - 1, Node());
|
||||
build_recursive(input, 0, 0, input.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<Node>& nodes() const { return m_nodes; }
|
||||
const Node& node(size_t idx) const { return m_nodes[idx]; }
|
||||
bool empty() const { return m_nodes.empty(); }
|
||||
|
||||
// Addressing the child nodes using the power of two rule.
|
||||
static size_t left_child_idx(size_t idx) { return idx * 2 + 1; }
|
||||
static size_t right_child_idx(size_t idx) { return left_child_idx(idx) + 1; }
|
||||
const Node& left_child(size_t idx) const { return m_nodes[left_child_idx(idx)]; }
|
||||
const Node& right_child(size_t idx) const { return m_nodes[right_child_idx(idx)]; }
|
||||
|
||||
template<typename SourceNode>
|
||||
void build(const std::vector<SourceNode> &input)
|
||||
{
|
||||
std::vector<SourceNode> copy(input);
|
||||
this->build(std::move(copy));
|
||||
}
|
||||
|
||||
private:
|
||||
// Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension.
|
||||
template<typename SourceNode>
|
||||
void build_recursive(std::vector<SourceNode> &input, size_t node, const size_t left, const size_t right)
|
||||
{
|
||||
assert(node < m_nodes.size());
|
||||
assert(left <= right);
|
||||
|
||||
if (left == right) {
|
||||
// Insert a node into the balanced tree.
|
||||
m_nodes[node].set(input[left]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate bounding box of the input.
|
||||
BoundingBox bbox(input[left].bbox());
|
||||
for (size_t i = left + 1; i <= right; ++ i)
|
||||
bbox.extend(input[i].bbox());
|
||||
int dimension = -1;
|
||||
bbox.diagonal().maxCoeff(&dimension);
|
||||
|
||||
// Partition the input to left / right pieces of the same length to produce a balanced tree.
|
||||
size_t center = (left + right) / 2;
|
||||
partition_input(input, size_t(dimension), left, right, center);
|
||||
// Insert an inner node into the tree. Inner node does not reference any input entity (triangle, line segment etc).
|
||||
m_nodes[node].idx = inner;
|
||||
m_nodes[node].bbox = bbox;
|
||||
build_recursive(input, node * 2 + 1, left, center);
|
||||
build_recursive(input, node * 2 + 2, center + 1, right);
|
||||
}
|
||||
|
||||
// Partition the input m_nodes <left, right> at "k" and "dimension" using the QuickSelect method:
|
||||
// https://en.wikipedia.org/wiki/Quickselect
|
||||
// Items left of the k'th item are lower than the k'th item in the "dimension",
|
||||
// items right of the k'th item are higher than the k'th item in the "dimension",
|
||||
template<typename SourceNode>
|
||||
void partition_input(std::vector<SourceNode> &input, const size_t dimension, size_t left, size_t right, const size_t k) const
|
||||
{
|
||||
while (left < right) {
|
||||
size_t center = (left + right) / 2;
|
||||
CoordType pivot;
|
||||
{
|
||||
// Buqdte sort the input[left], input[center], input[right], so that a median of the three values
|
||||
// will end up in input[center].
|
||||
CoordType left_value = input[left ].centroid()(dimension);
|
||||
CoordType center_value = input[center].centroid()(dimension);
|
||||
CoordType right_value = input[right ].centroid()(dimension);
|
||||
if (left_value > center_value) {
|
||||
std::swap(input[left], input[center]);
|
||||
std::swap(left_value, center_value);
|
||||
}
|
||||
if (left_value > right_value) {
|
||||
std::swap(input[left], input[right]);
|
||||
right_value = left_value;
|
||||
}
|
||||
if (center_value > right_value) {
|
||||
std::swap(input[center], input[right]);
|
||||
center_value = right_value;
|
||||
}
|
||||
pivot = center_value;
|
||||
}
|
||||
if (right <= left + 2)
|
||||
// The <left, right> interval is already sorted.
|
||||
break;
|
||||
size_t i = left;
|
||||
size_t j = right - 1;
|
||||
std::swap(input[center], input[j]);
|
||||
// Partition the set based on the pivot.
|
||||
for (;;) {
|
||||
// Skip left points that are already at correct positions.
|
||||
// Search will certainly stop at position (right - 1), which stores the pivot.
|
||||
while (input[++ i].centroid()(dimension) < pivot) ;
|
||||
// Skip right points that are already at correct positions.
|
||||
while (input[-- j].centroid()(dimension) > pivot && i < j) ;
|
||||
if (i >= j)
|
||||
break;
|
||||
std::swap(input[i], input[j]);
|
||||
}
|
||||
// Restore pivot to the center of the sequence.
|
||||
std::swap(input[i], input[right - 1]);
|
||||
// Which side the kth element is in?
|
||||
if (k < i)
|
||||
right = i - 1;
|
||||
else if (k == i)
|
||||
// Sequence is partitioned, kth element is at its place.
|
||||
break;
|
||||
else
|
||||
left = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// The balanced tree storage.
|
||||
std::vector<Node> m_nodes;
|
||||
};
|
||||
|
||||
using Tree2f = Tree<2, float>;
|
||||
using Tree3f = Tree<3, float>;
|
||||
using Tree2d = Tree<2, double>;
|
||||
using Tree3d = Tree<3, double>;
|
||||
|
||||
// Wrap a 2D Slic3r own BoundingBox to be passed to Tree::build() and similar
|
||||
// to build an AABBTree over coord_t 2D bounding boxes.
|
||||
class BoundingBoxWrapper {
|
||||
public:
|
||||
using BoundingBox = Eigen::AlignedBox<coord_t, 2>;
|
||||
BoundingBoxWrapper(const size_t idx, const Slic3r::BoundingBox &bbox) :
|
||||
m_idx(idx),
|
||||
// Inflate the bounding box a bit to account for numerical issues.
|
||||
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
|
||||
size_t idx() const { return m_idx; }
|
||||
const BoundingBox& bbox() const { return m_bbox; }
|
||||
Point centroid() const { return ((m_bbox.min().cast<int64_t>() + m_bbox.max().cast<int64_t>()) / 2).cast<int32_t>(); }
|
||||
private:
|
||||
size_t m_idx;
|
||||
BoundingBox m_bbox;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
|
||||
struct RayIntersector {
|
||||
using VertexType = AVertexType;
|
||||
using IndexedFaceType = AIndexedFaceType;
|
||||
using TreeType = ATreeType;
|
||||
using VectorType = AVectorType;
|
||||
|
||||
const std::vector<VertexType> &vertices;
|
||||
const std::vector<IndexedFaceType> &faces;
|
||||
const TreeType &tree;
|
||||
|
||||
const VectorType origin;
|
||||
const VectorType dir;
|
||||
const VectorType invdir;
|
||||
|
||||
// epsilon for ray-triangle intersection, see intersect_triangle1()
|
||||
const double eps;
|
||||
};
|
||||
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
struct RayIntersectorHits : RayIntersector<VertexType, IndexedFaceType, TreeType, VectorType> {
|
||||
std::vector<igl::Hit> hits;
|
||||
};
|
||||
|
||||
//FIXME implement SSE for float AABB trees with float ray queries.
|
||||
// SSE/SSE2 is supported by any Intel/AMD x64 processor.
|
||||
// SSE support requires 16 byte alignment of the AABB nodes, representing the bounding boxes with 4+4 floats,
|
||||
// storing the node index as the 4th element of the bounding box min value etc.
|
||||
// https://www.flipcode.com/archives/SSE_RayBox_Intersection_Test.shtml
|
||||
template <typename Derivedsource, typename Deriveddir, typename Scalar>
|
||||
inline bool ray_box_intersect_invdir(
|
||||
const Eigen::MatrixBase<Derivedsource> &origin,
|
||||
const Eigen::MatrixBase<Deriveddir> &inv_dir,
|
||||
Eigen::AlignedBox<Scalar,3> box,
|
||||
const Scalar &t0,
|
||||
const Scalar &t1) {
|
||||
// http://people.csail.mit.edu/amy/papers/box-jgt.pdf
|
||||
// "An Efficient and Robust Ray–Box Intersection Algorithm"
|
||||
if (inv_dir.x() < 0)
|
||||
std::swap(box.min().x(), box.max().x());
|
||||
if (inv_dir.y() < 0)
|
||||
std::swap(box.min().y(), box.max().y());
|
||||
Scalar tmin = (box.min().x() - origin.x()) * inv_dir.x();
|
||||
Scalar tymax = (box.max().y() - origin.y()) * inv_dir.y();
|
||||
if (tmin > tymax)
|
||||
return false;
|
||||
Scalar tmax = (box.max().x() - origin.x()) * inv_dir.x();
|
||||
Scalar tymin = (box.min().y() - origin.y()) * inv_dir.y();
|
||||
if (tymin > tmax)
|
||||
return false;
|
||||
if (tymin > tmin)
|
||||
tmin = tymin;
|
||||
if (tymax < tmax)
|
||||
tmax = tymax;
|
||||
if (inv_dir.z() < 0)
|
||||
std::swap(box.min().z(), box.max().z());
|
||||
Scalar tzmin = (box.min().z() - origin.z()) * inv_dir.z();
|
||||
if (tzmin > tmax)
|
||||
return false;
|
||||
Scalar tzmax = (box.max().z() - origin.z()) * inv_dir.z();
|
||||
if (tmin > tzmax)
|
||||
return false;
|
||||
if (tzmin > tmin)
|
||||
tmin = tzmin;
|
||||
if (tzmax < tmax)
|
||||
tmax = tzmax;
|
||||
return tmin < t1 && tmax > t0;
|
||||
}
|
||||
|
||||
// The following intersect_triangle() is derived from raytri.c routine intersect_triangle1()
|
||||
// Ray-Triangle Intersection Test Routines
|
||||
// Different optimizations of my and Ben Trumbore's
|
||||
// code from journals of graphics tools (JGT)
|
||||
// http://www.acm.org/jgt/
|
||||
// by Tomas Moller, May 2000
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<std::is_same<typename V::Scalar, double>::value&& std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &orig, const V &dir, const W &vert0, const W &vert1, const W &vert2, double &t, double &u, double &v, double eps)
|
||||
{
|
||||
// find vectors for two edges sharing vert0
|
||||
const V edge1 = vert1 - vert0;
|
||||
const V edge2 = vert2 - vert0;
|
||||
// begin calculating determinant - also used to calculate U parameter
|
||||
const V pvec = dir.cross(edge2);
|
||||
// if determinant is near zero, ray lies in plane of triangle
|
||||
const double det = edge1.dot(pvec);
|
||||
V qvec;
|
||||
|
||||
if (det > eps) {
|
||||
// calculate distance from vert0 to ray origin
|
||||
V tvec = orig - vert0;
|
||||
// calculate U parameter and test bounds
|
||||
u = tvec.dot(pvec);
|
||||
if (u < 0.0 || u > det)
|
||||
return false;
|
||||
// prepare to test V parameter
|
||||
qvec = tvec.cross(edge1);
|
||||
// calculate V parameter and test bounds
|
||||
v = dir.dot(qvec);
|
||||
if (v < 0.0 || u + v > det)
|
||||
return false;
|
||||
} else if (det < -eps) {
|
||||
// calculate distance from vert0 to ray origin
|
||||
V tvec = orig - vert0;
|
||||
// calculate U parameter and test bounds
|
||||
u = tvec.dot(pvec);
|
||||
if (u > 0.0 || u < det)
|
||||
return false;
|
||||
// prepare to test V parameter
|
||||
qvec = tvec.cross(edge1);
|
||||
// calculate V parameter and test bounds
|
||||
v = dir.dot(qvec);
|
||||
if (v > 0.0 || u + v < det)
|
||||
return false;
|
||||
} else
|
||||
// ray is parallel to the plane of the triangle
|
||||
return false;
|
||||
|
||||
double inv_det = 1.0 / det;
|
||||
// calculate t, ray intersects triangle
|
||||
t = edge2.dot(qvec) * inv_det;
|
||||
u *= inv_det;
|
||||
v *= inv_det;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<std::is_same<typename V::Scalar, double>::value && !std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) {
|
||||
return intersect_triangle(origin, dir, v0.template cast<double>(), v1.template cast<double>(), v2.template cast<double>(), t, u, v, eps);
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) {
|
||||
return intersect_triangle(origin.template cast<double>(), dir.template cast<double>(), v0, v1, v2, t, u, v, eps);
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && ! std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) {
|
||||
return intersect_triangle(origin.template cast<double>(), dir.template cast<double>(), v0.template cast<double>(), v1.template cast<double>(), v2.template cast<double>(), t, u, v, eps);
|
||||
}
|
||||
|
||||
template<typename Tree>
|
||||
double intersect_triangle_epsilon(const Tree &tree) {
|
||||
double eps = 0.000001;
|
||||
if (! tree.empty()) {
|
||||
const typename Tree::BoundingBox &bbox = tree.nodes().front().bbox;
|
||||
double l = (bbox.max() - bbox.min()).cwiseMax();
|
||||
if (l > 0)
|
||||
eps /= (l * l);
|
||||
}
|
||||
return eps;
|
||||
}
|
||||
|
||||
template<typename RayIntersectorType, typename Scalar>
|
||||
static inline bool intersect_ray_recursive_first_hit(
|
||||
RayIntersectorType &ray_intersector,
|
||||
size_t node_idx,
|
||||
Scalar min_t,
|
||||
igl::Hit &hit)
|
||||
{
|
||||
const auto &node = ray_intersector.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
|
||||
if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast<Scalar>(), Scalar(0), min_t))
|
||||
return false;
|
||||
|
||||
if (node.is_leaf()) {
|
||||
// shoot ray, record hit
|
||||
auto face = ray_intersector.faces[node.idx];
|
||||
double t, u, v;
|
||||
if (intersect_triangle(
|
||||
ray_intersector.origin, ray_intersector.dir,
|
||||
ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)],
|
||||
t, u, v, ray_intersector.eps)
|
||||
&& t > 0.) {
|
||||
hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) };
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
} else {
|
||||
// Left / right child node index.
|
||||
size_t left = node_idx * 2 + 1;
|
||||
size_t right = left + 1;
|
||||
igl::Hit left_hit;
|
||||
igl::Hit right_hit;
|
||||
bool left_ret = intersect_ray_recursive_first_hit(ray_intersector, left, min_t, left_hit);
|
||||
if (left_ret && left_hit.t < min_t) {
|
||||
min_t = left_hit.t;
|
||||
hit = left_hit;
|
||||
} else
|
||||
left_ret = false;
|
||||
bool right_ret = intersect_ray_recursive_first_hit(ray_intersector, right, min_t, right_hit);
|
||||
if (right_ret && right_hit.t < min_t)
|
||||
hit = right_hit;
|
||||
else
|
||||
right_ret = false;
|
||||
return left_ret || right_ret;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename RayIntersectorType>
|
||||
static inline void intersect_ray_recursive_all_hits(RayIntersectorType &ray_intersector, size_t node_idx)
|
||||
{
|
||||
using Scalar = typename RayIntersectorType::VectorType::Scalar;
|
||||
|
||||
const auto &node = ray_intersector.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
|
||||
if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast<Scalar>(),
|
||||
Scalar(0), std::numeric_limits<Scalar>::infinity()))
|
||||
return;
|
||||
|
||||
if (node.is_leaf()) {
|
||||
auto face = ray_intersector.faces[node.idx];
|
||||
double t, u, v;
|
||||
if (intersect_triangle(
|
||||
ray_intersector.origin, ray_intersector.dir,
|
||||
ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)],
|
||||
t, u, v, ray_intersector.eps)
|
||||
&& t > 0.) {
|
||||
ray_intersector.hits.emplace_back(igl::Hit{ int(node.idx), -1, float(u), float(v), float(t) });
|
||||
}
|
||||
} else {
|
||||
// Left / right child node index.
|
||||
size_t left = node_idx * 2 + 1;
|
||||
size_t right = left + 1;
|
||||
intersect_ray_recursive_all_hits(ray_intersector, left);
|
||||
intersect_ray_recursive_all_hits(ray_intersector, right);
|
||||
}
|
||||
}
|
||||
|
||||
// Real-time collision detection, Ericson, Chapter 5
|
||||
template<typename Vector>
|
||||
static inline Vector closest_point_to_triangle(const Vector &p, const Vector &a, const Vector &b, const Vector &c)
|
||||
{
|
||||
using Scalar = typename Vector::Scalar;
|
||||
// Check if P in vertex region outside A
|
||||
Vector ab = b - a;
|
||||
Vector ac = c - a;
|
||||
Vector ap = p - a;
|
||||
Scalar d1 = ab.dot(ap);
|
||||
Scalar d2 = ac.dot(ap);
|
||||
if (d1 <= 0 && d2 <= 0)
|
||||
return a;
|
||||
// Check if P in vertex region outside B
|
||||
Vector bp = p - b;
|
||||
Scalar d3 = ab.dot(bp);
|
||||
Scalar d4 = ac.dot(bp);
|
||||
if (d3 >= 0 && d4 <= d3)
|
||||
return b;
|
||||
// Check if P in edge region of AB, if so return projection of P onto AB
|
||||
Scalar vc = d1*d4 - d3*d2;
|
||||
if (a != b && vc <= 0 && d1 >= 0 && d3 <= 0) {
|
||||
Scalar v = d1 / (d1 - d3);
|
||||
return a + v * ab;
|
||||
}
|
||||
// Check if P in vertex region outside C
|
||||
Vector cp = p - c;
|
||||
Scalar d5 = ab.dot(cp);
|
||||
Scalar d6 = ac.dot(cp);
|
||||
if (d6 >= 0 && d5 <= d6)
|
||||
return c;
|
||||
// Check if P in edge region of AC, if so return projection of P onto AC
|
||||
Scalar vb = d5*d2 - d1*d6;
|
||||
if (vb <= 0 && d2 >= 0 && d6 <= 0) {
|
||||
Scalar w = d2 / (d2 - d6);
|
||||
return a + w * ac;
|
||||
}
|
||||
// Check if P in edge region of BC, if so return projection of P onto BC
|
||||
Scalar va = d3*d6 - d5*d4;
|
||||
if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) {
|
||||
Scalar w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
|
||||
return b + w * (c - b);
|
||||
}
|
||||
// P inside face region. Compute Q through its barycentric coordinates (u,v,w)
|
||||
Scalar denom = Scalar(1.0) / (va + vb + vc);
|
||||
Scalar v = vb * denom;
|
||||
Scalar w = vc * denom;
|
||||
return a + ab * v + ac * w; // = u*a + v*b + w*c, u = va * denom = 1.0-v-w
|
||||
};
|
||||
|
||||
|
||||
// Nothing to do with COVID-19 social distancing.
|
||||
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
|
||||
struct IndexedTriangleSetDistancer {
|
||||
using VertexType = AVertexType;
|
||||
using IndexedFaceType = AIndexedFaceType;
|
||||
using TreeType = ATreeType;
|
||||
using VectorType = AVectorType;
|
||||
using ScalarType = typename VectorType::Scalar;
|
||||
|
||||
const std::vector<VertexType> &vertices;
|
||||
const std::vector<IndexedFaceType> &faces;
|
||||
const TreeType &tree;
|
||||
|
||||
const VectorType origin;
|
||||
|
||||
inline VectorType closest_point_to_origin(size_t primitive_index,
|
||||
ScalarType& squared_distance) const {
|
||||
const auto &triangle = this->faces[primitive_index];
|
||||
VectorType closest_point = closest_point_to_triangle<VectorType>(origin,
|
||||
this->vertices[triangle(0)].template cast<ScalarType>(),
|
||||
this->vertices[triangle(1)].template cast<ScalarType>(),
|
||||
this->vertices[triangle(2)].template cast<ScalarType>());
|
||||
squared_distance = (origin - closest_point).squaredNorm();
|
||||
return closest_point;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename IndexedPrimitivesDistancerType, typename Scalar>
|
||||
static inline Scalar squared_distance_to_indexed_primitives_recursive(
|
||||
IndexedPrimitivesDistancerType &distancer,
|
||||
size_t node_idx,
|
||||
Scalar low_sqr_d,
|
||||
Scalar up_sqr_d,
|
||||
size_t &i,
|
||||
Eigen::PlainObjectBase<typename IndexedPrimitivesDistancerType::VectorType> &c)
|
||||
{
|
||||
using Vector = typename IndexedPrimitivesDistancerType::VectorType;
|
||||
|
||||
if (low_sqr_d > up_sqr_d)
|
||||
return low_sqr_d;
|
||||
|
||||
// Save the best achieved hit.
|
||||
auto set_min = [&i, &c, &up_sqr_d](const Scalar sqr_d_candidate, const size_t i_candidate, const Vector &c_candidate) {
|
||||
if (sqr_d_candidate < up_sqr_d) {
|
||||
i = i_candidate;
|
||||
c = c_candidate;
|
||||
up_sqr_d = sqr_d_candidate;
|
||||
}
|
||||
};
|
||||
|
||||
const auto &node = distancer.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
if (node.is_leaf())
|
||||
{
|
||||
Scalar sqr_dist;
|
||||
Vector c_candidate = distancer.closest_point_to_origin(node.idx, sqr_dist);
|
||||
set_min(sqr_dist, node.idx, c_candidate);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t left_node_idx = node_idx * 2 + 1;
|
||||
size_t right_node_idx = left_node_idx + 1;
|
||||
const auto &node_left = distancer.tree.node(left_node_idx);
|
||||
const auto &node_right = distancer.tree.node(right_node_idx);
|
||||
assert(node_left.is_valid());
|
||||
assert(node_right.is_valid());
|
||||
|
||||
bool looked_left = false;
|
||||
bool looked_right = false;
|
||||
const auto &look_left = [&]()
|
||||
{
|
||||
size_t i_left;
|
||||
Vector c_left = c;
|
||||
Scalar sqr_d_left = squared_distance_to_indexed_primitives_recursive(distancer, left_node_idx, low_sqr_d, up_sqr_d, i_left, c_left);
|
||||
set_min(sqr_d_left, i_left, c_left);
|
||||
looked_left = true;
|
||||
};
|
||||
const auto &look_right = [&]()
|
||||
{
|
||||
size_t i_right;
|
||||
Vector c_right = c;
|
||||
Scalar sqr_d_right = squared_distance_to_indexed_primitives_recursive(distancer, right_node_idx, low_sqr_d, up_sqr_d, i_right, c_right);
|
||||
set_min(sqr_d_right, i_right, c_right);
|
||||
looked_right = true;
|
||||
};
|
||||
|
||||
// must look left or right if in box
|
||||
using BBoxScalar = typename IndexedPrimitivesDistancerType::TreeType::BoundingBox::Scalar;
|
||||
if (node_left.bbox.contains(distancer.origin.template cast<BBoxScalar>()))
|
||||
look_left();
|
||||
if (node_right.bbox.contains(distancer.origin.template cast<BBoxScalar>()))
|
||||
look_right();
|
||||
// if haven't looked left and could be less than current min, then look
|
||||
Scalar left_up_sqr_d = node_left.bbox.squaredExteriorDistance(distancer.origin);
|
||||
Scalar right_up_sqr_d = node_right.bbox.squaredExteriorDistance(distancer.origin);
|
||||
if (left_up_sqr_d < right_up_sqr_d) {
|
||||
if (! looked_left && left_up_sqr_d < up_sqr_d)
|
||||
look_left();
|
||||
if (! looked_right && right_up_sqr_d < up_sqr_d)
|
||||
look_right();
|
||||
} else {
|
||||
if (! looked_right && right_up_sqr_d < up_sqr_d)
|
||||
look_right();
|
||||
if (! looked_left && left_up_sqr_d < up_sqr_d)
|
||||
look_left();
|
||||
}
|
||||
}
|
||||
return up_sqr_d;
|
||||
}
|
||||
|
||||
template<typename IndexedPrimitivesDistancerType, typename Scalar>
|
||||
static inline void indexed_primitives_within_distance_squared_recurisve(const IndexedPrimitivesDistancerType &distancer,
|
||||
size_t node_idx,
|
||||
Scalar squared_distance_limit,
|
||||
std::vector<size_t> &found_primitives_indices)
|
||||
{
|
||||
const auto &node = distancer.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
if (node.is_leaf()) {
|
||||
Scalar sqr_dist;
|
||||
distancer.closest_point_to_origin(node.idx, sqr_dist);
|
||||
if (sqr_dist < squared_distance_limit) { found_primitives_indices.push_back(node.idx); }
|
||||
} else {
|
||||
size_t left_node_idx = node_idx * 2 + 1;
|
||||
size_t right_node_idx = left_node_idx + 1;
|
||||
const auto &node_left = distancer.tree.node(left_node_idx);
|
||||
const auto &node_right = distancer.tree.node(right_node_idx);
|
||||
assert(node_left.is_valid());
|
||||
assert(node_right.is_valid());
|
||||
|
||||
if (node_left.bbox.squaredExteriorDistance(distancer.origin) < squared_distance_limit) {
|
||||
indexed_primitives_within_distance_squared_recurisve(distancer, left_node_idx, squared_distance_limit,
|
||||
found_primitives_indices);
|
||||
}
|
||||
if (node_right.bbox.squaredExteriorDistance(distancer.origin) < squared_distance_limit) {
|
||||
indexed_primitives_within_distance_squared_recurisve(distancer, right_node_idx, squared_distance_limit,
|
||||
found_primitives_indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Build a balanced AABB Tree over an indexed triangles set, balancing the tree
|
||||
// on centroids of the triangles.
|
||||
// Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies
|
||||
// during tree traversal.
|
||||
template<typename VertexType, typename IndexedFaceType>
|
||||
inline Tree<3, typename VertexType::Scalar> build_aabb_tree_over_indexed_triangle_set(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
//FIXME do we want to apply an epsilon?
|
||||
const typename VertexType::Scalar eps = 0)
|
||||
{
|
||||
using TreeType = Tree<3, typename VertexType::Scalar>;
|
||||
// using CoordType = typename TreeType::CoordType;
|
||||
using VectorType = typename TreeType::VectorType;
|
||||
using BoundingBox = typename TreeType::BoundingBox;
|
||||
|
||||
struct InputType {
|
||||
size_t idx() const { return m_idx; }
|
||||
const BoundingBox& bbox() const { return m_bbox; }
|
||||
const VectorType& centroid() const { return m_centroid; }
|
||||
|
||||
size_t m_idx;
|
||||
BoundingBox m_bbox;
|
||||
VectorType m_centroid;
|
||||
};
|
||||
|
||||
std::vector<InputType> input;
|
||||
input.reserve(faces.size());
|
||||
const VectorType veps(eps, eps, eps);
|
||||
for (size_t i = 0; i < faces.size(); ++ i) {
|
||||
const IndexedFaceType &face = faces[i];
|
||||
const VertexType &v1 = vertices[face(0)];
|
||||
const VertexType &v2 = vertices[face(1)];
|
||||
const VertexType &v3 = vertices[face(2)];
|
||||
InputType n;
|
||||
n.m_idx = i;
|
||||
n.m_centroid = (1./3.) * (v1 + v2 + v3);
|
||||
n.m_bbox = BoundingBox(v1, v1);
|
||||
n.m_bbox.extend(v2);
|
||||
n.m_bbox.extend(v3);
|
||||
n.m_bbox.min() -= veps;
|
||||
n.m_bbox.max() += veps;
|
||||
input.emplace_back(n);
|
||||
}
|
||||
|
||||
TreeType out;
|
||||
out.build(std::move(input));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Find a first intersection of a ray with indexed triangle set.
|
||||
// Intersection test is calculated with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline bool intersect_ray_first_hit(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Origin of the ray.
|
||||
const VectorType &origin,
|
||||
// Direction of the ray.
|
||||
const VectorType &dir,
|
||||
// First intersection of the ray with the indexed triangle set.
|
||||
igl::Hit &hit,
|
||||
// Epsilon for the ray-triangle intersection, it should be proportional to an average triangle edge length.
|
||||
const double eps = 0.000001)
|
||||
{
|
||||
using Scalar = typename VectorType::Scalar;
|
||||
auto ray_intersector = detail::RayIntersector<VertexType, IndexedFaceType, TreeType, VectorType> {
|
||||
vertices, faces, tree,
|
||||
origin, dir, VectorType(dir.cwiseInverse()),
|
||||
eps
|
||||
};
|
||||
return ! tree.empty() && detail::intersect_ray_recursive_first_hit(
|
||||
ray_intersector, size_t(0), std::numeric_limits<Scalar>::infinity(), hit);
|
||||
}
|
||||
|
||||
// Find all intersections of a ray with indexed triangle set.
|
||||
// Intersection test is calculated with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
// The output hits are sorted by the ray parameter.
|
||||
// If the ray intersects a shared edge of two triangles, hits for both triangles are returned.
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline bool intersect_ray_all_hits(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Origin of the ray.
|
||||
const VectorType &origin,
|
||||
// Direction of the ray.
|
||||
const VectorType &dir,
|
||||
// All intersections of the ray with the indexed triangle set, sorted by parameter t.
|
||||
std::vector<igl::Hit> &hits,
|
||||
// Epsilon for the ray-triangle intersection, it should be proportional to an average triangle edge length.
|
||||
const double eps = 0.000001)
|
||||
{
|
||||
auto ray_intersector = detail::RayIntersectorHits<VertexType, IndexedFaceType, TreeType, VectorType> {
|
||||
{ vertices, faces, {tree},
|
||||
origin, dir, VectorType(dir.cwiseInverse()),
|
||||
eps }
|
||||
};
|
||||
if (tree.empty()) {
|
||||
hits.clear();
|
||||
} else {
|
||||
// Reusing the output memory if there is some memory already pre-allocated.
|
||||
ray_intersector.hits = std::move(hits);
|
||||
ray_intersector.hits.clear();
|
||||
ray_intersector.hits.reserve(8);
|
||||
detail::intersect_ray_recursive_all_hits(ray_intersector, 0);
|
||||
hits = std::move(ray_intersector.hits);
|
||||
std::sort(hits.begin(), hits.end(), [](const auto &l, const auto &r) { return l.t < r.t; });
|
||||
}
|
||||
return ! hits.empty();
|
||||
}
|
||||
|
||||
// Finding a closest triangle, its closest point and squared distance to the closest point
|
||||
// on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree.
|
||||
// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
// Returns squared distance to the closest point or -1 if the input is empty.
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Point to which the closest point on the indexed triangle set is searched for.
|
||||
const VectorType &point,
|
||||
// Index of the closest triangle in faces.
|
||||
size_t &hit_idx_out,
|
||||
// Position of the closest point on the indexed triangle set.
|
||||
Eigen::PlainObjectBase<VectorType> &hit_point_out)
|
||||
{
|
||||
using Scalar = typename VectorType::Scalar;
|
||||
auto distancer = detail::IndexedTriangleSetDistancer<VertexType, IndexedFaceType, TreeType, VectorType>
|
||||
{ vertices, faces, tree, point };
|
||||
return tree.empty() ? Scalar(-1) :
|
||||
detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits<Scalar>::infinity(), hit_idx_out, hit_point_out);
|
||||
}
|
||||
|
||||
// Decides if exists some triangle in defined radius on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree.
|
||||
// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
// Returns true if exists some triangle in defined radius, false otherwise.
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline bool is_any_triangle_in_radius(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Point to which the closest point on the indexed triangle set is searched for.
|
||||
const VectorType &point,
|
||||
//Square of maximum distance in which triangle is searched for
|
||||
typename VectorType::Scalar &max_distance_squared)
|
||||
{
|
||||
using Scalar = typename VectorType::Scalar;
|
||||
auto distancer = detail::IndexedTriangleSetDistancer<VertexType, IndexedFaceType, TreeType, VectorType>
|
||||
{ vertices, faces, tree, point };
|
||||
|
||||
size_t hit_idx;
|
||||
VectorType hit_point = VectorType::Ones() * (NaN<typename VectorType::Scalar>);
|
||||
|
||||
if(tree.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0), max_distance_squared, hit_idx, hit_point);
|
||||
|
||||
return hit_point.allFinite();
|
||||
}
|
||||
|
||||
// Returns all triangles within the given radius limit
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
inline std::vector<size_t> all_triangles_in_radius(
|
||||
// Indexed triangle set - 3D vertices.
|
||||
const std::vector<VertexType> &vertices,
|
||||
// Indexed triangle set - triangular faces, references to vertices.
|
||||
const std::vector<IndexedFaceType> &faces,
|
||||
// AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices.
|
||||
const TreeType &tree,
|
||||
// Point to which the distances on the indexed triangle set is searched for.
|
||||
const VectorType &point,
|
||||
//Square of maximum distance in which triangles are searched for
|
||||
typename VectorType::Scalar max_distance_squared)
|
||||
{
|
||||
auto distancer = detail::IndexedTriangleSetDistancer<VertexType, IndexedFaceType, TreeType, VectorType>
|
||||
{ vertices, faces, tree, point };
|
||||
|
||||
if(tree.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<size_t> found_triangles{};
|
||||
detail::indexed_primitives_within_distance_squared_recurisve(distancer, size_t(0), max_distance_squared, found_triangles);
|
||||
return found_triangles;
|
||||
}
|
||||
|
||||
|
||||
// Traverse the tree and return the index of an entity whose bounding box
|
||||
// contains a given point. Returns size_t(-1) when the point is outside.
|
||||
template<typename TreeType, typename VectorType>
|
||||
void get_candidate_idxs(const TreeType& tree, const VectorType& v, std::vector<size_t>& candidates, size_t node_idx = 0)
|
||||
{
|
||||
if (tree.empty() || ! tree.node(node_idx).bbox.contains(v))
|
||||
return;
|
||||
|
||||
decltype(tree.node(node_idx)) node = tree.node(node_idx);
|
||||
static_assert(std::is_reference<decltype(node)>::value,
|
||||
"Nodes shall be addressed by reference.");
|
||||
assert(node.is_valid());
|
||||
assert(node.bbox.contains(v));
|
||||
|
||||
if (! node.is_leaf()) {
|
||||
if (tree.left_child(node_idx).bbox.contains(v))
|
||||
get_candidate_idxs(tree, v, candidates, tree.left_child_idx(node_idx));
|
||||
if (tree.right_child(node_idx).bbox.contains(v))
|
||||
get_candidate_idxs(tree, v, candidates, tree.right_child_idx(node_idx));
|
||||
} else
|
||||
candidates.push_back(node.idx);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Predicate: need to be specialized for intersections of different geomteries
|
||||
template<class G> struct Intersecting {};
|
||||
|
||||
// Intersection predicate specialization for box-box intersections
|
||||
template<class CoordType, int NumD>
|
||||
struct Intersecting<Eigen::AlignedBox<CoordType, NumD>> {
|
||||
Eigen::AlignedBox<CoordType, NumD> box;
|
||||
|
||||
Intersecting(const Eigen::AlignedBox<CoordType, NumD> &bb): box{bb} {}
|
||||
|
||||
bool operator() (const typename Tree<NumD, CoordType>::Node &node) const
|
||||
{
|
||||
return box.intersects(node.bbox);
|
||||
}
|
||||
};
|
||||
|
||||
template<class G> auto intersecting(const G &g) { return Intersecting<G>{g}; }
|
||||
|
||||
template<class G> struct Within {};
|
||||
|
||||
// Intersection predicate specialization for box-box intersections
|
||||
template<class CoordType, int NumD>
|
||||
struct Within<Eigen::AlignedBox<CoordType, NumD>> {
|
||||
Eigen::AlignedBox<CoordType, NumD> box;
|
||||
|
||||
Within(const Eigen::AlignedBox<CoordType, NumD> &bb): box{bb} {}
|
||||
|
||||
bool operator() (const typename Tree<NumD, CoordType>::Node &node) const
|
||||
{
|
||||
return node.is_leaf() ? box.contains(node.bbox) : box.intersects(node.bbox);
|
||||
}
|
||||
};
|
||||
|
||||
template<class G> auto within(const G &g) { return Within<G>{g}; }
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Returns true in case traversal should continue,
|
||||
// returns false if traversal should stop (for example if the first hit was found).
|
||||
template<int Dims, typename T, typename Pred, typename Fn>
|
||||
bool traverse_recurse(const Tree<Dims, T> &tree,
|
||||
size_t idx,
|
||||
Pred && pred,
|
||||
Fn && callback)
|
||||
{
|
||||
assert(tree.node(idx).is_valid());
|
||||
|
||||
if (!pred(tree.node(idx)))
|
||||
// Continue traversal.
|
||||
return true;
|
||||
|
||||
if (tree.node(idx).is_leaf()) {
|
||||
// Callback returns true to continue traversal, false to stop traversal.
|
||||
return callback(tree.node(idx));
|
||||
} else {
|
||||
|
||||
// call this with left and right node idx:
|
||||
auto trv = [&](size_t idx) -> bool {
|
||||
return traverse_recurse(tree, idx, std::forward<Pred>(pred),
|
||||
std::forward<Fn>(callback));
|
||||
};
|
||||
|
||||
// Left / right child node index.
|
||||
// Returns true if both children allow the traversal to continue.
|
||||
return trv(Tree<Dims, T>::left_child_idx(idx)) &&
|
||||
trv(Tree<Dims, T>::right_child_idx(idx));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Tree traversal with a predicate. Example usage:
|
||||
// traverse(tree, intersecting(QueryBox), [](size_t face_idx) {
|
||||
// /* ... */
|
||||
// });
|
||||
// Callback shall return true to continue traversal, false if it wants to stop traversal, for example if it found the answer.
|
||||
template<int Dims, typename T, typename Predicate, typename Fn>
|
||||
void traverse(const Tree<Dims, T> &tree, Predicate &&pred, Fn &&callback)
|
||||
{
|
||||
if (tree.empty()) return;
|
||||
|
||||
detail::traverse_recurse(tree, size_t(0), std::forward<Predicate>(pred),
|
||||
std::forward<Fn>(callback));
|
||||
}
|
||||
|
||||
} // namespace AABBTreeIndirect
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_AABBTreeIndirect_hpp_ */
|
||||
365
src/libslic3r/AABBTreeLines.hpp
Normal file
365
src/libslic3r/AABBTreeLines.hpp
Normal file
@@ -0,0 +1,365 @@
|
||||
#ifndef SRC_LIBSLIC3R_AABBTREELINES_HPP_
|
||||
#define SRC_LIBSLIC3R_AABBTREELINES_HPP_
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "libslic3r/AABBTreeIndirect.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r { namespace AABBTreeLines {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<typename ALineType, typename ATreeType, typename AVectorType> struct IndexedLinesDistancer
|
||||
{
|
||||
using LineType = ALineType;
|
||||
using TreeType = ATreeType;
|
||||
using VectorType = AVectorType;
|
||||
using ScalarType = typename VectorType::Scalar;
|
||||
|
||||
const std::vector<LineType> &lines;
|
||||
const TreeType &tree;
|
||||
|
||||
const VectorType origin;
|
||||
|
||||
inline VectorType closest_point_to_origin(size_t primitive_index, ScalarType &squared_distance) const
|
||||
{
|
||||
Vec<LineType::Dim, typename LineType::Scalar> nearest_point;
|
||||
const LineType &line = lines[primitive_index];
|
||||
squared_distance = line_alg::distance_to_squared(line, origin.template cast<typename LineType::Scalar>(), &nearest_point);
|
||||
return nearest_point.template cast<ScalarType>();
|
||||
}
|
||||
};
|
||||
|
||||
// returns number of intersections of ray starting in ray_origin and following the specified coordinate line with lines in tree
|
||||
// first number is hits in positive direction of ray, second number hits in negative direction. returns neagtive numbers when ray_origin is
|
||||
// on some line exactly.
|
||||
template<typename LineType, typename TreeType, typename VectorType, int coordinate>
|
||||
inline std::tuple<int, int> coordinate_aligned_ray_hit_count(size_t node_idx,
|
||||
const TreeType &tree,
|
||||
const std::vector<LineType> &lines,
|
||||
const VectorType &ray_origin)
|
||||
{
|
||||
static constexpr int other_coordinate = (coordinate + 1) % 2;
|
||||
using Scalar = typename LineType::Scalar;
|
||||
using Floating = typename std::conditional<std::is_floating_point<Scalar>::value, Scalar, double>::type;
|
||||
const auto &node = tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
if (node.is_leaf()) {
|
||||
const LineType &line = lines[node.idx];
|
||||
if (ray_origin[other_coordinate] < std::min(line.a[other_coordinate], line.b[other_coordinate]) ||
|
||||
ray_origin[other_coordinate] >= std::max(line.a[other_coordinate], line.b[other_coordinate])) {
|
||||
// the second inequality is nonsharp for a reason
|
||||
// without it, we may count contour border twice when the lines meet exactly at the spot of intersection. this prevents is
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
Scalar line_max = std::max(line.a[coordinate], line.b[coordinate]);
|
||||
Scalar line_min = std::min(line.a[coordinate], line.b[coordinate]);
|
||||
if (ray_origin[coordinate] > line_max) {
|
||||
return {1, 0};
|
||||
} else if (ray_origin[coordinate] < line_min) {
|
||||
return {0, 1};
|
||||
} else {
|
||||
// find intersection of ray with line
|
||||
// that is when ( line.a + t * (line.b - line.a) )[other_coordinate] == ray_origin[other_coordinate]
|
||||
// t = ray_origin[oc] - line.a[oc] / (line.b[oc] - line.a[oc]);
|
||||
// then we want to get value of intersection[ coordinate]
|
||||
// val_c = line.a[c] + t * (line.b[c] - line.a[c]);
|
||||
// Note that ray and line may overlap, when (line.b[oc] - line.a[oc]) is zero
|
||||
// In that case, we return negative number
|
||||
Floating distance_oc = line.b[other_coordinate] - line.a[other_coordinate];
|
||||
Floating t = (ray_origin[other_coordinate] - line.a[other_coordinate]) / distance_oc;
|
||||
Floating val_c = line.a[coordinate] + t * (line.b[coordinate] - line.a[coordinate]);
|
||||
if (ray_origin[coordinate] > val_c) {
|
||||
return {1, 0};
|
||||
} else if (ray_origin[coordinate] < val_c) {
|
||||
return {0, 1};
|
||||
} else { // ray origin is on boundary
|
||||
return {-1, -1};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int intersections_above = 0;
|
||||
int intersections_below = 0;
|
||||
size_t left_node_idx = node_idx * 2 + 1;
|
||||
size_t right_node_idx = left_node_idx + 1;
|
||||
const auto &node_left = tree.node(left_node_idx);
|
||||
const auto &node_right = tree.node(right_node_idx);
|
||||
assert(node_left.is_valid());
|
||||
assert(node_right.is_valid());
|
||||
|
||||
if (node_left.bbox.min()[other_coordinate] <= ray_origin[other_coordinate] &&
|
||||
node_left.bbox.max()[other_coordinate] >=
|
||||
ray_origin[other_coordinate]) {
|
||||
auto [above, below] = coordinate_aligned_ray_hit_count<LineType, TreeType, VectorType, coordinate>(left_node_idx, tree, lines,
|
||||
ray_origin);
|
||||
if (above < 0 || below < 0) return {-1, -1};
|
||||
intersections_above += above;
|
||||
intersections_below += below;
|
||||
}
|
||||
|
||||
if (node_right.bbox.min()[other_coordinate] <= ray_origin[other_coordinate] &&
|
||||
node_right.bbox.max()[other_coordinate] >= ray_origin[other_coordinate]) {
|
||||
auto [above, below] = coordinate_aligned_ray_hit_count<LineType, TreeType, VectorType, coordinate>(right_node_idx, tree, lines,
|
||||
ray_origin);
|
||||
if (above < 0 || below < 0) return {-1, -1};
|
||||
intersections_above += above;
|
||||
intersections_below += below;
|
||||
}
|
||||
return {intersections_above, intersections_below};
|
||||
}
|
||||
}
|
||||
|
||||
template<typename LineType, typename TreeType, typename VectorType>
|
||||
inline std::vector<std::pair<VectorType, size_t>> get_intersections_with_line(size_t node_idx,
|
||||
const TreeType &tree,
|
||||
const std::vector<LineType> &lines,
|
||||
const LineType &line,
|
||||
const typename TreeType::BoundingBox &line_bb)
|
||||
{
|
||||
const auto &node = tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
if (node.is_leaf()) {
|
||||
VectorType intersection_pt;
|
||||
if (line_alg::intersection(line, lines[node.idx], &intersection_pt)) {
|
||||
return {std::pair<VectorType, size_t>(intersection_pt, node.idx)};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
size_t left_node_idx = node_idx * 2 + 1;
|
||||
size_t right_node_idx = left_node_idx + 1;
|
||||
const auto &node_left = tree.node(left_node_idx);
|
||||
const auto &node_right = tree.node(right_node_idx);
|
||||
assert(node_left.is_valid());
|
||||
assert(node_right.is_valid());
|
||||
|
||||
std::vector<std::pair<VectorType, size_t>> result;
|
||||
|
||||
if (node_left.bbox.intersects(line_bb)) {
|
||||
std::vector<std::pair<VectorType, size_t>> intersections =
|
||||
get_intersections_with_line<LineType, TreeType, VectorType>(left_node_idx, tree, lines, line, line_bb);
|
||||
result.insert(result.end(), intersections.begin(), intersections.end());
|
||||
}
|
||||
|
||||
if (node_right.bbox.intersects(line_bb)) {
|
||||
std::vector<std::pair<VectorType, size_t>> intersections =
|
||||
get_intersections_with_line<LineType, TreeType, VectorType>(right_node_idx, tree, lines, line, line_bb);
|
||||
result.insert(result.end(), intersections.begin(), intersections.end());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Build a balanced AABB Tree over a vector of lines, balancing the tree
|
||||
// on centroids of the lines.
|
||||
// Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies
|
||||
// during tree traversal.
|
||||
template<typename LineType>
|
||||
inline AABBTreeIndirect::Tree<2, typename LineType::Scalar> build_aabb_tree_over_indexed_lines(const std::vector<LineType> &lines)
|
||||
{
|
||||
using TreeType = AABBTreeIndirect::Tree<2, typename LineType::Scalar>;
|
||||
// using CoordType = typename TreeType::CoordType;
|
||||
using VectorType = typename TreeType::VectorType;
|
||||
using BoundingBox = typename TreeType::BoundingBox;
|
||||
|
||||
struct InputType
|
||||
{
|
||||
size_t idx() const { return m_idx; }
|
||||
const BoundingBox &bbox() const { return m_bbox; }
|
||||
const VectorType ¢roid() const { return m_centroid; }
|
||||
|
||||
size_t m_idx;
|
||||
BoundingBox m_bbox;
|
||||
VectorType m_centroid;
|
||||
};
|
||||
|
||||
std::vector<InputType> input;
|
||||
input.reserve(lines.size());
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
const LineType &line = lines[i];
|
||||
InputType n;
|
||||
n.m_idx = i;
|
||||
n.m_centroid = (line.a + line.b) * 0.5;
|
||||
n.m_bbox = BoundingBox(line.a, line.a);
|
||||
n.m_bbox.extend(line.b);
|
||||
input.emplace_back(n);
|
||||
}
|
||||
|
||||
TreeType out;
|
||||
out.build(std::move(input));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Finding a closest line, its closest point and squared distance to the closest point
|
||||
// Returns squared distance to the closest point or -1 if the input is empty.
|
||||
// or no closer point than max_sq_dist
|
||||
template<typename LineType, typename TreeType, typename VectorType>
|
||||
inline typename VectorType::Scalar squared_distance_to_indexed_lines(
|
||||
const std::vector<LineType> &lines,
|
||||
const TreeType &tree,
|
||||
const VectorType &point,
|
||||
size_t &hit_idx_out,
|
||||
Eigen::PlainObjectBase<VectorType> &hit_point_out,
|
||||
typename VectorType::Scalar max_sqr_dist = std::numeric_limits<typename VectorType::Scalar>::infinity())
|
||||
{
|
||||
using Scalar = typename VectorType::Scalar;
|
||||
if (tree.empty()) return Scalar(-1);
|
||||
auto distancer = detail::IndexedLinesDistancer<LineType, TreeType, VectorType>{lines, tree, point};
|
||||
return AABBTreeIndirect::detail::squared_distance_to_indexed_primitives_recursive(distancer, size_t(0), Scalar(0), max_sqr_dist,
|
||||
hit_idx_out, hit_point_out);
|
||||
}
|
||||
|
||||
// Returns all lines within the given radius limit
|
||||
template<typename LineType, typename TreeType, typename VectorType>
|
||||
inline std::vector<size_t> all_lines_in_radius(const std::vector<LineType> &lines,
|
||||
const TreeType &tree,
|
||||
const VectorType &point,
|
||||
typename VectorType::Scalar max_distance_squared)
|
||||
{
|
||||
auto distancer = detail::IndexedLinesDistancer<LineType, TreeType, VectorType>{lines, tree, point};
|
||||
|
||||
if (tree.empty()) { return {}; }
|
||||
|
||||
std::vector<size_t> found_lines{};
|
||||
AABBTreeIndirect::detail::indexed_primitives_within_distance_squared_recurisve(distancer, size_t(0), max_distance_squared, found_lines);
|
||||
return found_lines;
|
||||
}
|
||||
|
||||
// return 1 if true, -1 if false, 0 for point on contour (or if cannot be determined)
|
||||
template<typename LineType, typename TreeType, typename VectorType>
|
||||
inline int point_outside_closed_contours(const std::vector<LineType> &lines, const TreeType &tree, const VectorType &point)
|
||||
{
|
||||
if (tree.empty()) { return 1; }
|
||||
|
||||
auto [hits_above, hits_below] = detail::coordinate_aligned_ray_hit_count<LineType, TreeType, VectorType, 0>(0, tree, lines, point);
|
||||
if (hits_above < 0 || hits_below < 0) {
|
||||
return 0;
|
||||
} else if (hits_above % 2 == 1 && hits_below % 2 == 1) {
|
||||
return -1;
|
||||
} else if (hits_above % 2 == 0 && hits_below % 2 == 0) {
|
||||
return 1;
|
||||
} else { // this should not happen with closed contours. lets check it in Y direction
|
||||
auto [hits_above, hits_below] = detail::coordinate_aligned_ray_hit_count<LineType, TreeType, VectorType, 1>(0, tree, lines, point);
|
||||
if (hits_above < 0 || hits_below < 0) {
|
||||
return 0;
|
||||
} else if (hits_above % 2 == 1 && hits_below % 2 == 1) {
|
||||
return -1;
|
||||
} else if (hits_above % 2 == 0 && hits_below % 2 == 0) {
|
||||
return 1;
|
||||
} else { // both results were unclear
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<bool sorted, typename VectorType, typename LineType, typename TreeType>
|
||||
inline std::vector<std::pair<VectorType, size_t>> get_intersections_with_line(const std::vector<LineType> &lines,
|
||||
const TreeType &tree,
|
||||
const LineType &line)
|
||||
{
|
||||
if (tree.empty()) {
|
||||
return {};
|
||||
}
|
||||
auto line_bb = typename TreeType::BoundingBox(line.a, line.a);
|
||||
line_bb.extend(line.b);
|
||||
|
||||
auto intersections = detail::get_intersections_with_line<LineType, TreeType, VectorType>(0, tree, lines, line, line_bb);
|
||||
if (sorted) {
|
||||
using Floating =
|
||||
typename std::conditional<std::is_floating_point<typename LineType::Scalar>::value, typename LineType::Scalar, double>::type;
|
||||
|
||||
std::vector<std::pair<Floating, std::pair<VectorType, size_t>>> points_with_sq_distance{};
|
||||
for (const auto &p : intersections) {
|
||||
points_with_sq_distance.emplace_back((p.first - line.a).template cast<Floating>().squaredNorm(), p);
|
||||
}
|
||||
std::sort(points_with_sq_distance.begin(), points_with_sq_distance.end(),
|
||||
[](const std::pair<Floating, std::pair<VectorType, size_t>> &left,
|
||||
std::pair<Floating, std::pair<VectorType, size_t>> &right) { return left.first < right.first; });
|
||||
for (size_t i = 0; i < points_with_sq_distance.size(); i++) {
|
||||
intersections[i] = points_with_sq_distance[i].second;
|
||||
}
|
||||
}
|
||||
|
||||
return intersections;
|
||||
}
|
||||
|
||||
template<typename LineType> class LinesDistancer
|
||||
{
|
||||
public:
|
||||
using Scalar = typename LineType::Scalar;
|
||||
using Floating = typename std::conditional<std::is_floating_point<Scalar>::value, Scalar, double>::type;
|
||||
private:
|
||||
std::vector<LineType> lines;
|
||||
AABBTreeIndirect::Tree<2, Scalar> tree;
|
||||
|
||||
public:
|
||||
explicit LinesDistancer(const std::vector<LineType> &lines) : lines(lines)
|
||||
{
|
||||
tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(this->lines);
|
||||
}
|
||||
|
||||
explicit LinesDistancer(std::vector<LineType> &&lines) : lines(lines)
|
||||
{
|
||||
tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(this->lines);
|
||||
}
|
||||
|
||||
LinesDistancer() = default;
|
||||
|
||||
// 1 true, -1 false, 0 cannot determine
|
||||
int outside(const Vec<2, Scalar> &point) const { return point_outside_closed_contours(lines, tree, point); }
|
||||
|
||||
// negative sign means inside
|
||||
template<bool SIGNED_DISTANCE>
|
||||
std::tuple<Floating, size_t, Vec<2, Floating>> distance_from_lines_extra(const Vec<2, Scalar> &point) const
|
||||
{
|
||||
size_t nearest_line_index_out = size_t(-1);
|
||||
Vec<2, Floating> nearest_point_out = Vec<2, Floating>::Zero();
|
||||
Vec<2, Floating> p = point.template cast<Floating>();
|
||||
auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, p, nearest_line_index_out, nearest_point_out);
|
||||
|
||||
if (distance < 0) {
|
||||
return {std::numeric_limits<Floating>::infinity(), nearest_line_index_out, nearest_point_out};
|
||||
}
|
||||
distance = sqrt(distance);
|
||||
|
||||
if (SIGNED_DISTANCE) {
|
||||
distance *= outside(point);
|
||||
}
|
||||
|
||||
return {distance, nearest_line_index_out, nearest_point_out};
|
||||
}
|
||||
|
||||
template<bool SIGNED_DISTANCE> Floating distance_from_lines(const Vec<2, typename LineType::Scalar> &point) const
|
||||
{
|
||||
auto [dist, idx, np] = distance_from_lines_extra<SIGNED_DISTANCE>(point);
|
||||
return dist;
|
||||
}
|
||||
|
||||
std::vector<size_t> all_lines_in_radius(const Vec<2, typename LineType::Scalar> &point, Floating radius)
|
||||
{
|
||||
return all_lines_in_radius(this->lines, this->tree, point, radius * radius);
|
||||
}
|
||||
|
||||
template<bool sorted> std::vector<std::pair<Vec<2, Scalar>, size_t>> intersections_with_line(const LineType &line) const
|
||||
{
|
||||
return get_intersections_with_line<sorted, Vec<2, Scalar>>(lines, tree, line);
|
||||
}
|
||||
|
||||
const LineType &get_line(size_t line_idx) const { return lines[line_idx]; }
|
||||
|
||||
const std::vector<LineType> &get_lines() const { return lines; }
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::AABBTreeLines
|
||||
|
||||
#endif /* SRC_LIBSLIC3R_AABBTREELINES_HPP_ */
|
||||
157
src/libslic3r/AStar.hpp
Normal file
157
src/libslic3r/AStar.hpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#ifndef ASTAR_HPP
|
||||
#define ASTAR_HPP
|
||||
|
||||
#include <cmath> // std::isinf() is here
|
||||
#include <unordered_map>
|
||||
|
||||
#include "libslic3r/MutablePriorityQueue.hpp"
|
||||
|
||||
namespace Slic3r { namespace astar {
|
||||
|
||||
// Borrowed from C++20
|
||||
template<class T> using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
|
||||
|
||||
// Input interface for the Astar algorithm. Specialize this struct for a
|
||||
// particular type and implement all the 4 methods and specify the Node type
|
||||
// to register the new type for the astar implementation.
|
||||
template<class T> struct TracerTraits_
|
||||
{
|
||||
// The type of a node used by this tracer. Usually a point in space.
|
||||
using Node = typename T::Node;
|
||||
|
||||
// Call fn for every new node reachable from node 'src'. fn should have the
|
||||
// candidate node as its only argument.
|
||||
template<class Fn> static void foreach_reachable(const T &tracer, const Node &src, Fn &&fn) { tracer.foreach_reachable(src, fn); }
|
||||
|
||||
// Get the distance from node 'a' to node 'b'. This is sometimes referred
|
||||
// to as the g value of a node in AStar context.
|
||||
static float distance(const T &tracer, const Node &a, const Node &b) { return tracer.distance(a, b); }
|
||||
|
||||
// Get the estimated distance heuristic from node 'n' to the destination.
|
||||
// This is referred to as the h value in AStar context.
|
||||
// If node 'n' is the goal, this function should return a negative value.
|
||||
// Note that this heuristic should be admissible (never bigger than the real
|
||||
// cost) in order for Astar to work.
|
||||
static float goal_heuristic(const T &tracer, const Node &n) { return tracer.goal_heuristic(n); }
|
||||
|
||||
// Return a unique identifier (hash) for node 'n'.
|
||||
static size_t unique_id(const T &tracer, const Node &n) { return tracer.unique_id(n); }
|
||||
};
|
||||
|
||||
// Helper definition to get the node type of a tracer
|
||||
template<class T> using TracerNodeT = typename TracerTraits_<remove_cvref_t<T>>::Node;
|
||||
|
||||
constexpr auto Unassigned = std::numeric_limits<size_t>::max();
|
||||
|
||||
template<class Tracer> struct QNode // Queue node. Keeps track of scores g, and h
|
||||
{
|
||||
TracerNodeT<Tracer> node; // The actual node itself
|
||||
size_t queue_id; // Position in the open queue or Unassigned if closed
|
||||
size_t parent; // unique id of the parent or Unassigned
|
||||
|
||||
float g, h;
|
||||
float f() const { return g + h; }
|
||||
|
||||
QNode(TracerNodeT<Tracer> n = {}, size_t p = Unassigned, float gval = std::numeric_limits<float>::infinity(), float hval = 0.f)
|
||||
: node{std::move(n)}, parent{p}, queue_id{InvalidQueueID}, g{gval}, h{hval}
|
||||
{}
|
||||
};
|
||||
|
||||
// Run the AStar algorithm on a tracer implementation.
|
||||
// The 'tracer' argument encapsulates the domain (grid, point cloud, etc...)
|
||||
// The 'source' argument is the starting node.
|
||||
// The 'out' argument is the output iterator into which the output nodes are
|
||||
// written. For performance reasons, the order is reverse, from the destination
|
||||
// to the source -- (destination included, source is not).
|
||||
// The 'cached_nodes' argument is an optional associative container to hold a
|
||||
// QNode entry for each visited node. Any compatible container can be used
|
||||
// (like std::map or maps with different allocators, even a sufficiently large
|
||||
// std::vector).
|
||||
//
|
||||
// Note that no destination node is given in the signature. The tracer's
|
||||
// goal_heuristic() method should return a negative value if a node is a
|
||||
// destination node.
|
||||
template<class Tracer, class It, class NodeMap = std::unordered_map<size_t, QNode<Tracer>>>
|
||||
bool search_route(const Tracer &tracer, const TracerNodeT<Tracer> &source, It out, NodeMap &&cached_nodes = {})
|
||||
{
|
||||
using Node = TracerNodeT<Tracer>;
|
||||
using QNode = QNode<Tracer>;
|
||||
using TracerTraits = TracerTraits_<remove_cvref_t<Tracer>>;
|
||||
|
||||
struct LessPred
|
||||
{ // Comparison functor needed by the priority queue
|
||||
NodeMap &m;
|
||||
bool operator()(size_t node_a, size_t node_b) { return m[node_a].f() < m[node_b].f(); }
|
||||
};
|
||||
|
||||
auto qopen = make_mutable_priority_queue<size_t, true>([&cached_nodes](size_t el, size_t qidx) { cached_nodes[el].queue_id = qidx; }, LessPred{cached_nodes});
|
||||
|
||||
QNode initial{source, /*parent = */ Unassigned, /*g = */ 0.f};
|
||||
size_t source_id = TracerTraits::unique_id(tracer, source);
|
||||
cached_nodes[source_id] = initial;
|
||||
qopen.push(source_id);
|
||||
|
||||
size_t goal_id = TracerTraits::goal_heuristic(tracer, source) < 0.f ? source_id : Unassigned;
|
||||
|
||||
while (goal_id == Unassigned && !qopen.empty()) {
|
||||
size_t q_id = qopen.top();
|
||||
qopen.pop();
|
||||
QNode &q = cached_nodes[q_id];
|
||||
|
||||
// This should absolutely be initialized in the cache already
|
||||
assert(!std::isinf(q.g));
|
||||
|
||||
TracerTraits::foreach_reachable(tracer, q.node, [&](const Node &succ_nd) {
|
||||
if (goal_id != Unassigned) return true;
|
||||
|
||||
float h = TracerTraits::goal_heuristic(tracer, succ_nd);
|
||||
float dst = TracerTraits::distance(tracer, q.node, succ_nd);
|
||||
size_t succ_id = TracerTraits::unique_id(tracer, succ_nd);
|
||||
QNode qsucc_nd{succ_nd, q_id, q.g + dst, h};
|
||||
|
||||
if (h < 0.f) {
|
||||
goal_id = succ_id;
|
||||
cached_nodes[succ_id] = qsucc_nd;
|
||||
} else {
|
||||
// If succ_id is not in cache, it gets created with g = infinity
|
||||
QNode &prev_nd = cached_nodes[succ_id];
|
||||
|
||||
if (qsucc_nd.g < prev_nd.g) {
|
||||
// new route is better, apply it:
|
||||
|
||||
// Save the old queue id, it would be lost after the next line
|
||||
size_t queue_id = prev_nd.queue_id;
|
||||
|
||||
// The cache needs to be updated either way
|
||||
prev_nd = qsucc_nd;
|
||||
|
||||
if (queue_id == InvalidQueueID)
|
||||
// was in closed or unqueued, rescheduling
|
||||
qopen.push(succ_id);
|
||||
else // was in open, updating
|
||||
qopen.update(queue_id);
|
||||
}
|
||||
}
|
||||
|
||||
return goal_id != Unassigned;
|
||||
});
|
||||
}
|
||||
|
||||
// Write the output, do not reverse. Clients can do so if they need to.
|
||||
if (goal_id != Unassigned) {
|
||||
const QNode *q = &cached_nodes[goal_id];
|
||||
while (q->parent != Unassigned) {
|
||||
assert(!std::isinf(q->g)); // Uninitialized nodes are NOT allowed
|
||||
|
||||
*out = q->node;
|
||||
++out;
|
||||
q = &cached_nodes[q->parent];
|
||||
}
|
||||
}
|
||||
|
||||
return goal_id != Unassigned;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::astar
|
||||
|
||||
#endif // ASTAR_HPP
|
||||
130
src/libslic3r/AnyPtr.hpp
Normal file
130
src/libslic3r/AnyPtr.hpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#ifndef ANYPTR_HPP
|
||||
#define ANYPTR_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// A general purpose pointer holder that can hold any type of smart pointer
|
||||
// or raw pointer which can own or not own any object they point to.
|
||||
// In case a raw pointer is stored, it is not destructed so ownership is
|
||||
// assumed to be foreign.
|
||||
//
|
||||
// The stored pointer is not checked for being null when dereferenced.
|
||||
//
|
||||
// This is a movable only object due to the fact that it can possibly hold
|
||||
// a unique_ptr which a non-copy.
|
||||
template<class T>
|
||||
class AnyPtr {
|
||||
enum { RawPtr, UPtr, ShPtr, WkPtr };
|
||||
|
||||
boost::variant<T*, std::unique_ptr<T>, std::shared_ptr<T>, std::weak_ptr<T>> ptr;
|
||||
|
||||
template<class Self> static T *get_ptr(Self &&s)
|
||||
{
|
||||
switch (s.ptr.which()) {
|
||||
case RawPtr: return boost::get<T *>(s.ptr);
|
||||
case UPtr: return boost::get<std::unique_ptr<T>>(s.ptr).get();
|
||||
case ShPtr: return boost::get<std::shared_ptr<T>>(s.ptr).get();
|
||||
case WkPtr: {
|
||||
auto shptr = boost::get<std::weak_ptr<T>>(s.ptr).lock();
|
||||
return shptr.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
template<class TT = T, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(TT *p = nullptr) : ptr{p}
|
||||
{}
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(std::unique_ptr<TT> p) : ptr{std::unique_ptr<T>(std::move(p))}
|
||||
{}
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(std::shared_ptr<TT> p) : ptr{std::shared_ptr<T>(std::move(p))}
|
||||
{}
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(std::weak_ptr<TT> p) : ptr{std::weak_ptr<T>(std::move(p))}
|
||||
{}
|
||||
|
||||
~AnyPtr() = default;
|
||||
|
||||
AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {}
|
||||
AnyPtr(const AnyPtr &other) = delete;
|
||||
|
||||
AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; }
|
||||
AnyPtr &operator=(const AnyPtr &other) = delete;
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(TT *p) { ptr = p; return *this; }
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(std::unique_ptr<TT> p) { ptr = std::move(p); return *this; }
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(std::shared_ptr<TT> p) { ptr = p; return *this; }
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(std::weak_ptr<TT> p) { ptr = std::move(p); return *this; }
|
||||
|
||||
const T &operator*() const { return *get_ptr(*this); }
|
||||
T &operator*() { return *get_ptr(*this); }
|
||||
|
||||
T *operator->() { return get_ptr(*this); }
|
||||
const T *operator->() const { return get_ptr(*this); }
|
||||
|
||||
T *get() { return get_ptr(*this); }
|
||||
const T *get() const { return get_ptr(*this); }
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
switch (ptr.which()) {
|
||||
case RawPtr: return bool(boost::get<T *>(ptr));
|
||||
case UPtr: return bool(boost::get<std::unique_ptr<T>>(ptr));
|
||||
case ShPtr: return bool(boost::get<std::shared_ptr<T>>(ptr));
|
||||
case WkPtr: {
|
||||
auto shptr = boost::get<std::weak_ptr<T>>(ptr).lock();
|
||||
return bool(shptr);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the stored pointer is a shared or weak pointer, returns a reference
|
||||
// counted copy. Empty shared pointer is returned otherwise.
|
||||
std::shared_ptr<T> get_shared_cpy() const
|
||||
{
|
||||
std::shared_ptr<T> ret;
|
||||
|
||||
switch (ptr.which()) {
|
||||
case ShPtr: ret = boost::get<std::shared_ptr<T>>(ptr); break;
|
||||
case WkPtr: ret = boost::get<std::weak_ptr<T>>(ptr).lock(); break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// If the underlying pointer is unique, convert to shared pointer
|
||||
void convert_unique_to_shared()
|
||||
{
|
||||
if (ptr.which() == UPtr)
|
||||
ptr = std::shared_ptr<T>{std::move(boost::get<std::unique_ptr<T>>(ptr))};
|
||||
}
|
||||
|
||||
// Returns true if the data is owned by this AnyPtr instance
|
||||
bool is_owned() const noexcept
|
||||
{
|
||||
return ptr.which() == UPtr || ptr.which() == ShPtr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // ANYPTR_HPP
|
||||
1290
src/libslic3r/AppConfig.cpp
Normal file
1290
src/libslic3r/AppConfig.cpp
Normal file
File diff suppressed because it is too large
Load Diff
297
src/libslic3r/AppConfig.hpp
Normal file
297
src/libslic3r/AppConfig.hpp
Normal file
@@ -0,0 +1,297 @@
|
||||
#ifndef slic3r_AppConfig_hpp_
|
||||
#define slic3r_AppConfig_hpp_
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "nlohmann/json.hpp"
|
||||
#include <boost/algorithm/string/trim_all.hpp>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Semver.hpp"
|
||||
#include "Calib.hpp"
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
#define ENV_DEV_HOST "0"
|
||||
#define ENV_QAT_HOST "1"
|
||||
#define ENV_PRE_HOST "2"
|
||||
#define ENV_PRODUCT_HOST "3"
|
||||
|
||||
#define SUPPORT_DARK_MODE
|
||||
//#define _MSW_DARK_MODE
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppConfig
|
||||
{
|
||||
public:
|
||||
enum class EAppMode : unsigned char
|
||||
{
|
||||
Editor,
|
||||
GCodeViewer
|
||||
};
|
||||
|
||||
//QDS: remove GCodeViewer as seperate APP logic
|
||||
explicit AppConfig() :
|
||||
m_dirty(false),
|
||||
m_orig_version(Semver::invalid()),
|
||||
m_mode(EAppMode::Editor),
|
||||
m_legacy_datadir(false)
|
||||
{
|
||||
this->reset();
|
||||
}
|
||||
|
||||
std::string get_language_code();
|
||||
std::string get_hms_host();
|
||||
|
||||
// Clear and reset to defaults.
|
||||
void reset();
|
||||
// Override missing or keys with their defaults.
|
||||
void set_defaults();
|
||||
|
||||
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
|
||||
// return error string or empty strinf
|
||||
std::string load();
|
||||
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
|
||||
void save();
|
||||
|
||||
// Does this config need to be saved?
|
||||
bool dirty() const { return m_dirty; }
|
||||
|
||||
|
||||
void set_dirty() { m_dirty = true; }
|
||||
|
||||
// Const accessor, it will return false if a section or a key does not exist.
|
||||
bool get(const std::string §ion, const std::string &key, std::string &value) const
|
||||
{
|
||||
value.clear();
|
||||
auto it = m_storage.find(section);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
auto it2 = it->second.find(key);
|
||||
if (it2 == it->second.end())
|
||||
return false;
|
||||
value = it2->second;
|
||||
return true;
|
||||
}
|
||||
std::string get(const std::string §ion, const std::string &key) const
|
||||
{ std::string value; this->get(section, key, value); return value; }
|
||||
std::string get(const std::string &key) const
|
||||
{ std::string value; this->get("app", key, value); return value; }
|
||||
void set(const std::string §ion, const std::string &key, const std::string &value)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
std::string key_trimmed = key;
|
||||
boost::trim_all(key_trimmed);
|
||||
assert(key_trimmed == key);
|
||||
assert(! key_trimmed.empty());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
std::string &old = m_storage[section][key];
|
||||
if (old != value) {
|
||||
old = value;
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_str(const std::string& section, const std::string& key, const std::string& value)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
std::string key_trimmed = key;
|
||||
boost::trim_all(key_trimmed);
|
||||
assert(key_trimmed == key);
|
||||
assert(!key_trimmed.empty());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
std::string& old = m_storage[section][key];
|
||||
if (old != value) {
|
||||
old = value;
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set(const std::string& section, const std::string &key, bool value)
|
||||
{
|
||||
if (value){
|
||||
set(section, key, std::string("true"));
|
||||
} else {
|
||||
set(section, key, std::string("false"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void set(const std::string &key, const std::string &value)
|
||||
{ this->set("app", key, value); }
|
||||
|
||||
void set_bool(const std::string &key, const bool &value)
|
||||
{
|
||||
this->set("app", key, value);
|
||||
}
|
||||
|
||||
bool has(const std::string §ion, const std::string &key) const
|
||||
{
|
||||
auto it = m_storage.find(section);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
auto it2 = it->second.find(key);
|
||||
return it2 != it->second.end() && ! it2->second.empty();
|
||||
}
|
||||
bool has(const std::string &key) const
|
||||
{ return this->has("app", key); }
|
||||
|
||||
void erase(const std::string §ion, const std::string &key)
|
||||
{
|
||||
auto it = m_storage.find(section);
|
||||
if (it != m_storage.end()) {
|
||||
it->second.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
bool has_section(const std::string §ion) const
|
||||
{ return m_storage.find(section) != m_storage.end(); }
|
||||
const std::map<std::string, std::string>& get_section(const std::string §ion) const
|
||||
{ return m_storage.find(section)->second; }
|
||||
void set_section(const std::string §ion, const std::map<std::string, std::string>& data)
|
||||
{ m_storage[section] = data; }
|
||||
void clear_section(const std::string §ion)
|
||||
{ m_storage[section].clear(); }
|
||||
|
||||
typedef std::map<std::string, std::map<std::string, std::set<std::string>>> VendorMap;
|
||||
bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const;
|
||||
void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable);
|
||||
void set_vendors(const AppConfig &from);
|
||||
void set_vendors(const VendorMap &vendors) { m_vendors = vendors; m_dirty = true; }
|
||||
void set_vendors(VendorMap &&vendors) { m_vendors = std::move(vendors); m_dirty = true; }
|
||||
const VendorMap& vendors() const { return m_vendors; }
|
||||
|
||||
const std::vector<std::string> &get_filament_presets() const { return m_filament_presets; }
|
||||
void set_filament_presets(const std::vector<std::string> &filament_presets){
|
||||
m_filament_presets = filament_presets;
|
||||
m_dirty = true;
|
||||
}
|
||||
const std::vector<std::string> &get_filament_colors() const { return m_filament_colors; }
|
||||
void set_filament_colors(const std::vector<std::string> &filament_colors){
|
||||
m_filament_colors = filament_colors;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
const std::vector<PrinterCaliInfo> &get_printer_cali_infos() const { return m_printer_cali_infos; }
|
||||
void save_printer_cali_infos(const PrinterCaliInfo& cali_info, bool need_change_status = true);
|
||||
|
||||
// return recent/last_opened_folder or recent/settings_folder or empty string.
|
||||
std::string get_last_dir() const;
|
||||
void update_config_dir(const std::string &dir);
|
||||
void update_skein_dir(const std::string &dir);
|
||||
|
||||
//std::string get_last_output_dir(const std::string &alt) const;
|
||||
//void update_last_output_dir(const std::string &dir);
|
||||
std::string get_last_output_dir(const std::string& alt, const bool removable = false) const;
|
||||
void update_last_output_dir(const std::string &dir, const bool removable = false);
|
||||
|
||||
// QDS: backup & restore
|
||||
std::string get_last_backup_dir() const;
|
||||
void update_last_backup_dir(const std::string &dir);
|
||||
|
||||
std::string get_region();
|
||||
std::string get_country_code();
|
||||
bool is_engineering_region();
|
||||
|
||||
void save_custom_color_to_config(const std::vector<std::string> &colors);
|
||||
std::vector<std::string> get_custom_color_from_config();
|
||||
// reset the current print / filament / printer selections, so that
|
||||
// the PresetBundle::load_selections(const AppConfig &config) call will select
|
||||
// the first non-default preset when called.
|
||||
void reset_selections();
|
||||
|
||||
// Get the default config path from Slic3r::data_dir().
|
||||
std::string config_path();
|
||||
|
||||
// Returns true if the user's data directory comes from before Slic3r 1.40.0 (no updating)
|
||||
bool legacy_datadir() const { return m_legacy_datadir; }
|
||||
void set_legacy_datadir(bool value) { m_legacy_datadir = value; }
|
||||
|
||||
// Get the Slic3r version check url.
|
||||
// This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file.
|
||||
std::string version_check_url() const;
|
||||
|
||||
// Returns the original Slic3r version found in the ini file before it was overwritten
|
||||
// by the current version
|
||||
Semver orig_version() const { return m_orig_version; }
|
||||
|
||||
// Does the config file exist?
|
||||
bool exists();
|
||||
|
||||
void set_loading_path(const std::string& path) { m_loading_path = path; }
|
||||
std::string loading_path() { return (m_loading_path.empty() ? config_path() : m_loading_path); }
|
||||
|
||||
std::vector<std::string> get_recent_projects() const;
|
||||
void set_recent_projects(const std::vector<std::string>& recent_projects);
|
||||
|
||||
void set_mouse_device(const std::string& name, double translation_speed, double translation_deadzone, float rotation_speed, float rotation_deadzone, double zoom_speed, bool swap_yz);
|
||||
std::vector<std::string> get_mouse_device_names() const;
|
||||
bool get_mouse_device_translation_speed(const std::string& name, double& speed) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "translation_speed", speed); }
|
||||
bool get_mouse_device_translation_deadzone(const std::string& name, double& deadzone) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "translation_deadzone", deadzone); }
|
||||
bool get_mouse_device_rotation_speed(const std::string& name, float& speed) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "rotation_speed", speed); }
|
||||
bool get_mouse_device_rotation_deadzone(const std::string& name, float& deadzone) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "rotation_deadzone", deadzone); }
|
||||
bool get_mouse_device_zoom_speed(const std::string& name, double& speed) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "zoom_speed", speed); }
|
||||
bool get_mouse_device_swap_yz(const std::string& name, bool& swap) const
|
||||
{ return get_3dmouse_device_numeric_value(name, "swap_yz", swap); }
|
||||
|
||||
static const std::string SECTION_FILAMENTS;
|
||||
static const std::string SECTION_MATERIALS;
|
||||
|
||||
//w13
|
||||
bool get_seal() { return m_seal; }
|
||||
void set_seal(bool val) { m_seal = val; }
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
bool get_3dmouse_device_numeric_value(const std::string &device_name, const char *parameter_name, T &out) const
|
||||
{
|
||||
std::string key = std::string("mouse_device:") + device_name;
|
||||
auto it = m_storage.find(key);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
auto it_val = it->second.find(parameter_name);
|
||||
if (it_val == it->second.end())
|
||||
return false;
|
||||
out = T(string_to_double_decimal_point(it_val->second));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Type of application: Editor or GCodeViewer
|
||||
EAppMode m_mode { EAppMode::Editor };
|
||||
// Map of section, name -> value
|
||||
std::map<std::string, std::map<std::string, std::string>> m_storage;
|
||||
|
||||
// Map of enabled vendors / models / variants
|
||||
VendorMap m_vendors;
|
||||
// Has any value been modified since the config.ini has been last saved or loaded?
|
||||
bool m_dirty;
|
||||
// Original version found in the ini file before it was overwritten
|
||||
Semver m_orig_version;
|
||||
// Whether the existing version is before system profiles & configuration updating
|
||||
bool m_legacy_datadir;
|
||||
|
||||
std::string m_loading_path;
|
||||
|
||||
std::vector<std::string> m_filament_presets;
|
||||
std::vector<std::string> m_filament_colors;
|
||||
|
||||
std::vector<PrinterCaliInfo> m_printer_cali_infos;
|
||||
//w13
|
||||
bool m_seal = true;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_AppConfig_hpp_ */
|
||||
79
src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp
Normal file
79
src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
BeadingStrategy::BeadingStrategy(coord_t optimal_width, double wall_split_middle_threshold, double wall_add_middle_threshold, coord_t default_transition_length, float transitioning_angle)
|
||||
: optimal_width(optimal_width)
|
||||
, wall_split_middle_threshold(wall_split_middle_threshold)
|
||||
, wall_add_middle_threshold(wall_add_middle_threshold)
|
||||
, default_transition_length(default_transition_length)
|
||||
, transitioning_angle(transitioning_angle)
|
||||
{
|
||||
name = "Unknown";
|
||||
}
|
||||
|
||||
BeadingStrategy::BeadingStrategy(const BeadingStrategy &other)
|
||||
: optimal_width(other.optimal_width)
|
||||
, wall_split_middle_threshold(other.wall_split_middle_threshold)
|
||||
, wall_add_middle_threshold(other.wall_add_middle_threshold)
|
||||
, default_transition_length(other.default_transition_length)
|
||||
, transitioning_angle(other.transitioning_angle)
|
||||
, name(other.name)
|
||||
{}
|
||||
|
||||
coord_t BeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
if (lower_bead_count == 0)
|
||||
return scaled<coord_t>(0.01);
|
||||
return default_transition_length;
|
||||
}
|
||||
|
||||
float BeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
|
||||
{
|
||||
coord_t lower_optimum = getOptimalThickness(lower_bead_count);
|
||||
coord_t transition_point = getTransitionThickness(lower_bead_count);
|
||||
coord_t upper_optimum = getOptimalThickness(lower_bead_count + 1);
|
||||
return 1.0 - float(transition_point - lower_optimum) / float(upper_optimum - lower_optimum);
|
||||
}
|
||||
|
||||
std::vector<coord_t> BeadingStrategy::getNonlinearThicknesses(coord_t lower_bead_count) const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string BeadingStrategy::toString() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
double BeadingStrategy::getSplitMiddleThreshold() const
|
||||
{
|
||||
return wall_split_middle_threshold;
|
||||
}
|
||||
|
||||
double BeadingStrategy::getTransitioningAngle() const
|
||||
{
|
||||
return transitioning_angle;
|
||||
}
|
||||
|
||||
coord_t BeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
return optimal_width * bead_count;
|
||||
}
|
||||
|
||||
coord_t BeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
const coord_t lower_ideal_width = getOptimalThickness(lower_bead_count);
|
||||
const coord_t higher_ideal_width = getOptimalThickness(lower_bead_count + 1);
|
||||
const double threshold = lower_bead_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold;
|
||||
return lower_ideal_width + threshold * (higher_ideal_width - lower_ideal_width);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
117
src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp
Normal file
117
src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef BEADING_STRATEGY_H
|
||||
#define BEADING_STRATEGY_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../../libslic3r.h"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
template<typename T> constexpr T pi_div(const T div) { return static_cast<T>(M_PI) / div; }
|
||||
|
||||
/*!
|
||||
* Mostly virtual base class template.
|
||||
*
|
||||
* Strategy for covering a given (constant) horizontal model thickness with a number of beads.
|
||||
*
|
||||
* The beads may have different widths.
|
||||
*
|
||||
* TODO: extend with printing order?
|
||||
*/
|
||||
class BeadingStrategy
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* The beading for a given horizontal model thickness.
|
||||
*/
|
||||
struct Beading
|
||||
{
|
||||
coord_t total_thickness;
|
||||
std::vector<coord_t> bead_widths; //! The line width of each bead from the outer inset inward
|
||||
std::vector<coord_t> toolpath_locations; //! The distance of the toolpath location of each bead from the outline
|
||||
coord_t left_over; //! The distance not covered by any bead; gap area.
|
||||
};
|
||||
|
||||
BeadingStrategy(coord_t optimal_width, double wall_split_middle_threshold, double wall_add_middle_threshold, coord_t default_transition_length, float transitioning_angle = pi_div(3));
|
||||
|
||||
BeadingStrategy(const BeadingStrategy &other);
|
||||
|
||||
virtual ~BeadingStrategy() = default;
|
||||
|
||||
/*!
|
||||
* Retrieve the bead widths with which to cover a given thickness.
|
||||
*
|
||||
* Requirement: Given a constant \p bead_count the output of each bead width must change gradually along with the \p thickness.
|
||||
*
|
||||
* \note The \p bead_count might be different from the \ref BeadingStrategy::optimal_bead_count
|
||||
*/
|
||||
virtual Beading compute(coord_t thickness, coord_t bead_count) const = 0;
|
||||
|
||||
/*!
|
||||
* The ideal thickness for a given \param bead_count
|
||||
*/
|
||||
virtual coord_t getOptimalThickness(coord_t bead_count) const;
|
||||
|
||||
/*!
|
||||
* The model thickness at which \ref BeadingStrategy::optimal_bead_count transitions from \p lower_bead_count to \p lower_bead_count + 1
|
||||
*/
|
||||
virtual coord_t getTransitionThickness(coord_t lower_bead_count) const;
|
||||
|
||||
/*!
|
||||
* The number of beads should we ideally usefor a given model thickness
|
||||
*/
|
||||
virtual coord_t getOptimalBeadCount(coord_t thickness) const = 0;
|
||||
|
||||
/*!
|
||||
* The length of the transitioning region along the marked / significant regions of the skeleton.
|
||||
*
|
||||
* Transitions are used to smooth out the jumps in integer bead count; the jumps turn into ramps with some incline defined by their length.
|
||||
*/
|
||||
virtual coord_t getTransitioningLength(coord_t lower_bead_count) const;
|
||||
|
||||
/*!
|
||||
* The fraction of the transition length to put between the lower end of the transition and the point where the unsmoothed bead count jumps.
|
||||
*
|
||||
* Transitions are used to smooth out the jumps in integer bead count; the jumps turn into ramps which could be positioned relative to the jump location.
|
||||
*/
|
||||
virtual float getTransitionAnchorPos(coord_t lower_bead_count) const;
|
||||
|
||||
/*!
|
||||
* Get the locations in a bead count region where \ref BeadingStrategy::compute exhibits a bend in the widths.
|
||||
* Ordered from lower thickness to higher.
|
||||
*
|
||||
* This is used to insert extra support bones into the skeleton, so that the resulting beads in long trapezoids don't linearly change between the two ends.
|
||||
*/
|
||||
virtual std::vector<coord_t> getNonlinearThicknesses(coord_t lower_bead_count) const;
|
||||
|
||||
virtual std::string toString() const;
|
||||
|
||||
double getSplitMiddleThreshold() const;
|
||||
double getTransitioningAngle() const;
|
||||
|
||||
protected:
|
||||
std::string name;
|
||||
|
||||
coord_t optimal_width; //! Optimal bead width, nominal width off the walls in 'ideal' circumstances.
|
||||
|
||||
double wall_split_middle_threshold; //! Threshold when a middle wall should be split into two, as a ratio of the optimal wall width.
|
||||
|
||||
double wall_add_middle_threshold; //! Threshold when a new middle wall should be added between an even number of walls, as a ratio of the optimal wall width.
|
||||
|
||||
coord_t default_transition_length; //! The length of the region to smoothly transfer between bead counts
|
||||
|
||||
/*!
|
||||
* The maximum angle between outline segments smaller than which we are going to add transitions
|
||||
* Equals 180 - the "limit bisector angle" from the paper
|
||||
*/
|
||||
double transitioning_angle;
|
||||
};
|
||||
|
||||
using BeadingStrategyPtr = std::unique_ptr<BeadingStrategy>;
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // BEADING_STRATEGY_H
|
||||
@@ -0,0 +1,52 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "BeadingStrategyFactory.hpp"
|
||||
|
||||
#include "LimitedBeadingStrategy.hpp"
|
||||
#include "WideningBeadingStrategy.hpp"
|
||||
#include "DistributedBeadingStrategy.hpp"
|
||||
#include "RedistributeBeadingStrategy.hpp"
|
||||
#include "OuterWallInsetBeadingStrategy.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
BeadingStrategyPtr BeadingStrategyFactory::makeStrategy(
|
||||
const coord_t preferred_bead_width_outer,
|
||||
const coord_t preferred_bead_width_inner,
|
||||
const coord_t preferred_transition_length,
|
||||
const float transitioning_angle,
|
||||
const bool print_thin_walls,
|
||||
const coord_t min_bead_width,
|
||||
const coord_t min_feature_size,
|
||||
const double wall_split_middle_threshold,
|
||||
const double wall_add_middle_threshold,
|
||||
const coord_t max_bead_count,
|
||||
const coord_t outer_wall_offset,
|
||||
const int inward_distributed_center_wall_count,
|
||||
const double minimum_variable_line_ratio
|
||||
)
|
||||
{
|
||||
BeadingStrategyPtr ret = std::make_unique<DistributedBeadingStrategy>(preferred_bead_width_inner, preferred_transition_length, transitioning_angle, wall_split_middle_threshold, wall_add_middle_threshold, inward_distributed_center_wall_count);
|
||||
BOOST_LOG_TRIVIAL(debug) << "Applying the Redistribute meta-strategy with outer-wall width = " << preferred_bead_width_outer << ", inner-wall width = " << preferred_bead_width_inner << ".";
|
||||
ret = std::make_unique<RedistributeBeadingStrategy>(preferred_bead_width_outer, minimum_variable_line_ratio, std::move(ret));
|
||||
|
||||
if (print_thin_walls) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Applying the Widening Beading meta-strategy with minimum input width " << min_feature_size << " and minimum output width " << min_bead_width << ".";
|
||||
ret = std::make_unique<WideningBeadingStrategy>(std::move(ret), min_feature_size, min_bead_width);
|
||||
}
|
||||
if (outer_wall_offset > 0) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Applying the OuterWallOffset meta-strategy with offset = " << outer_wall_offset << ".";
|
||||
ret = std::make_unique<OuterWallInsetBeadingStrategy>(outer_wall_offset, std::move(ret));
|
||||
}
|
||||
|
||||
//Apply the LimitedBeadingStrategy last, since that adds a 0-width marker wall which other beading strategies shouldn't touch.
|
||||
BOOST_LOG_TRIVIAL(debug) << "Applying the Limited Beading meta-strategy with maximum bead count = " << max_bead_count << ".";
|
||||
ret = std::make_unique<LimitedBeadingStrategy>(max_bead_count, std::move(ret));
|
||||
return ret;
|
||||
}
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef BEADING_STRATEGY_FACTORY_H
|
||||
#define BEADING_STRATEGY_FACTORY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
#include "../../Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
class BeadingStrategyFactory
|
||||
{
|
||||
public:
|
||||
static BeadingStrategyPtr makeStrategy
|
||||
(
|
||||
coord_t preferred_bead_width_outer = scaled<coord_t>(0.0005),
|
||||
coord_t preferred_bead_width_inner = scaled<coord_t>(0.0005),
|
||||
coord_t preferred_transition_length = scaled<coord_t>(0.0004),
|
||||
float transitioning_angle = M_PI / 4.0,
|
||||
bool print_thin_walls = false,
|
||||
coord_t min_bead_width = 0,
|
||||
coord_t min_feature_size = 0,
|
||||
double wall_split_middle_threshold = 0.5,
|
||||
double wall_add_middle_threshold = 0.5,
|
||||
coord_t max_bead_count = 0,
|
||||
coord_t outer_wall_offset = 0,
|
||||
int inward_distributed_center_wall_count = 2,
|
||||
double minimum_variable_line_width = 0.5
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // BEADING_STRATEGY_FACTORY_H
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
#include <numeric>
|
||||
#include "DistributedBeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
DistributedBeadingStrategy::DistributedBeadingStrategy(const coord_t optimal_width,
|
||||
const coord_t default_transition_length,
|
||||
const double transitioning_angle,
|
||||
const double wall_split_middle_threshold,
|
||||
const double wall_add_middle_threshold,
|
||||
const int distribution_radius)
|
||||
: BeadingStrategy(optimal_width, wall_split_middle_threshold, wall_add_middle_threshold, default_transition_length, transitioning_angle)
|
||||
{
|
||||
if(distribution_radius >= 2)
|
||||
one_over_distribution_radius_squared = 1.0f / (distribution_radius - 1) * 1.0f / (distribution_radius - 1);
|
||||
else
|
||||
one_over_distribution_radius_squared = 1.0f / 1 * 1.0f / 1;
|
||||
name = "DistributedBeadingStrategy";
|
||||
}
|
||||
|
||||
DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(const coord_t thickness, const coord_t bead_count) const
|
||||
{
|
||||
Beading ret;
|
||||
|
||||
ret.total_thickness = thickness;
|
||||
if (bead_count > 2) {
|
||||
const coord_t to_be_divided = thickness - bead_count * optimal_width;
|
||||
const float middle = static_cast<float>(bead_count - 1) / 2;
|
||||
|
||||
const auto getWeight = [middle, this](coord_t bead_idx) {
|
||||
const float dev_from_middle = bead_idx - middle;
|
||||
return std::max(0.0f, 1.0f - one_over_distribution_radius_squared * dev_from_middle * dev_from_middle);
|
||||
};
|
||||
|
||||
std::vector<float> weights;
|
||||
weights.resize(bead_count);
|
||||
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++)
|
||||
weights[bead_idx] = getWeight(bead_idx);
|
||||
|
||||
const float total_weight = std::accumulate(weights.cbegin(), weights.cend(), 0.f);
|
||||
coord_t accumulated_width = 0;
|
||||
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++) {
|
||||
const float weight_fraction = weights[bead_idx] / total_weight;
|
||||
const coord_t splitup_left_over_weight = to_be_divided * weight_fraction;
|
||||
const coord_t width = (bead_idx == bead_count - 1) ? thickness - accumulated_width : optimal_width + splitup_left_over_weight;
|
||||
|
||||
// Be aware that toolpath_locations is computed by dividing the width by 2, so toolpath_locations
|
||||
// could be off by 1 because of rounding errors.
|
||||
if (bead_idx == 0)
|
||||
ret.toolpath_locations.emplace_back(width / 2);
|
||||
else
|
||||
ret.toolpath_locations.emplace_back(ret.toolpath_locations.back() + (ret.bead_widths.back() + width) / 2);
|
||||
ret.bead_widths.emplace_back(width);
|
||||
accumulated_width += width;
|
||||
}
|
||||
ret.left_over = 0;
|
||||
assert((accumulated_width + ret.left_over) == thickness);
|
||||
} else if (bead_count == 2) {
|
||||
const coord_t outer_width = thickness / 2;
|
||||
ret.bead_widths.emplace_back(outer_width);
|
||||
ret.bead_widths.emplace_back(outer_width);
|
||||
ret.toolpath_locations.emplace_back(outer_width / 2);
|
||||
ret.toolpath_locations.emplace_back(thickness - outer_width / 2);
|
||||
ret.left_over = 0;
|
||||
} else if (bead_count == 1) {
|
||||
const coord_t outer_width = thickness;
|
||||
ret.bead_widths.emplace_back(outer_width);
|
||||
ret.toolpath_locations.emplace_back(outer_width / 2);
|
||||
ret.left_over = 0;
|
||||
} else {
|
||||
ret.left_over = thickness;
|
||||
}
|
||||
|
||||
assert(([&ret = std::as_const(ret), thickness]() -> bool {
|
||||
coord_t total_bead_width = 0;
|
||||
for (const coord_t &bead_width : ret.bead_widths)
|
||||
total_bead_width += bead_width;
|
||||
return (total_bead_width + ret.left_over) == thickness;
|
||||
}()));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
coord_t DistributedBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
const coord_t naive_count = thickness / optimal_width; // How many lines we can fit in for sure.
|
||||
const coord_t remainder = thickness - naive_count * optimal_width; // Space left after fitting that many lines.
|
||||
const coord_t minimum_line_width = optimal_width * (naive_count % 2 == 1 ? wall_split_middle_threshold : wall_add_middle_threshold);
|
||||
return naive_count + (remainder >= minimum_line_width); // If there's enough space, fit an extra one.
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef DISTRIBUTED_BEADING_STRATEGY_H
|
||||
#define DISTRIBUTED_BEADING_STRATEGY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* This beading strategy chooses a wall count that would make the line width
|
||||
* deviate the least from the optimal line width, and then distributes the lines
|
||||
* evenly among the thickness available.
|
||||
*/
|
||||
class DistributedBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
protected:
|
||||
float one_over_distribution_radius_squared; // (1 / distribution_radius)^2
|
||||
|
||||
public:
|
||||
/*!
|
||||
* \param distribution_radius the radius (in number of beads) over which to distribute the discrepancy between the feature size and the optimal thickness
|
||||
*/
|
||||
DistributedBeadingStrategy(coord_t optimal_width,
|
||||
coord_t default_transition_length,
|
||||
double transitioning_angle,
|
||||
double wall_split_middle_threshold,
|
||||
double wall_add_middle_threshold,
|
||||
int distribution_radius);
|
||||
|
||||
~DistributedBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // DISTRIBUTED_BEADING_STRATEGY_H
|
||||
126
src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp
Normal file
126
src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <cassert>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "LimitedBeadingStrategy.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
std::string LimitedBeadingStrategy::toString() const
|
||||
{
|
||||
return std::string("LimitedBeadingStrategy+") + parent->toString();
|
||||
}
|
||||
|
||||
coord_t LimitedBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitioningLength(lower_bead_count);
|
||||
}
|
||||
|
||||
float LimitedBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitionAnchorPos(lower_bead_count);
|
||||
}
|
||||
|
||||
LimitedBeadingStrategy::LimitedBeadingStrategy(const coord_t max_bead_count, BeadingStrategyPtr parent)
|
||||
: BeadingStrategy(*parent)
|
||||
, max_bead_count(max_bead_count)
|
||||
, parent(std::move(parent))
|
||||
{
|
||||
if (max_bead_count % 2 == 1)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "LimitedBeadingStrategy with odd bead count is odd indeed!";
|
||||
}
|
||||
}
|
||||
|
||||
LimitedBeadingStrategy::Beading LimitedBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
if (bead_count <= max_bead_count)
|
||||
{
|
||||
Beading ret = parent->compute(thickness, bead_count);
|
||||
bead_count = ret.toolpath_locations.size();
|
||||
|
||||
if (bead_count % 2 == 0 && bead_count == max_bead_count)
|
||||
{
|
||||
const coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1];
|
||||
const coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1];
|
||||
ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2);
|
||||
ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
assert(bead_count == max_bead_count + 1);
|
||||
if(bead_count != max_bead_count + 1)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "Too many beads! " << bead_count << " != " << max_bead_count + 1;
|
||||
}
|
||||
|
||||
coord_t optimal_thickness = parent->getOptimalThickness(max_bead_count);
|
||||
Beading ret = parent->compute(optimal_thickness, max_bead_count);
|
||||
bead_count = ret.toolpath_locations.size();
|
||||
ret.left_over += thickness - ret.total_thickness;
|
||||
ret.total_thickness = thickness;
|
||||
|
||||
// Enforce symmetry
|
||||
if (bead_count % 2 == 1) {
|
||||
ret.toolpath_locations[bead_count / 2] = thickness / 2;
|
||||
ret.bead_widths[bead_count / 2] = thickness - optimal_thickness;
|
||||
}
|
||||
for (coord_t bead_idx = 0; bead_idx < (bead_count + 1) / 2; bead_idx++)
|
||||
ret.toolpath_locations[bead_count - 1 - bead_idx] = thickness - ret.toolpath_locations[bead_idx];
|
||||
|
||||
//Create a "fake" inner wall with 0 width to indicate the edge of the walled area.
|
||||
//This wall can then be used by other structures to e.g. fill the infill area adjacent to the variable-width walls.
|
||||
coord_t innermost_toolpath_location = ret.toolpath_locations[max_bead_count / 2 - 1];
|
||||
coord_t innermost_toolpath_width = ret.bead_widths[max_bead_count / 2 - 1];
|
||||
ret.toolpath_locations.insert(ret.toolpath_locations.begin() + max_bead_count / 2, innermost_toolpath_location + innermost_toolpath_width / 2);
|
||||
ret.bead_widths.insert(ret.bead_widths.begin() + max_bead_count / 2, 0);
|
||||
|
||||
//Symmetry on both sides. Symmetry is guaranteed since this code is stopped early if the bead_count <= max_bead_count, and never reaches this point then.
|
||||
const size_t opposite_bead = bead_count - (max_bead_count / 2 - 1);
|
||||
innermost_toolpath_location = ret.toolpath_locations[opposite_bead];
|
||||
innermost_toolpath_width = ret.bead_widths[opposite_bead];
|
||||
ret.toolpath_locations.insert(ret.toolpath_locations.begin() + opposite_bead, innermost_toolpath_location - innermost_toolpath_width / 2);
|
||||
ret.bead_widths.insert(ret.bead_widths.begin() + opposite_bead, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
coord_t LimitedBeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
if (bead_count <= max_bead_count)
|
||||
return parent->getOptimalThickness(bead_count);
|
||||
assert(false);
|
||||
return scaled<coord_t>(1000.); // 1 meter (Cura was returning 10 meter)
|
||||
}
|
||||
|
||||
coord_t LimitedBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
if (lower_bead_count < max_bead_count)
|
||||
return parent->getTransitionThickness(lower_bead_count);
|
||||
|
||||
if (lower_bead_count == max_bead_count)
|
||||
return parent->getOptimalThickness(lower_bead_count + 1) - scaled<coord_t>(0.01);
|
||||
|
||||
assert(false);
|
||||
return scaled<coord_t>(900.); // 0.9 meter;
|
||||
}
|
||||
|
||||
coord_t LimitedBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
coord_t parent_bead_count = parent->getOptimalBeadCount(thickness);
|
||||
if (parent_bead_count <= max_bead_count) {
|
||||
return parent->getOptimalBeadCount(thickness);
|
||||
} else if (parent_bead_count == max_bead_count + 1) {
|
||||
if (thickness < parent->getOptimalThickness(max_bead_count + 1) - scaled<coord_t>(0.01))
|
||||
return max_bead_count;
|
||||
else
|
||||
return max_bead_count + 1;
|
||||
}
|
||||
else return max_bead_count + 1;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,49 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef LIMITED_BEADING_STRATEGY_H
|
||||
#define LIMITED_BEADING_STRATEGY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* This is a meta-strategy that can be applied on top of any other beading
|
||||
* strategy, which limits the thickness of the walls to the thickness that the
|
||||
* lines can reasonably print.
|
||||
*
|
||||
* The width of the wall is limited to the maximum number of contours times the
|
||||
* maximum width of each of these contours.
|
||||
*
|
||||
* If the width of the wall gets limited, this strategy outputs one additional
|
||||
* bead with 0 width. This bead is used to denote the limits of the walled area.
|
||||
* Other structures can then use this border to align their structures to, such
|
||||
* as to create correctly overlapping infill or skin, or to align the infill
|
||||
* pattern to any extra infill walls.
|
||||
*/
|
||||
class LimitedBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
public:
|
||||
LimitedBeadingStrategy(coord_t max_bead_count, BeadingStrategyPtr parent);
|
||||
|
||||
~LimitedBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
coord_t getOptimalThickness(coord_t bead_count) const override;
|
||||
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
|
||||
|
||||
float getTransitionAnchorPos(coord_t lower_bead_count) const override;
|
||||
|
||||
protected:
|
||||
const coord_t max_bead_count;
|
||||
const BeadingStrategyPtr parent;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // LIMITED_DISTRIBUTED_BEADING_STRATEGY_H
|
||||
@@ -0,0 +1,59 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "OuterWallInsetBeadingStrategy.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
OuterWallInsetBeadingStrategy::OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent)
|
||||
: BeadingStrategy(*parent), parent(std::move(parent)), outer_wall_offset(outer_wall_offset)
|
||||
{
|
||||
name = "OuterWallOfsetBeadingStrategy";
|
||||
}
|
||||
|
||||
coord_t OuterWallInsetBeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
return parent->getOptimalThickness(bead_count);
|
||||
}
|
||||
|
||||
coord_t OuterWallInsetBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitionThickness(lower_bead_count);
|
||||
}
|
||||
|
||||
coord_t OuterWallInsetBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
return parent->getOptimalBeadCount(thickness);
|
||||
}
|
||||
|
||||
coord_t OuterWallInsetBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitioningLength(lower_bead_count);
|
||||
}
|
||||
|
||||
std::string OuterWallInsetBeadingStrategy::toString() const
|
||||
{
|
||||
return std::string("OuterWallOfsetBeadingStrategy+") + parent->toString();
|
||||
}
|
||||
|
||||
BeadingStrategy::Beading OuterWallInsetBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
Beading ret = parent->compute(thickness, bead_count);
|
||||
|
||||
// Actual count and thickness as represented by extant walls. Don't count any potential zero-width 'signaling' walls.
|
||||
bead_count = std::count_if(ret.bead_widths.begin(), ret.bead_widths.end(), [](const coord_t width) { return width > 0; });
|
||||
|
||||
// No need to apply any inset if there is just a single wall.
|
||||
if (bead_count < 2)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Actually move the outer wall inside. Ensure that the outer wall never goes beyond the middle line.
|
||||
ret.toolpath_locations[0] = std::min(ret.toolpath_locations[0] + outer_wall_offset, thickness / 2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,35 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef OUTER_WALL_INSET_BEADING_STRATEGY_H
|
||||
#define OUTER_WALL_INSET_BEADING_STRATEGY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
/*
|
||||
* This is a meta strategy that allows for the outer wall to be inset towards the inside of the model.
|
||||
*/
|
||||
class OuterWallInsetBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
public:
|
||||
OuterWallInsetBeadingStrategy(coord_t outer_wall_offset, BeadingStrategyPtr parent);
|
||||
|
||||
~OuterWallInsetBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
|
||||
coord_t getOptimalThickness(coord_t bead_count) const override;
|
||||
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
|
||||
|
||||
std::string toString() const override;
|
||||
|
||||
private:
|
||||
BeadingStrategyPtr parent;
|
||||
coord_t outer_wall_offset;
|
||||
};
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // OUTER_WALL_INSET_BEADING_STRATEGY_H
|
||||
@@ -0,0 +1,97 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "RedistributeBeadingStrategy.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
RedistributeBeadingStrategy::RedistributeBeadingStrategy(const coord_t optimal_width_outer,
|
||||
const double minimum_variable_line_ratio,
|
||||
BeadingStrategyPtr parent)
|
||||
: BeadingStrategy(*parent)
|
||||
, parent(std::move(parent))
|
||||
, optimal_width_outer(optimal_width_outer)
|
||||
, minimum_variable_line_ratio(minimum_variable_line_ratio)
|
||||
{
|
||||
name = "RedistributeBeadingStrategy";
|
||||
}
|
||||
|
||||
coord_t RedistributeBeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
const coord_t inner_bead_count = std::max(static_cast<coord_t>(0), bead_count - 2);
|
||||
const coord_t outer_bead_count = bead_count - inner_bead_count;
|
||||
return parent->getOptimalThickness(inner_bead_count) + optimal_width_outer * outer_bead_count;
|
||||
}
|
||||
|
||||
coord_t RedistributeBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
switch (lower_bead_count) {
|
||||
case 0: return minimum_variable_line_ratio * optimal_width_outer;
|
||||
case 1: return (1.0 + parent->getSplitMiddleThreshold()) * optimal_width_outer;
|
||||
default: return parent->getTransitionThickness(lower_bead_count - 2) + 2 * optimal_width_outer;
|
||||
}
|
||||
}
|
||||
|
||||
coord_t RedistributeBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
if (thickness < minimum_variable_line_ratio * optimal_width_outer)
|
||||
return 0;
|
||||
if (thickness <= 2 * optimal_width_outer)
|
||||
return thickness > (1.0 + parent->getSplitMiddleThreshold()) * optimal_width_outer ? 2 : 1;
|
||||
return parent->getOptimalBeadCount(thickness - 2 * optimal_width_outer) + 2;
|
||||
}
|
||||
|
||||
coord_t RedistributeBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitioningLength(lower_bead_count);
|
||||
}
|
||||
|
||||
float RedistributeBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitionAnchorPos(lower_bead_count);
|
||||
}
|
||||
|
||||
std::string RedistributeBeadingStrategy::toString() const
|
||||
{
|
||||
return std::string("RedistributeBeadingStrategy+") + parent->toString();
|
||||
}
|
||||
|
||||
BeadingStrategy::Beading RedistributeBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
Beading ret;
|
||||
|
||||
// Take care of all situations in which no lines are actually produced:
|
||||
if (bead_count == 0 || thickness < minimum_variable_line_ratio * optimal_width_outer) {
|
||||
ret.left_over = thickness;
|
||||
ret.total_thickness = thickness;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Compute the beadings of the inner walls, if any:
|
||||
const coord_t inner_bead_count = bead_count - 2;
|
||||
const coord_t inner_thickness = thickness - 2 * optimal_width_outer;
|
||||
if (inner_bead_count > 0 && inner_thickness > 0) {
|
||||
ret = parent->compute(inner_thickness, inner_bead_count);
|
||||
for (auto &toolpath_location : ret.toolpath_locations) toolpath_location += optimal_width_outer;
|
||||
}
|
||||
|
||||
// Insert the outer wall(s) around the previously computed inner wall(s), which may be empty:
|
||||
const coord_t actual_outer_thickness = bead_count > 2 ? std::min(thickness / 2, optimal_width_outer) : thickness / bead_count;
|
||||
ret.bead_widths.insert(ret.bead_widths.begin(), actual_outer_thickness);
|
||||
ret.toolpath_locations.insert(ret.toolpath_locations.begin(), actual_outer_thickness / 2);
|
||||
if (bead_count > 1) {
|
||||
ret.bead_widths.push_back(actual_outer_thickness);
|
||||
ret.toolpath_locations.push_back(thickness - actual_outer_thickness / 2);
|
||||
}
|
||||
|
||||
// Ensure correct total and left over thickness.
|
||||
ret.total_thickness = thickness;
|
||||
ret.left_over = thickness - std::accumulate(ret.bead_widths.cbegin(), ret.bead_widths.cend(), static_cast<coord_t>(0));
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,56 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef REDISTRIBUTE_DISTRIBUTED_BEADING_STRATEGY_H
|
||||
#define REDISTRIBUTE_DISTRIBUTED_BEADING_STRATEGY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
/*!
|
||||
* A meta-beading-strategy that takes outer and inner wall widths into account.
|
||||
*
|
||||
* The outer wall will try to keep a constant width by only applying the beading strategy on the inner walls. This
|
||||
* ensures that this outer wall doesn't react to changes happening to inner walls. It will limit print artifacts on
|
||||
* the surface of the print. Although this strategy technically deviates from the original philosophy of the paper.
|
||||
* It will generally results in better prints because of a smoother motion and less variation in extrusion width in
|
||||
* the outer walls.
|
||||
*
|
||||
* If the thickness of the model is less then two times the optimal outer wall width and once the minimum inner wall
|
||||
* width it will keep the minimum inner wall at a minimum constant and vary the outer wall widths symmetrical. Until
|
||||
* The thickness of the model is that of at least twice the optimal outer wall width it will then use two
|
||||
* symmetrical outer walls only. Until it transitions into a single outer wall. These last scenario's are always
|
||||
* symmetrical in nature, disregarding the user specified strategy.
|
||||
*/
|
||||
class RedistributeBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* /param optimal_width_outer Outer wall width, guaranteed to be the actual (save rounding errors) at a
|
||||
* bead count if the parent strategies' optimum bead width is a weighted
|
||||
* average of the outer and inner walls at that bead count.
|
||||
* /param minimum_variable_line_ratio Minimum factor that the variable line might deviate from the optimal width.
|
||||
*/
|
||||
RedistributeBeadingStrategy(coord_t optimal_width_outer, double minimum_variable_line_ratio, BeadingStrategyPtr parent);
|
||||
|
||||
~RedistributeBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
|
||||
coord_t getOptimalThickness(coord_t bead_count) const override;
|
||||
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
|
||||
float getTransitionAnchorPos(coord_t lower_bead_count) const override;
|
||||
|
||||
std::string toString() const override;
|
||||
|
||||
protected:
|
||||
BeadingStrategyPtr parent;
|
||||
coord_t optimal_width_outer;
|
||||
double minimum_variable_line_ratio;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // INWARD_DISTRIBUTED_BEADING_STRATEGY_H
|
||||
@@ -0,0 +1,82 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "WideningBeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
WideningBeadingStrategy::WideningBeadingStrategy(BeadingStrategyPtr parent, const coord_t min_input_width, const coord_t min_output_width)
|
||||
: BeadingStrategy(*parent)
|
||||
, parent(std::move(parent))
|
||||
, min_input_width(min_input_width)
|
||||
, min_output_width(min_output_width)
|
||||
{
|
||||
}
|
||||
|
||||
std::string WideningBeadingStrategy::toString() const
|
||||
{
|
||||
return std::string("Widening+") + parent->toString();
|
||||
}
|
||||
|
||||
WideningBeadingStrategy::Beading WideningBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
{
|
||||
if (thickness < optimal_width) {
|
||||
Beading ret;
|
||||
ret.total_thickness = thickness;
|
||||
if (thickness >= min_input_width)
|
||||
{
|
||||
ret.bead_widths.emplace_back(std::max(thickness, min_output_width));
|
||||
ret.toolpath_locations.emplace_back(thickness / 2);
|
||||
} else {
|
||||
ret.left_over = thickness;
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return parent->compute(thickness, bead_count);
|
||||
}
|
||||
}
|
||||
|
||||
coord_t WideningBeadingStrategy::getOptimalThickness(coord_t bead_count) const
|
||||
{
|
||||
return parent->getOptimalThickness(bead_count);
|
||||
}
|
||||
|
||||
coord_t WideningBeadingStrategy::getTransitionThickness(coord_t lower_bead_count) const
|
||||
{
|
||||
if (lower_bead_count == 0)
|
||||
return min_input_width;
|
||||
else
|
||||
return parent->getTransitionThickness(lower_bead_count);
|
||||
}
|
||||
|
||||
coord_t WideningBeadingStrategy::getOptimalBeadCount(coord_t thickness) const
|
||||
{
|
||||
if (thickness < min_input_width)
|
||||
return 0;
|
||||
coord_t ret = parent->getOptimalBeadCount(thickness);
|
||||
if (thickness >= min_input_width && ret < 1)
|
||||
return 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
coord_t WideningBeadingStrategy::getTransitioningLength(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitioningLength(lower_bead_count);
|
||||
}
|
||||
|
||||
float WideningBeadingStrategy::getTransitionAnchorPos(coord_t lower_bead_count) const
|
||||
{
|
||||
return parent->getTransitionAnchorPos(lower_bead_count);
|
||||
}
|
||||
|
||||
std::vector<coord_t> WideningBeadingStrategy::getNonlinearThicknesses(coord_t lower_bead_count) const
|
||||
{
|
||||
std::vector<coord_t> ret;
|
||||
ret.emplace_back(min_output_width);
|
||||
std::vector<coord_t> pret = parent->getNonlinearThicknesses(lower_bead_count);
|
||||
ret.insert(ret.end(), pret.begin(), pret.end());
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
@@ -0,0 +1,46 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef WIDENING_BEADING_STRATEGY_H
|
||||
#define WIDENING_BEADING_STRATEGY_H
|
||||
|
||||
#include "BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* This is a meta-strategy that can be applied on any other beading strategy. If
|
||||
* the part is thinner than a single line, this strategy adjusts the part so
|
||||
* that it becomes the minimum thickness of one line.
|
||||
*
|
||||
* This way, tiny pieces that are smaller than a single line will still be
|
||||
* printed.
|
||||
*/
|
||||
class WideningBeadingStrategy : public BeadingStrategy
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Takes responsibility for deleting \param parent
|
||||
*/
|
||||
WideningBeadingStrategy(BeadingStrategyPtr parent, coord_t min_input_width, coord_t min_output_width);
|
||||
|
||||
~WideningBeadingStrategy() override = default;
|
||||
|
||||
Beading compute(coord_t thickness, coord_t bead_count) const override;
|
||||
coord_t getOptimalThickness(coord_t bead_count) const override;
|
||||
coord_t getTransitionThickness(coord_t lower_bead_count) const override;
|
||||
coord_t getOptimalBeadCount(coord_t thickness) const override;
|
||||
coord_t getTransitioningLength(coord_t lower_bead_count) const override;
|
||||
float getTransitionAnchorPos(coord_t lower_bead_count) const override;
|
||||
std::vector<coord_t> getNonlinearThicknesses(coord_t lower_bead_count) const override;
|
||||
std::string toString() const override;
|
||||
|
||||
protected:
|
||||
BeadingStrategyPtr parent;
|
||||
const coord_t min_input_width;
|
||||
const coord_t min_output_width;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // WIDENING_BEADING_STRATEGY_H
|
||||
2128
src/libslic3r/Arachne/SkeletalTrapezoidation.cpp
Normal file
2128
src/libslic3r/Arachne/SkeletalTrapezoidation.cpp
Normal file
File diff suppressed because it is too large
Load Diff
581
src/libslic3r/Arachne/SkeletalTrapezoidation.hpp
Normal file
581
src/libslic3r/Arachne/SkeletalTrapezoidation.hpp
Normal file
@@ -0,0 +1,581 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef SKELETAL_TRAPEZOIDATION_H
|
||||
#define SKELETAL_TRAPEZOIDATION_H
|
||||
|
||||
#include <boost/polygon/voronoi.hpp>
|
||||
|
||||
#include <memory> // smart pointers
|
||||
#include <utility> // pair
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "utils/HalfEdgeGraph.hpp"
|
||||
#include "utils/PolygonsSegmentIndex.hpp"
|
||||
#include "utils/ExtrusionJunction.hpp"
|
||||
#include "utils/ExtrusionLine.hpp"
|
||||
#include "SkeletalTrapezoidationEdge.hpp"
|
||||
#include "SkeletalTrapezoidationJoint.hpp"
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
#include "SkeletalTrapezoidationGraph.hpp"
|
||||
#include "../Geometry/Voronoi.hpp"
|
||||
|
||||
//#define ARACHNE_DEBUG
|
||||
//#define ARACHNE_DEBUG_VORONOI
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
using VD = Slic3r::Geometry::VoronoiDiagram;
|
||||
|
||||
/*!
|
||||
* Main class of the dynamic beading strategies.
|
||||
*
|
||||
* The input polygon region is decomposed into trapezoids and represented as a half-edge data-structure.
|
||||
*
|
||||
* We determine which edges are 'central' accordinding to the transitioning_angle of the beading strategy,
|
||||
* and determine the bead count for these central regions and apply them outward when generating toolpaths. [oversimplified]
|
||||
*
|
||||
* The method can be visually explained as generating the 3D union of cones surface on the outline polygons,
|
||||
* and changing the heights along central regions of that surface so that they are flat.
|
||||
* For more info, please consult the paper "A framework for adaptive width control of dense contour-parallel toolpaths in fused
|
||||
deposition modeling" by Kuipers et al.
|
||||
* This visual explanation aid explains the use of "upward", "lower" etc,
|
||||
* i.e. the radial distance and/or the bead count are used as heights of this visualization, there is no coordinate called 'Z'.
|
||||
*
|
||||
* TODO: split this class into two:
|
||||
* 1. Class for generating the decomposition and aux functions for performing updates
|
||||
* 2. Class for editing the structure for our purposes.
|
||||
*/
|
||||
class SkeletalTrapezoidation
|
||||
{
|
||||
using graph_t = SkeletalTrapezoidationGraph;
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
using Beading = BeadingStrategy::Beading;
|
||||
using BeadingPropagation = SkeletalTrapezoidationJoint::BeadingPropagation;
|
||||
using TransitionMiddle = SkeletalTrapezoidationEdge::TransitionMiddle;
|
||||
using TransitionEnd = SkeletalTrapezoidationEdge::TransitionEnd;
|
||||
|
||||
template<typename T>
|
||||
using ptr_vector_t = std::vector<std::shared_ptr<T>>;
|
||||
|
||||
double transitioning_angle; //!< How pointy a region should be before we apply the method. Equals 180* - limit_bisector_angle
|
||||
coord_t discretization_step_size; //!< approximate size of segments when parabolic VD edges get discretized (and vertex-vertex edges)
|
||||
coord_t transition_filter_dist; //!< Filter transition mids (i.e. anchors) closer together than this
|
||||
coord_t allowed_filter_deviation; //!< The allowed line width deviation induced by filtering
|
||||
coord_t beading_propagation_transition_dist; //!< When there are different beadings propagated from below and from above, use this transitioning distance
|
||||
static constexpr coord_t central_filter_dist = scaled<coord_t>(0.02); //!< Filter areas marked as 'central' smaller than this
|
||||
static constexpr coord_t snap_dist = scaled<coord_t>(0.02); //!< Generic arithmatic inaccuracy. Only used to determine whether a transition really needs to insert an extra edge.
|
||||
|
||||
/*!
|
||||
* The strategy to use to fill a certain shape with lines.
|
||||
*
|
||||
* Various BeadingStrategies are available that differ in which lines get to
|
||||
* print at their optimal width, where the play is being compensated, and
|
||||
* how the joints are handled where we transition to different numbers of
|
||||
* lines.
|
||||
*/
|
||||
const BeadingStrategy& beading_strategy;
|
||||
|
||||
public:
|
||||
using Segment = PolygonsSegmentIndex;
|
||||
using NodeSet = ankerl::unordered_dense::set<node_t *>;
|
||||
using EdgeSet = ankerl::unordered_dense::set<edge_t *>;
|
||||
using EdgeMap = ankerl::unordered_dense::map<const VD::edge_type *, edge_t *>;
|
||||
using NodeMap = ankerl::unordered_dense::map<const VD::vertex_type *, node_t *>;
|
||||
|
||||
/*!
|
||||
* Construct a new trapezoidation problem to solve.
|
||||
* \param polys The shapes to fill with walls.
|
||||
* \param beading_strategy The strategy to use to fill these shapes.
|
||||
* \param transitioning_angle Where we transition to a different number of
|
||||
* walls, how steep should this transition be? A lower angle means that the
|
||||
* transition will be longer.
|
||||
* \param discretization_step_size Since g-code can't represent smooth
|
||||
* transitions in line width, the line width must change with discretized
|
||||
* steps. This indicates how long the line segments between those steps will
|
||||
* be.
|
||||
* \param transition_filter_dist The minimum length of transitions.
|
||||
* Transitions shorter than this will be considered for dissolution.
|
||||
* \param beading_propagation_transition_dist When there are different
|
||||
* beadings propagated from below and from above, use this transitioning
|
||||
* distance.
|
||||
*/
|
||||
SkeletalTrapezoidation(const Polygons& polys,
|
||||
const BeadingStrategy& beading_strategy,
|
||||
double transitioning_angle
|
||||
, coord_t discretization_step_size
|
||||
, coord_t transition_filter_dist
|
||||
, coord_t allowed_filter_deviation
|
||||
, coord_t beading_propagation_transition_dist);
|
||||
|
||||
/*!
|
||||
* A skeletal graph through the polygons that we need to fill with beads.
|
||||
*
|
||||
* The skeletal graph represents the medial axes through each part of the
|
||||
* polygons, and the lines from these medial axes towards each vertex of the
|
||||
* polygons. The graph can be used to see what the width is of a polygon in
|
||||
* each place and where the width transitions.
|
||||
*/
|
||||
graph_t graph;
|
||||
|
||||
/*!
|
||||
* Generate the paths that the printer must extrude, to print the outlines
|
||||
* in the input polygons.
|
||||
* \param filter_outermost_central_edges Some edges are "central" but still
|
||||
* touch the outside of the polygon. If enabled, don't treat these as
|
||||
* "central" but as if it's a obtuse corner. As a result, sharp corners will
|
||||
* no longer end in a single line but will just loop.
|
||||
*/
|
||||
void generateToolpaths(std::vector<VariableWidthLines> &generated_toolpaths, bool filter_outermost_central_edges = false);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
Polygons outline;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Auxiliary for referencing one transition along an edge which may contain multiple transitions
|
||||
*/
|
||||
struct TransitionMidRef
|
||||
{
|
||||
edge_t* edge;
|
||||
std::list<TransitionMiddle>::iterator transition_it;
|
||||
TransitionMidRef(edge_t* edge, std::list<TransitionMiddle>::iterator transition_it)
|
||||
: edge(edge)
|
||||
, transition_it(transition_it)
|
||||
{}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Compute the skeletal trapezoidation decomposition of the input shape.
|
||||
*
|
||||
* Compute the Voronoi Diagram (VD) and transfer all inside edges into our half-edge (HE) datastructure.
|
||||
*
|
||||
* The algorithm is currently a bit overcomplicated, because the discretization of parabolic edges is performed at the same time as all edges are being transfered,
|
||||
* which means that there is no one-to-one mapping from VD edges to HE edges.
|
||||
* Instead we map from a VD edge to the last HE edge.
|
||||
* This could be cimplified by recording the edges which should be discretized and discretizing the mafterwards.
|
||||
*
|
||||
* Another complication arises because the VD uses floating logic, which can result in zero-length segments after rounding to integers.
|
||||
* We therefore collapse edges and their whole cells afterwards.
|
||||
*/
|
||||
void constructFromPolygons(const Polygons& polys);
|
||||
|
||||
/*!
|
||||
* mapping each voronoi VD edge to the corresponding halfedge HE edge
|
||||
* In case the result segment is discretized, we map the VD edge to the *last* HE edge
|
||||
*/
|
||||
EdgeMap vd_edge_to_he_edge;
|
||||
NodeMap vd_node_to_he_node;
|
||||
node_t &makeNode(const VD::vertex_type &vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet.
|
||||
|
||||
/*!
|
||||
* (Eventual) returned 'polylines per index' result (from generateToolpaths):
|
||||
*/
|
||||
std::vector<VariableWidthLines> *p_generated_toolpaths;
|
||||
|
||||
/*!
|
||||
* Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges)
|
||||
* \p prev_edge serves as input and output. May be null as input.
|
||||
*/
|
||||
void transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector<Segment> &segments);
|
||||
|
||||
/*!
|
||||
* Discretize a Voronoi edge that represents the medial axis of a vertex-
|
||||
* line region or vertex-vertex region into small segments that can be
|
||||
* considered to have a straight medial axis and a linear line width
|
||||
* transition.
|
||||
*
|
||||
* The medial axis between a point and a line is a parabola. The rest of the
|
||||
* algorithm doesn't want to have to deal with parabola, so this discretises
|
||||
* the parabola into straight line segments. This is necessary if there is a
|
||||
* sharp inner corner (acts as a point) that comes close to a straight edge.
|
||||
*
|
||||
* The medial axis between a point and a point is a straight line segment.
|
||||
* However the distance from the medial axis to either of those points draws
|
||||
* a parabola as you go along the medial axis. That means that the resulting
|
||||
* line width along the medial axis would not be linearly increasing or
|
||||
* linearly decreasing, but needs to take the shape of a parabola. Instead,
|
||||
* we'll break this edge up into tiny line segments that can approximate the
|
||||
* parabola with tiny linear increases or decreases in line width.
|
||||
* \param segment The variable-width Voronoi edge to discretize.
|
||||
* \param points All vertices of the original Polygons to fill with beads.
|
||||
* \param segments All line segments of the original Polygons to fill with
|
||||
* beads.
|
||||
* \return A number of coordinates along the edge where the edge is broken
|
||||
* up into discrete pieces.
|
||||
*/
|
||||
Points discretize(const VD::edge_type& segment, const std::vector<Segment>& segments);
|
||||
|
||||
/*!
|
||||
* Compute the range of line segments that surround a cell of the skeletal
|
||||
* graph that belongs to a point on the medial axis.
|
||||
*
|
||||
* This should only be used on cells that belong to a corner in the skeletal
|
||||
* graph, e.g. triangular cells, not trapezoid cells.
|
||||
*
|
||||
* The resulting line segments is just the first and the last segment. They
|
||||
* are linked to the neighboring segments, so you can iterate over the
|
||||
* segments until you reach the last segment.
|
||||
* \param cell The cell to compute the range of line segments for.
|
||||
* \param[out] start_source_point The start point of the source segment of
|
||||
* this cell.
|
||||
* \param[out] end_source_point The end point of the source segment of this
|
||||
* cell.
|
||||
* \param[out] starting_vd_edge The edge of the Voronoi diagram where the
|
||||
* loop around the cell starts.
|
||||
* \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop
|
||||
* around the cell ends.
|
||||
* \param points All vertices of the input Polygons.
|
||||
* \param segments All edges of the input Polygons.
|
||||
* /return Whether the cell is inside of the polygon. If it's outside of the
|
||||
* polygon we should skip processing it altogether.
|
||||
*/
|
||||
static bool computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector<Segment> &segments);
|
||||
|
||||
/*!
|
||||
* For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two
|
||||
* That way we can reach both the quad_start and the quad_end from the [incident_edge] of the two new nodes
|
||||
* Otherwise if node.incident_edge = quad_start you couldnt reach quad_end.twin by normal iteration (i.e. it = it.twin.next)
|
||||
*/
|
||||
void separatePointyQuadEndNodes();
|
||||
|
||||
|
||||
// ^ init | v transitioning
|
||||
|
||||
void updateIsCentral(); // Update the "is_central" flag for each edge based on the transitioning_angle
|
||||
|
||||
/*!
|
||||
* Filter out small central areas.
|
||||
*
|
||||
* Only used to get rid of small edges which get marked as central because
|
||||
* of rounding errors because the region is so small.
|
||||
*/
|
||||
void filterCentral(coord_t max_length);
|
||||
|
||||
/*!
|
||||
* Filter central areas connected to starting_edge recursively.
|
||||
* \return Whether we should unmark this section marked as central, on the
|
||||
* way back out of the recursion.
|
||||
*/
|
||||
bool filterCentral(edge_t* starting_edge, coord_t traveled_dist, coord_t max_length);
|
||||
|
||||
/*!
|
||||
* Unmark the outermost edges directly connected to the outline, as not
|
||||
* being central.
|
||||
*
|
||||
* Only used to emulate some related literature.
|
||||
*
|
||||
* The paper shows that this function is bad for the stability of the framework.
|
||||
*/
|
||||
void filterOuterCentral();
|
||||
|
||||
/*!
|
||||
* Set bead count in central regions based on the optimal_bead_count of the
|
||||
* beading strategy.
|
||||
*/
|
||||
void updateBeadCount();
|
||||
|
||||
/*!
|
||||
* Add central regions and set bead counts where there is an end of the
|
||||
* central area and when traveling upward we get to another region with the
|
||||
* same bead count.
|
||||
*/
|
||||
void filterNoncentralRegions();
|
||||
|
||||
/*!
|
||||
* Add central regions and set bead counts for a particular edge and all of
|
||||
* its adjacent edges.
|
||||
*
|
||||
* Recursive subroutine for \ref filterNoncentralRegions().
|
||||
* \return Whether to set the bead count on the way back
|
||||
*/
|
||||
bool filterNoncentralRegions(edge_t* to_edge, coord_t bead_count, coord_t traveled_dist, coord_t max_dist);
|
||||
|
||||
/*!
|
||||
* Generate middle points of all transitions on edges.
|
||||
*
|
||||
* The transition middle points are saved in the graph itself. They are also
|
||||
* returned via the output parameter.
|
||||
* \param[out] edge_transitions A list of transitions that were generated.
|
||||
*/
|
||||
void generateTransitionMids(ptr_vector_t<std::list<TransitionMiddle>>& edge_transitions);
|
||||
|
||||
/*!
|
||||
* Removes some transition middle points.
|
||||
*
|
||||
* Transitions can be removed if there are multiple intersecting transitions
|
||||
* that are too close together. If transitions have opposite effects, both
|
||||
* are removed.
|
||||
*/
|
||||
void filterTransitionMids();
|
||||
|
||||
/*!
|
||||
* Merge transitions that are too close together.
|
||||
* \param edge_to_start Edge pointing to the node from which to start
|
||||
* traveling in all directions except along \p edge_to_start .
|
||||
* \param origin_transition The transition for which we are checking nearby
|
||||
* transitions.
|
||||
* \param traveled_dist The distance traveled before we came to
|
||||
* \p edge_to_start.to .
|
||||
* \param going_up Whether we are traveling in the upward direction as seen
|
||||
* from the \p origin_transition. If this doesn't align with the direction
|
||||
* according to the R diff on a consecutive edge we know there was a local
|
||||
* optimum.
|
||||
* \return Whether the origin transition should be dissolved.
|
||||
*/
|
||||
std::list<TransitionMidRef> dissolveNearbyTransitions(edge_t* edge_to_start, TransitionMiddle& origin_transition, coord_t traveled_dist, coord_t max_dist, bool going_up);
|
||||
|
||||
/*!
|
||||
* Spread a certain bead count over a region in the graph.
|
||||
* \param edge_to_start One edge of the region to spread the bead count in.
|
||||
* \param from_bead_count All edges with this bead count will be changed.
|
||||
* \param to_bead_count The new bead count for those edges.
|
||||
*/
|
||||
void dissolveBeadCountRegion(edge_t* edge_to_start, coord_t from_bead_count, coord_t to_bead_count);
|
||||
|
||||
/*!
|
||||
* Change the bead count if the given edge is at the end of a central
|
||||
* region.
|
||||
*
|
||||
* This is necessary to provide a transitioning bead count to the edges of a
|
||||
* central region to transition more smoothly from a high bead count in the
|
||||
* central region to a lower bead count at the edge.
|
||||
* \param edge_to_start One edge from a zone that needs to be filtered.
|
||||
* \param traveled_dist The distance along the edges we've traveled so far.
|
||||
* \param max_distance Don't filter beyond this range.
|
||||
* \param replacing_bead_count The new bead count for this region.
|
||||
* \return ``true`` if the bead count of this edge was changed.
|
||||
*/
|
||||
bool filterEndOfCentralTransition(edge_t* edge_to_start, coord_t traveled_dist, coord_t max_dist, coord_t replacing_bead_count);
|
||||
|
||||
/*!
|
||||
* Generate the endpoints of all transitions for all edges in the graph.
|
||||
* \param[out] edge_transition_ends The resulting transition endpoints.
|
||||
*/
|
||||
void generateAllTransitionEnds(ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends);
|
||||
|
||||
/*!
|
||||
* Also set the rest values at nodes in between the transition ends
|
||||
*/
|
||||
void applyTransitions(ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends);
|
||||
|
||||
/*!
|
||||
* Create extra edges along all edges, where it needs to transition from one
|
||||
* bead count to another.
|
||||
*
|
||||
* For example, if an edge of the graph goes from a bead count of 6 to a
|
||||
* bead count of 1, it needs to generate 5 places where the beads around
|
||||
* this line transition to a lower bead count. These are the "ribs". They
|
||||
* reach from the edge to the border of the polygon. Where the beads hit
|
||||
* those ribs the beads know to make a transition.
|
||||
*/
|
||||
void generateTransitioningRibs();
|
||||
|
||||
/*!
|
||||
* Generate the endpoints of a specific transition midpoint.
|
||||
* \param edge The edge to create transitions on.
|
||||
* \param mid_R The radius of the transition middle point.
|
||||
* \param transition_lower_bead_count The bead count at the lower end of the
|
||||
* transition.
|
||||
* \param[out] edge_transition_ends A list of endpoints to add the new
|
||||
* endpoints to.
|
||||
*/
|
||||
void generateTransitionEnds(edge_t& edge, coord_t mid_R, coord_t transition_lower_bead_count, ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends);
|
||||
|
||||
/*!
|
||||
* Compute a single endpoint of a transition.
|
||||
* \param edge The edge to generate the endpoint for.
|
||||
* \param start_pos The position where the transition starts.
|
||||
* \param end_pos The position where the transition ends on the other side.
|
||||
* \param transition_half_length The distance to the transition middle
|
||||
* point.
|
||||
* \param start_rest The gap between the start of the transition and the
|
||||
* starting endpoint, as ratio of the inner bead width at the high end of
|
||||
* the transition.
|
||||
* \param end_rest The gap between the end of the transition and the ending
|
||||
* endpoint, as ratio of the inner bead width at the high end of the
|
||||
* transition.
|
||||
* \param transition_lower_bead_count The bead count at the lower end of the
|
||||
* transition.
|
||||
* \param[out] edge_transition_ends The list to put the resulting endpoints
|
||||
* in.
|
||||
* \return Whether the given edge is going downward (i.e. towards a thinner
|
||||
* region of the polygon).
|
||||
*/
|
||||
bool generateTransitionEnd(edge_t& edge, coord_t start_pos, coord_t end_pos, coord_t transition_half_length, double start_rest, double end_rest, coord_t transition_lower_bead_count, ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends);
|
||||
|
||||
/*!
|
||||
* Determines whether an edge is going downwards or upwards in the graph.
|
||||
*
|
||||
* An edge is said to go "downwards" if it's going towards a narrower part
|
||||
* of the polygon. The notion of "downwards" comes from the conical
|
||||
* representation of the graph, where the polygon is filled with a cone of
|
||||
* maximum radius.
|
||||
*
|
||||
* This function works by recursively checking adjacent edges until the edge
|
||||
* is reached.
|
||||
* \param outgoing The edge to check.
|
||||
* \param traveled_dist The distance traversed so far.
|
||||
* \param transition_half_length The radius of the transition width.
|
||||
* \param lower_bead_count The bead count at the lower end of the edge.
|
||||
* \return ``true`` if this edge is going down, or ``false`` if it's going
|
||||
* up.
|
||||
*/
|
||||
bool isGoingDown(edge_t* outgoing, coord_t traveled_dist, coord_t transition_half_length, coord_t lower_bead_count) const;
|
||||
|
||||
/*!
|
||||
* Determines whether this edge marks the end of the central region.
|
||||
* \param edge The edge to check.
|
||||
* \return ``true`` if this edge goes from a central region to a non-central
|
||||
* region, or ``false`` in every other case (central to central, non-central
|
||||
* to non-central, non-central to central, or end-of-the-line).
|
||||
*/
|
||||
bool isEndOfCentral(const edge_t& edge) const;
|
||||
|
||||
/*!
|
||||
* Create extra ribs in the graph where the graph contains a parabolic arc
|
||||
* or a straight between two inner corners.
|
||||
*
|
||||
* There might be transitions there as the beads go through a narrow
|
||||
* bottleneck in the polygon.
|
||||
*/
|
||||
void generateExtraRibs();
|
||||
|
||||
// ^ transitioning ^
|
||||
|
||||
// v toolpath generation v
|
||||
|
||||
/*!
|
||||
* \param[out] segments the generated segments
|
||||
*/
|
||||
void generateSegments();
|
||||
|
||||
/*!
|
||||
* From a quad (a group of linked edges in one cell of the Voronoi), find
|
||||
* the edge pointing to the node that is furthest away from the border of the polygon.
|
||||
* \param quad_start_edge The first edge of the quad.
|
||||
* \return The edge of the quad that is furthest away from the border.
|
||||
*/
|
||||
edge_t* getQuadMaxRedgeTo(edge_t* quad_start_edge);
|
||||
|
||||
/*!
|
||||
* Propagate beading information from nodes that are closer to the edge
|
||||
* (low radius R) to nodes that are farther from the edge (high R).
|
||||
*
|
||||
* only propagate from nodes with beading info upward to nodes without beading info
|
||||
*
|
||||
* Edges are sorted by their radius, so that we can do a depth-first walk
|
||||
* without employing a recursive algorithm.
|
||||
*
|
||||
* In upward propagated beadings we store the distance traveled, so that we can merge these beadings with the downward propagated beadings in \ref propagateBeadingsDownward(.)
|
||||
*
|
||||
* \param upward_quad_mids all upward halfedges of the inner skeletal edges (not directly connected to the outline) sorted on their highest [distance_to_boundary]. Higher dist first.
|
||||
*/
|
||||
void propagateBeadingsUpward(std::vector<edge_t*>& upward_quad_mids, ptr_vector_t<BeadingPropagation>& node_beadings);
|
||||
|
||||
/*!
|
||||
* propagate beading info from higher R nodes to lower R nodes
|
||||
*
|
||||
* merge with upward propagated beadings if they are encountered
|
||||
*
|
||||
* don't transfer to nodes which lie on the outline polygon
|
||||
*
|
||||
* edges are sorted so that we can do a depth-first walk without employing a recursive algorithm
|
||||
*
|
||||
* \param upward_quad_mids all upward halfedges of the inner skeletal edges (not directly connected to the outline) sorted on their highest [distance_to_boundary]. Higher dist first.
|
||||
*/
|
||||
void propagateBeadingsDownward(std::vector<edge_t*>& upward_quad_mids, ptr_vector_t<BeadingPropagation>& node_beadings);
|
||||
|
||||
/*!
|
||||
* Subroutine of \ref propagateBeadingsDownward(std::vector<edge_t*>&, ptr_vector_t<BeadingPropagation>&)
|
||||
*/
|
||||
void propagateBeadingsDownward(edge_t* edge_to_peak, ptr_vector_t<BeadingPropagation>& node_beadings);
|
||||
|
||||
/*!
|
||||
* Find a beading in between two other beadings.
|
||||
*
|
||||
* This creates a new beading. With this we can find the coordinates of the
|
||||
* endpoints of the actual line segments to draw.
|
||||
*
|
||||
* The parameters \p left and \p right are not actually always left or right
|
||||
* but just arbitrary directions to visually indicate the difference.
|
||||
* \param left One of the beadings to interpolate between.
|
||||
* \param ratio_left_to_whole The position within the two beadings to sample
|
||||
* an interpolation. Should be a ratio between 0 and 1.
|
||||
* \param right One of the beadings to interpolate between.
|
||||
* \param switching_radius The bead radius at which we switch from the left
|
||||
* beading to the merged beading, if the beadings have a different number of
|
||||
* beads.
|
||||
* \return The beading at the interpolated location.
|
||||
*/
|
||||
Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius) const;
|
||||
|
||||
/*!
|
||||
* Subroutine of \ref interpolate(const Beading&, Ratio, const Beading&, coord_t)
|
||||
*
|
||||
* This creates a new Beading between two beadings, assuming that both have
|
||||
* the same number of beads.
|
||||
* \param left One of the beadings to interpolate between.
|
||||
* \param ratio_left_to_whole The position within the two beadings to sample
|
||||
* an interpolation. Should be a ratio between 0 and 1.
|
||||
* \param right One of the beadings to interpolate between.
|
||||
* \return The beading at the interpolated location.
|
||||
*/
|
||||
Beading interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right) const;
|
||||
|
||||
/*!
|
||||
* Get the beading at a certain node of the skeletal graph, or create one if
|
||||
* it doesn't have one yet.
|
||||
*
|
||||
* This is a lazy get.
|
||||
* \param node The node to get the beading from.
|
||||
* \param node_beadings A list of all beadings for nodes.
|
||||
* \return The beading of that node.
|
||||
*/
|
||||
std::shared_ptr<BeadingPropagation> getOrCreateBeading(node_t* node, ptr_vector_t<BeadingPropagation>& node_beadings);
|
||||
|
||||
/*!
|
||||
* In case we cannot find the beading of a node, get a beading from the
|
||||
* nearest node.
|
||||
* \param node The node to attempt to get a beading from. The actual node
|
||||
* that the returned beading is from may be a different, nearby node.
|
||||
* \param max_dist The maximum distance to search for.
|
||||
* \return A beading for the node, or ``nullptr`` if there is no node nearby
|
||||
* with a beading.
|
||||
*/
|
||||
std::shared_ptr<BeadingPropagation> getNearestBeading(node_t* node, coord_t max_dist);
|
||||
|
||||
/*!
|
||||
* generate junctions for each bone
|
||||
* \param edge_to_junctions junctions ordered high R to low R
|
||||
*/
|
||||
void generateJunctions(ptr_vector_t<BeadingPropagation>& node_beadings, ptr_vector_t<LineJunctions>& edge_junctions);
|
||||
|
||||
/*!
|
||||
* Add a new toolpath segment, defined between two extrusion-juntions.
|
||||
*
|
||||
* \param from The junction from which to add a segment.
|
||||
* \param to The junction to which to add a segment.
|
||||
* \param is_odd Whether this segment is an odd gap filler along the middle of the skeleton.
|
||||
* \param force_new_path Whether to prevent adding this path to an existing path which ends in \p from
|
||||
* \param from_is_3way Whether the \p from junction is a splitting junction where two normal wall lines and a gap filler line come together.
|
||||
* \param to_is_3way Whether the \p to junction is a splitting junction where two normal wall lines and a gap filler line come together.
|
||||
*/
|
||||
void addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way);
|
||||
|
||||
/*!
|
||||
* connect junctions in each quad
|
||||
*/
|
||||
void connectJunctions(ptr_vector_t<LineJunctions>& edge_junctions);
|
||||
|
||||
/*!
|
||||
* Genrate small segments for local maxima where the beading would only result in a single bead
|
||||
*/
|
||||
void generateLocalMaximaSingleBeads();
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // VORONOI_QUADRILATERALIZATION_H
|
||||
122
src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp
Normal file
122
src/libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp
Normal file
@@ -0,0 +1,122 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef SKELETAL_TRAPEZOIDATION_EDGE_H
|
||||
#define SKELETAL_TRAPEZOIDATION_EDGE_H
|
||||
|
||||
#include <memory> // smart pointers
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/ExtrusionJunction.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
class SkeletalTrapezoidationEdge
|
||||
{
|
||||
private:
|
||||
enum class Central { UNKNOWN = -1, NO, YES };
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Representing the location along an edge where the anchor position of a transition should be placed.
|
||||
*/
|
||||
struct TransitionMiddle
|
||||
{
|
||||
coord_t pos; // Position along edge as measure from edge.from.p
|
||||
int lower_bead_count;
|
||||
coord_t feature_radius; // The feature radius at which this transition is placed
|
||||
TransitionMiddle(coord_t pos, int lower_bead_count, coord_t feature_radius)
|
||||
: pos(pos), lower_bead_count(lower_bead_count)
|
||||
, feature_radius(feature_radius)
|
||||
{}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Represents the location along an edge where the lower or upper end of a transition should be placed.
|
||||
*/
|
||||
struct TransitionEnd
|
||||
{
|
||||
coord_t pos; // Position along edge as measure from edge.from.p, where the edge is always the half edge oriented from lower to higher R
|
||||
int lower_bead_count;
|
||||
bool is_lower_end; // Whether this is the ed of the transition with lower bead count
|
||||
TransitionEnd(coord_t pos, int lower_bead_count, bool is_lower_end)
|
||||
: pos(pos), lower_bead_count(lower_bead_count), is_lower_end(is_lower_end)
|
||||
{}
|
||||
};
|
||||
|
||||
enum class EdgeType
|
||||
{
|
||||
NORMAL = 0, // from voronoi diagram
|
||||
EXTRA_VD = 1, // introduced to voronoi diagram in order to make the gMAT
|
||||
TRANSITION_END = 2 // introduced to voronoi diagram in order to make the gMAT
|
||||
};
|
||||
EdgeType type;
|
||||
|
||||
SkeletalTrapezoidationEdge() : SkeletalTrapezoidationEdge(EdgeType::NORMAL) {}
|
||||
SkeletalTrapezoidationEdge(const EdgeType &type) : type(type), is_central(Central::UNKNOWN) {}
|
||||
|
||||
bool isCentral() const
|
||||
{
|
||||
assert(is_central != Central::UNKNOWN);
|
||||
return is_central == Central::YES;
|
||||
}
|
||||
void setIsCentral(bool b)
|
||||
{
|
||||
is_central = b ? Central::YES : Central::NO;
|
||||
}
|
||||
bool centralIsSet() const
|
||||
{
|
||||
return is_central != Central::UNKNOWN;
|
||||
}
|
||||
|
||||
bool hasTransitions(bool ignore_empty = false) const
|
||||
{
|
||||
return transitions.use_count() > 0 && (ignore_empty || ! transitions.lock()->empty());
|
||||
}
|
||||
void setTransitions(std::shared_ptr<std::list<TransitionMiddle>> storage)
|
||||
{
|
||||
transitions = storage;
|
||||
}
|
||||
std::shared_ptr<std::list<TransitionMiddle>> getTransitions()
|
||||
{
|
||||
return transitions.lock();
|
||||
}
|
||||
|
||||
bool hasTransitionEnds(bool ignore_empty = false) const
|
||||
{
|
||||
return transition_ends.use_count() > 0 && (ignore_empty || ! transition_ends.lock()->empty());
|
||||
}
|
||||
void setTransitionEnds(std::shared_ptr<std::list<TransitionEnd>> storage)
|
||||
{
|
||||
transition_ends = storage;
|
||||
}
|
||||
std::shared_ptr<std::list<TransitionEnd>> getTransitionEnds()
|
||||
{
|
||||
return transition_ends.lock();
|
||||
}
|
||||
|
||||
bool hasExtrusionJunctions(bool ignore_empty = false) const
|
||||
{
|
||||
return extrusion_junctions.use_count() > 0 && (ignore_empty || ! extrusion_junctions.lock()->empty());
|
||||
}
|
||||
void setExtrusionJunctions(std::shared_ptr<LineJunctions> storage)
|
||||
{
|
||||
extrusion_junctions = storage;
|
||||
}
|
||||
std::shared_ptr<LineJunctions> getExtrusionJunctions()
|
||||
{
|
||||
return extrusion_junctions.lock();
|
||||
}
|
||||
|
||||
private:
|
||||
Central is_central; //! whether the edge is significant; whether the source segments have a sharp angle; -1 is unknown
|
||||
|
||||
std::weak_ptr<std::list<TransitionMiddle>> transitions;
|
||||
std::weak_ptr<std::list<TransitionEnd>> transition_ends;
|
||||
std::weak_ptr<LineJunctions> extrusion_junctions;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // SKELETAL_TRAPEZOIDATION_EDGE_H
|
||||
467
src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp
Normal file
467
src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp
Normal file
@@ -0,0 +1,467 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "SkeletalTrapezoidationGraph.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "utils/linearAlg2D.hpp"
|
||||
#include "../Line.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
STHalfEdge::STHalfEdge(SkeletalTrapezoidationEdge data) : HalfEdge(data) {}
|
||||
|
||||
bool STHalfEdge::canGoUp(bool strict) const
|
||||
{
|
||||
if (to->data.distance_to_boundary > from->data.distance_to_boundary)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (to->data.distance_to_boundary < from->data.distance_to_boundary || strict)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Edge is between equidistqant verts; recurse!
|
||||
for (edge_t* outgoing = next; outgoing != twin; outgoing = outgoing->twin->next)
|
||||
{
|
||||
if (outgoing->canGoUp())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
assert(outgoing->twin); if (!outgoing->twin) return false;
|
||||
assert(outgoing->twin->next); if (!outgoing->twin->next) return true; // This point is on the boundary?! Should never occur
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool STHalfEdge::isUpward() const
|
||||
{
|
||||
if (to->data.distance_to_boundary > from->data.distance_to_boundary)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (to->data.distance_to_boundary < from->data.distance_to_boundary)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Equidistant edge case:
|
||||
std::optional<coord_t> forward_up_dist = this->distToGoUp();
|
||||
std::optional<coord_t> backward_up_dist = twin->distToGoUp();
|
||||
if (forward_up_dist && backward_up_dist)
|
||||
{
|
||||
return forward_up_dist < backward_up_dist;
|
||||
}
|
||||
|
||||
if (forward_up_dist)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (backward_up_dist)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return to->p < from->p; // Arbitrary ordering, which returns the opposite for the twin edge
|
||||
}
|
||||
|
||||
std::optional<coord_t> STHalfEdge::distToGoUp() const
|
||||
{
|
||||
if (to->data.distance_to_boundary > from->data.distance_to_boundary)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (to->data.distance_to_boundary < from->data.distance_to_boundary)
|
||||
{
|
||||
return std::optional<coord_t>();
|
||||
}
|
||||
|
||||
// Edge is between equidistqant verts; recurse!
|
||||
std::optional<coord_t> ret;
|
||||
for (edge_t* outgoing = next; outgoing != twin; outgoing = outgoing->twin->next)
|
||||
{
|
||||
std::optional<coord_t> dist_to_up = outgoing->distToGoUp();
|
||||
if (dist_to_up)
|
||||
{
|
||||
if (ret)
|
||||
{
|
||||
ret = std::min(*ret, *dist_to_up);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = dist_to_up;
|
||||
}
|
||||
}
|
||||
assert(outgoing->twin); if (!outgoing->twin) return std::optional<coord_t>();
|
||||
assert(outgoing->twin->next); if (!outgoing->twin->next) return 0; // This point is on the boundary?! Should never occur
|
||||
}
|
||||
if (ret)
|
||||
{
|
||||
ret = *ret + (to->p - from->p).cast<int64_t>().norm();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
STHalfEdge* STHalfEdge::getNextUnconnected()
|
||||
{
|
||||
edge_t* result = static_cast<STHalfEdge*>(this);
|
||||
while (result->next)
|
||||
{
|
||||
result = result->next;
|
||||
if (result == this)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return result->twin;
|
||||
}
|
||||
|
||||
STHalfEdgeNode::STHalfEdgeNode(SkeletalTrapezoidationJoint data, Point p) : HalfEdgeNode(data, p) {}
|
||||
|
||||
bool STHalfEdgeNode::isMultiIntersection()
|
||||
{
|
||||
int odd_path_count = 0;
|
||||
edge_t* outgoing = this->incident_edge;
|
||||
do
|
||||
{
|
||||
if ( ! outgoing)
|
||||
{ // This is a node on the outside
|
||||
return false;
|
||||
}
|
||||
if (outgoing->data.isCentral())
|
||||
{
|
||||
odd_path_count++;
|
||||
}
|
||||
} while (outgoing = outgoing->twin->next, outgoing != this->incident_edge);
|
||||
return odd_path_count > 2;
|
||||
}
|
||||
|
||||
bool STHalfEdgeNode::isCentral() const
|
||||
{
|
||||
edge_t* edge = incident_edge;
|
||||
do
|
||||
{
|
||||
if (edge->data.isCentral())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
assert(edge->twin); if (!edge->twin) return false;
|
||||
} while (edge = edge->twin->next, edge != incident_edge);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool STHalfEdgeNode::isLocalMaximum(bool strict) const
|
||||
{
|
||||
if (data.distance_to_boundary == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
edge_t* edge = incident_edge;
|
||||
do
|
||||
{
|
||||
if (edge->canGoUp(strict))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
assert(edge->twin); if (!edge->twin) return false;
|
||||
|
||||
if (!edge->twin->next)
|
||||
{ // This point is on the boundary
|
||||
return false;
|
||||
}
|
||||
} while (edge = edge->twin->next, edge != incident_edge);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist)
|
||||
{
|
||||
std::unordered_map<edge_t*, std::list<edge_t>::iterator> edge_locator;
|
||||
std::unordered_map<node_t*, std::list<node_t>::iterator> node_locator;
|
||||
|
||||
for (auto edge_it = edges.begin(); edge_it != edges.end(); ++edge_it)
|
||||
{
|
||||
edge_locator.emplace(&*edge_it, edge_it);
|
||||
}
|
||||
|
||||
for (auto node_it = nodes.begin(); node_it != nodes.end(); ++node_it)
|
||||
{
|
||||
node_locator.emplace(&*node_it, node_it);
|
||||
}
|
||||
|
||||
auto safelyRemoveEdge = [this, &edge_locator](edge_t* to_be_removed, std::list<edge_t>::iterator& current_edge_it, bool& edge_it_is_updated)
|
||||
{
|
||||
if (current_edge_it != edges.end()
|
||||
&& to_be_removed == &*current_edge_it)
|
||||
{
|
||||
current_edge_it = edges.erase(current_edge_it);
|
||||
edge_it_is_updated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
edges.erase(edge_locator[to_be_removed]);
|
||||
}
|
||||
};
|
||||
|
||||
auto should_collapse = [snap_dist](node_t* a, node_t* b)
|
||||
{
|
||||
return shorter_then(a->p - b->p, snap_dist);
|
||||
};
|
||||
|
||||
for (auto edge_it = edges.begin(); edge_it != edges.end();)
|
||||
{
|
||||
if (edge_it->prev)
|
||||
{
|
||||
edge_it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
edge_t* quad_start = &*edge_it;
|
||||
edge_t* quad_end = quad_start; while (quad_end->next) quad_end = quad_end->next;
|
||||
edge_t* quad_mid = (quad_start->next == quad_end)? nullptr : quad_start->next;
|
||||
|
||||
bool edge_it_is_updated = false;
|
||||
if (quad_mid && should_collapse(quad_mid->from, quad_mid->to))
|
||||
{
|
||||
assert(quad_mid->twin);
|
||||
if(!quad_mid->twin)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "Encountered quad edge without a twin.";
|
||||
continue; //Prevent accessing unallocated memory.
|
||||
}
|
||||
int count = 0;
|
||||
for (edge_t* edge_from_3 = quad_end; edge_from_3 && edge_from_3 != quad_mid->twin; edge_from_3 = edge_from_3->twin->next)
|
||||
{
|
||||
edge_from_3->from = quad_mid->from;
|
||||
edge_from_3->twin->to = quad_mid->from;
|
||||
if (count > 50)
|
||||
{
|
||||
std::cerr << edge_from_3->from->p << " - " << edge_from_3->to->p << '\n';
|
||||
}
|
||||
if (++count > 1000)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// o-o > collapse top
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// o o
|
||||
if (quad_mid->from->incident_edge == quad_mid)
|
||||
{
|
||||
if (quad_mid->twin->next)
|
||||
{
|
||||
quad_mid->from->incident_edge = quad_mid->twin->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
quad_mid->from->incident_edge = quad_mid->prev->twin;
|
||||
}
|
||||
}
|
||||
|
||||
nodes.erase(node_locator[quad_mid->to]);
|
||||
|
||||
quad_mid->prev->next = quad_mid->next;
|
||||
quad_mid->next->prev = quad_mid->prev;
|
||||
quad_mid->twin->next->prev = quad_mid->twin->prev;
|
||||
quad_mid->twin->prev->next = quad_mid->twin->next;
|
||||
|
||||
safelyRemoveEdge(quad_mid->twin, edge_it, edge_it_is_updated);
|
||||
safelyRemoveEdge(quad_mid, edge_it, edge_it_is_updated);
|
||||
}
|
||||
|
||||
// o-o
|
||||
// | | > collapse sides
|
||||
// o o
|
||||
if ( should_collapse(quad_start->from, quad_end->to) && should_collapse(quad_start->to, quad_end->from))
|
||||
{ // Collapse start and end edges and remove whole cell
|
||||
|
||||
quad_start->twin->to = quad_end->to;
|
||||
quad_end->to->incident_edge = quad_end->twin;
|
||||
if (quad_end->from->incident_edge == quad_end)
|
||||
{
|
||||
if (quad_end->twin->next)
|
||||
{
|
||||
quad_end->from->incident_edge = quad_end->twin->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
quad_end->from->incident_edge = quad_end->prev->twin;
|
||||
}
|
||||
}
|
||||
nodes.erase(node_locator[quad_start->from]);
|
||||
|
||||
quad_start->twin->twin = quad_end->twin;
|
||||
quad_end->twin->twin = quad_start->twin;
|
||||
safelyRemoveEdge(quad_start, edge_it, edge_it_is_updated);
|
||||
safelyRemoveEdge(quad_end, edge_it, edge_it_is_updated);
|
||||
}
|
||||
// If only one side had collapsable length then the cell on the other side of that edge has to collapse
|
||||
// if we would collapse that one edge then that would change the quad_start and/or quad_end of neighboring cells
|
||||
// this is to do with the constraint that !prev == !twin.next
|
||||
|
||||
if (!edge_it_is_updated)
|
||||
{
|
||||
edge_it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end)
|
||||
{
|
||||
Point p;
|
||||
Line(start_source_point, end_source_point).distance_to_infinite_squared(prev_edge->to->p, &p);
|
||||
coord_t dist = (prev_edge->to->p - p).cast<int64_t>().norm();
|
||||
prev_edge->to->data.distance_to_boundary = dist;
|
||||
assert(dist >= 0);
|
||||
|
||||
nodes.emplace_front(SkeletalTrapezoidationJoint(), p);
|
||||
node_t* node = &nodes.front();
|
||||
node->data.distance_to_boundary = 0;
|
||||
|
||||
edges.emplace_front(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD));
|
||||
edge_t* forth_edge = &edges.front();
|
||||
edges.emplace_front(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD));
|
||||
edge_t* back_edge = &edges.front();
|
||||
|
||||
prev_edge->next = forth_edge;
|
||||
forth_edge->prev = prev_edge;
|
||||
forth_edge->from = prev_edge->to;
|
||||
forth_edge->to = node;
|
||||
forth_edge->twin = back_edge;
|
||||
back_edge->twin = forth_edge;
|
||||
back_edge->from = node;
|
||||
back_edge->to = prev_edge->to;
|
||||
node->incident_edge = back_edge;
|
||||
|
||||
prev_edge = back_edge;
|
||||
}
|
||||
|
||||
std::pair<SkeletalTrapezoidationGraph::edge_t*, SkeletalTrapezoidationGraph::edge_t*> SkeletalTrapezoidationGraph::insertRib(edge_t& edge, node_t* mid_node)
|
||||
{
|
||||
edge_t* edge_before = edge.prev;
|
||||
edge_t* edge_after = edge.next;
|
||||
node_t* node_before = edge.from;
|
||||
node_t* node_after = edge.to;
|
||||
|
||||
Point p = mid_node->p;
|
||||
|
||||
const Line source_segment = getSource(edge);
|
||||
Point px;
|
||||
source_segment.distance_to_squared(p, &px);
|
||||
coord_t dist = (p - px).cast<int64_t>().norm();
|
||||
assert(dist > 0);
|
||||
mid_node->data.distance_to_boundary = dist;
|
||||
mid_node->data.transition_ratio = 0; // Both transition end should have rest = 0, because at the ends a whole number of beads fits without rest
|
||||
|
||||
nodes.emplace_back(SkeletalTrapezoidationJoint(), px);
|
||||
node_t* source_node = &nodes.back();
|
||||
source_node->data.distance_to_boundary = 0;
|
||||
|
||||
edge_t* first = &edge;
|
||||
edges.emplace_back(SkeletalTrapezoidationEdge());
|
||||
edge_t* second = &edges.back();
|
||||
edges.emplace_back(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::TRANSITION_END));
|
||||
edge_t* outward_edge = &edges.back();
|
||||
edges.emplace_back(SkeletalTrapezoidationEdge(SkeletalTrapezoidationEdge::EdgeType::TRANSITION_END));
|
||||
edge_t* inward_edge = &edges.back();
|
||||
|
||||
if (edge_before)
|
||||
{
|
||||
edge_before->next = first;
|
||||
}
|
||||
first->next = outward_edge;
|
||||
outward_edge->next = nullptr;
|
||||
inward_edge->next = second;
|
||||
second->next = edge_after;
|
||||
|
||||
if (edge_after)
|
||||
{
|
||||
edge_after->prev = second;
|
||||
}
|
||||
second->prev = inward_edge;
|
||||
inward_edge->prev = nullptr;
|
||||
outward_edge->prev = first;
|
||||
first->prev = edge_before;
|
||||
|
||||
first->to = mid_node;
|
||||
outward_edge->to = source_node;
|
||||
inward_edge->to = mid_node;
|
||||
second->to = node_after;
|
||||
|
||||
first->from = node_before;
|
||||
outward_edge->from = mid_node;
|
||||
inward_edge->from = source_node;
|
||||
second->from = mid_node;
|
||||
|
||||
node_before->incident_edge = first;
|
||||
mid_node->incident_edge = outward_edge;
|
||||
source_node->incident_edge = inward_edge;
|
||||
if (edge_after)
|
||||
{
|
||||
node_after->incident_edge = edge_after;
|
||||
}
|
||||
|
||||
first->data.setIsCentral(true);
|
||||
outward_edge->data.setIsCentral(false); // TODO verify this is always the case.
|
||||
inward_edge->data.setIsCentral(false);
|
||||
second->data.setIsCentral(true);
|
||||
|
||||
outward_edge->twin = inward_edge;
|
||||
inward_edge->twin = outward_edge;
|
||||
|
||||
first->twin = nullptr; // we don't know these yet!
|
||||
second->twin = nullptr;
|
||||
|
||||
assert(second->prev->from->data.distance_to_boundary == 0);
|
||||
|
||||
return std::make_pair(first, second);
|
||||
}
|
||||
|
||||
SkeletalTrapezoidationGraph::edge_t* SkeletalTrapezoidationGraph::insertNode(edge_t* edge, Point mid, coord_t mide_node_bead_count)
|
||||
{
|
||||
edge_t* last_edge_replacing_input = edge;
|
||||
|
||||
nodes.emplace_back(SkeletalTrapezoidationJoint(), mid);
|
||||
node_t* mid_node = &nodes.back();
|
||||
|
||||
edge_t* twin = last_edge_replacing_input->twin;
|
||||
last_edge_replacing_input->twin = nullptr;
|
||||
twin->twin = nullptr;
|
||||
std::pair<edge_t*, edge_t*> left_pair = insertRib(*last_edge_replacing_input, mid_node);
|
||||
std::pair<edge_t*, edge_t*> right_pair = insertRib(*twin, mid_node);
|
||||
edge_t* first_edge_replacing_input = left_pair.first;
|
||||
last_edge_replacing_input = left_pair.second;
|
||||
edge_t* first_edge_replacing_twin = right_pair.first;
|
||||
edge_t* last_edge_replacing_twin = right_pair.second;
|
||||
|
||||
first_edge_replacing_input->twin = last_edge_replacing_twin;
|
||||
last_edge_replacing_twin->twin = first_edge_replacing_input;
|
||||
last_edge_replacing_input->twin = first_edge_replacing_twin;
|
||||
first_edge_replacing_twin->twin = last_edge_replacing_input;
|
||||
|
||||
mid_node->data.bead_count = mide_node_bead_count;
|
||||
|
||||
return last_edge_replacing_input;
|
||||
}
|
||||
|
||||
Line SkeletalTrapezoidationGraph::getSource(const edge_t &edge) const
|
||||
{
|
||||
const edge_t *from_edge = &edge;
|
||||
while (from_edge->prev)
|
||||
from_edge = from_edge->prev;
|
||||
|
||||
const edge_t *to_edge = &edge;
|
||||
while (to_edge->next)
|
||||
to_edge = to_edge->next;
|
||||
|
||||
return Line(from_edge->from->p, to_edge->to->p);
|
||||
}
|
||||
|
||||
}
|
||||
105
src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp
Normal file
105
src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp
Normal file
@@ -0,0 +1,105 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef SKELETAL_TRAPEZOIDATION_GRAPH_H
|
||||
#define SKELETAL_TRAPEZOIDATION_GRAPH_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "utils/HalfEdgeGraph.hpp"
|
||||
#include "SkeletalTrapezoidationEdge.hpp"
|
||||
#include "SkeletalTrapezoidationJoint.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
class STHalfEdgeNode;
|
||||
|
||||
class STHalfEdge : public HalfEdge<SkeletalTrapezoidationJoint, SkeletalTrapezoidationEdge, STHalfEdgeNode, STHalfEdge>
|
||||
{
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
public:
|
||||
STHalfEdge(SkeletalTrapezoidationEdge data);
|
||||
|
||||
/*!
|
||||
* Check (recursively) whether there is any upward edge from the distance_to_boundary of the from of the \param edge
|
||||
*
|
||||
* \param strict Whether equidistant edges can count as a local maximum
|
||||
*/
|
||||
bool canGoUp(bool strict = false) const;
|
||||
|
||||
/*!
|
||||
* Check whether the edge goes from a lower to a higher distance_to_boundary.
|
||||
* Effectively deals with equidistant edges by looking beyond this edge.
|
||||
*/
|
||||
bool isUpward() const;
|
||||
|
||||
/*!
|
||||
* Calculate the traversed distance until we meet an upward edge.
|
||||
* Useful for calling on edges between equidistant points.
|
||||
*
|
||||
* If we can go up then the distance includes the length of the \param edge
|
||||
*/
|
||||
std::optional<coord_t> distToGoUp() const;
|
||||
|
||||
STHalfEdge* getNextUnconnected();
|
||||
};
|
||||
|
||||
class STHalfEdgeNode : public HalfEdgeNode<SkeletalTrapezoidationJoint, SkeletalTrapezoidationEdge, STHalfEdgeNode, STHalfEdge>
|
||||
{
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
public:
|
||||
STHalfEdgeNode(SkeletalTrapezoidationJoint data, Point p);
|
||||
|
||||
bool isMultiIntersection();
|
||||
|
||||
bool isCentral() const;
|
||||
|
||||
/*!
|
||||
* Check whether this node has a locally maximal distance_to_boundary
|
||||
*
|
||||
* \param strict Whether equidistant edges can count as a local maximum
|
||||
*/
|
||||
bool isLocalMaximum(bool strict = false) const;
|
||||
};
|
||||
|
||||
class SkeletalTrapezoidationGraph: public HalfEdgeGraph<SkeletalTrapezoidationJoint, SkeletalTrapezoidationEdge, STHalfEdgeNode, STHalfEdge>
|
||||
{
|
||||
using edge_t = STHalfEdge;
|
||||
using node_t = STHalfEdgeNode;
|
||||
public:
|
||||
|
||||
/*!
|
||||
* If an edge is too small, collapse it and its twin and fix the surrounding edges to ensure a consistent graph.
|
||||
*
|
||||
* Don't collapse support edges, unless we can collapse the whole quad.
|
||||
*
|
||||
* o-,
|
||||
* | "-o
|
||||
* | | > Don't collapse this edge only.
|
||||
* o o
|
||||
*/
|
||||
void collapseSmallEdges(coord_t snap_dist = 5);
|
||||
|
||||
void makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end);
|
||||
|
||||
/*!
|
||||
* Insert a node into the graph and connect it to the input polygon using ribs
|
||||
*
|
||||
* \return the last edge which replaced [edge], which points to the same [to] node
|
||||
*/
|
||||
edge_t* insertNode(edge_t* edge, Point mid, coord_t mide_node_bead_count);
|
||||
|
||||
/*!
|
||||
* Return the first and last edge of the edges replacing \p edge pointing to the same node
|
||||
*/
|
||||
std::pair<edge_t*, edge_t*> insertRib(edge_t& edge, node_t* mid_node);
|
||||
|
||||
protected:
|
||||
Line getSource(const edge_t& edge) const;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
||||
60
src/libslic3r/Arachne/SkeletalTrapezoidationJoint.hpp
Normal file
60
src/libslic3r/Arachne/SkeletalTrapezoidationJoint.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef SKELETAL_TRAPEZOIDATION_JOINT_H
|
||||
#define SKELETAL_TRAPEZOIDATION_JOINT_H
|
||||
|
||||
#include <memory> // smart pointers
|
||||
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
class SkeletalTrapezoidationJoint
|
||||
{
|
||||
using Beading = BeadingStrategy::Beading;
|
||||
public:
|
||||
struct BeadingPropagation
|
||||
{
|
||||
Beading beading;
|
||||
coord_t dist_to_bottom_source;
|
||||
coord_t dist_from_top_source;
|
||||
bool is_upward_propagated_only;
|
||||
BeadingPropagation(const Beading& beading)
|
||||
: beading(beading)
|
||||
, dist_to_bottom_source(0)
|
||||
, dist_from_top_source(0)
|
||||
, is_upward_propagated_only(false)
|
||||
{}
|
||||
};
|
||||
|
||||
coord_t distance_to_boundary;
|
||||
coord_t bead_count;
|
||||
float transition_ratio; //! The distance near the skeleton to leave free because this joint is in the middle of a transition, as a fraction of the inner bead width of the bead at the higher transition.
|
||||
SkeletalTrapezoidationJoint()
|
||||
: distance_to_boundary(-1)
|
||||
, bead_count(-1)
|
||||
, transition_ratio(0)
|
||||
{}
|
||||
|
||||
bool hasBeading() const
|
||||
{
|
||||
return beading.use_count() > 0;
|
||||
}
|
||||
void setBeading(std::shared_ptr<BeadingPropagation> storage)
|
||||
{
|
||||
beading = storage;
|
||||
}
|
||||
std::shared_ptr<BeadingPropagation> getBeading()
|
||||
{
|
||||
return beading.lock();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::weak_ptr<BeadingPropagation> beading;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // SKELETAL_TRAPEZOIDATION_JOINT_H
|
||||
843
src/libslic3r/Arachne/WallToolPaths.cpp
Normal file
843
src/libslic3r/Arachne/WallToolPaths.cpp
Normal file
@@ -0,0 +1,843 @@
|
||||
// Copyright (c) 2022 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <algorithm> //For std::partition_copy and std::min_element.
|
||||
#include <unordered_set>
|
||||
|
||||
#include "WallToolPaths.hpp"
|
||||
|
||||
#include "SkeletalTrapezoidation.hpp"
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "utils/linearAlg2D.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "utils/SparseLineGrid.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "utils/PolylineStitcher.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
//#define ARACHNE_STITCH_PATCH_DEBUG
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0, const coord_t bead_width_x,
|
||||
const size_t inset_count, const coord_t wall_0_inset, const coordf_t layer_height, const WallToolPathsParams ¶ms)
|
||||
: outline(outline)
|
||||
, bead_width_0(bead_width_0)
|
||||
, bead_width_x(bead_width_x)
|
||||
, inset_count(inset_count)
|
||||
, wall_0_inset(wall_0_inset)
|
||||
, layer_height(layer_height)
|
||||
, print_thin_walls(Slic3r::Arachne::fill_outline_gaps)
|
||||
, min_feature_size(scaled<coord_t>(params.min_feature_size))
|
||||
, min_bead_width(scaled<coord_t>(params.min_bead_width))
|
||||
, small_area_length(static_cast<double>(bead_width_0) / 2.)
|
||||
, wall_transition_filter_deviation(scaled<coord_t>(params.wall_transition_filter_deviation))
|
||||
, toolpaths_generated(false)
|
||||
, m_params(params)
|
||||
{
|
||||
}
|
||||
|
||||
void simplify(Polygon &thiss, const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared)
|
||||
{
|
||||
if (thiss.size() < 3) {
|
||||
thiss.points.clear();
|
||||
return;
|
||||
}
|
||||
if (thiss.size() == 3)
|
||||
return;
|
||||
|
||||
Polygon new_path;
|
||||
Point previous = thiss.points.back();
|
||||
Point previous_previous = thiss.points.at(thiss.points.size() - 2);
|
||||
Point current = thiss.points.at(0);
|
||||
|
||||
/* When removing a vertex, we check the height of the triangle of the area
|
||||
being removed from the original polygon by the simplification. However,
|
||||
when consecutively removing multiple vertices the height of the previously
|
||||
removed vertices w.r.t. the shortcut path changes.
|
||||
In order to not recompute the new height value of previously removed
|
||||
vertices we compute the height of a representative triangle, which covers
|
||||
the same amount of area as the area being cut off. We use the Shoelace
|
||||
formula to accumulate the area under the removed segments. This works by
|
||||
computing the area in a 'fan' where each of the blades of the fan go from
|
||||
the origin to one of the segments. While removing vertices the area in
|
||||
this fan accumulates. By subtracting the area of the blade connected to
|
||||
the short-cutting segment we obtain the total area of the cutoff region.
|
||||
From this area we compute the height of the representative triangle using
|
||||
the standard formula for a triangle area: A = .5*b*h
|
||||
*/
|
||||
int64_t accumulated_area_removed = int64_t(previous.x()) * int64_t(current.y()) - int64_t(previous.y()) * int64_t(current.x()); // Twice the Shoelace formula for area of polygon per line segment.
|
||||
|
||||
for (size_t point_idx = 0; point_idx < thiss.points.size(); point_idx++) {
|
||||
current = thiss.points.at(point_idx % thiss.points.size());
|
||||
|
||||
//Check if the accumulated area doesn't exceed the maximum.
|
||||
Point next;
|
||||
if (point_idx + 1 < thiss.points.size()) {
|
||||
next = thiss.points.at(point_idx + 1);
|
||||
} else if (point_idx + 1 == thiss.points.size() && new_path.size() > 1) { // don't spill over if the [next] vertex will then be equal to [previous]
|
||||
next = new_path[0]; //Spill over to new polygon for checking removed area.
|
||||
} else {
|
||||
next = thiss.points.at((point_idx + 1) % thiss.points.size());
|
||||
}
|
||||
const int64_t removed_area_next = int64_t(current.x()) * int64_t(next.y()) - int64_t(current.y()) * int64_t(next.x()); // Twice the Shoelace formula for area of polygon per line segment.
|
||||
const int64_t negative_area_closing = int64_t(next.x()) * int64_t(previous.y()) - int64_t(next.y()) * int64_t(previous.x()); // area between the origin and the short-cutting segment
|
||||
accumulated_area_removed += removed_area_next;
|
||||
|
||||
const int64_t length2 = (current - previous).cast<int64_t>().squaredNorm();
|
||||
if (length2 < scaled<int64_t>(25.)) {
|
||||
// We're allowed to always delete segments of less than 5 micron.
|
||||
continue;
|
||||
}
|
||||
|
||||
const int64_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // close the shortcut area polygon
|
||||
const int64_t base_length_2 = (next - previous).cast<int64_t>().squaredNorm();
|
||||
|
||||
if (base_length_2 == 0) //Two line segments form a line back and forth with no area.
|
||||
continue; //Remove the vertex.
|
||||
//We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared.
|
||||
//1/2 L = A [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5
|
||||
//A = 1/2 * b * h [triangle area formula]
|
||||
//L = b * h [apply above two and take out the 1/2]
|
||||
//h = L / b [divide by b]
|
||||
//h^2 = (L / b)^2 [square it]
|
||||
//h^2 = L^2 / b^2 [factor the divisor]
|
||||
const int64_t height_2 = double(area_removed_so_far) * double(area_removed_so_far) / double(base_length_2);
|
||||
if ((height_2 <= Slic3r::sqr(scaled<coord_t>(0.005)) //Almost exactly colinear (barring rounding errors).
|
||||
&& Line::distance_to_infinite(current, previous, next) <= scaled<double>(0.005))) // make sure that height_2 is not small because of cancellation of positive and negative areas
|
||||
continue;
|
||||
|
||||
if (length2 < smallest_line_segment_squared
|
||||
&& height_2 <= allowed_error_distance_squared) // removing the vertex doesn't introduce too much error.)
|
||||
{
|
||||
const int64_t next_length2 = (current - next).cast<int64_t>().squaredNorm();
|
||||
if (next_length2 > 4 * smallest_line_segment_squared) {
|
||||
// Special case; The next line is long. If we were to remove this, it could happen that we get quite noticeable artifacts.
|
||||
// We should instead move this point to a location where both edges are kept and then remove the previous point that we wanted to keep.
|
||||
// By taking the intersection of these two lines, we get a point that preserves the direction (so it makes the corner a bit more pointy).
|
||||
// We just need to be sure that the intersection point does not introduce an artifact itself.
|
||||
Point intersection_point;
|
||||
bool has_intersection = Line(previous_previous, previous).intersection_infinite(Line(current, next), &intersection_point);
|
||||
if (!has_intersection
|
||||
|| Line::distance_to_infinite_squared(intersection_point, previous, current) > double(allowed_error_distance_squared)
|
||||
|| (intersection_point - previous).cast<int64_t>().squaredNorm() > smallest_line_segment_squared // The intersection point is way too far from the 'previous'
|
||||
|| (intersection_point - next).cast<int64_t>().squaredNorm() > smallest_line_segment_squared) // and 'next' points, so it shouldn't replace 'current'
|
||||
{
|
||||
// We can't find a better spot for it, but the size of the line is more than 5 micron.
|
||||
// So the only thing we can do here is leave it in...
|
||||
}
|
||||
else {
|
||||
// New point seems like a valid one.
|
||||
current = intersection_point;
|
||||
// If there was a previous point added, remove it.
|
||||
if(!new_path.empty()) {
|
||||
new_path.points.pop_back();
|
||||
previous = previous_previous;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continue; //Remove the vertex.
|
||||
}
|
||||
}
|
||||
//Don't remove the vertex.
|
||||
accumulated_area_removed = removed_area_next; // so that in the next iteration it's the area between the origin, [previous] and [current]
|
||||
previous_previous = previous;
|
||||
previous = current; //Note that "previous" is only updated if we don't remove the vertex.
|
||||
new_path.points.push_back(current);
|
||||
}
|
||||
|
||||
thiss = new_path;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Removes vertices of the polygons to make sure that they are not too high
|
||||
* resolution.
|
||||
*
|
||||
* This removes points which are connected to line segments that are shorter
|
||||
* than the `smallest_line_segment`, unless that would introduce a deviation
|
||||
* in the contour of more than `allowed_error_distance`.
|
||||
*
|
||||
* Criteria:
|
||||
* 1. Never remove a vertex if either of the connceted segments is larger than \p smallest_line_segment
|
||||
* 2. Never remove a vertex if the distance between that vertex and the final resulting polygon would be higher than \p allowed_error_distance
|
||||
* 3. The direction of segments longer than \p smallest_line_segment always
|
||||
* remains unaltered (but their end points may change if it is connected to
|
||||
* a small segment)
|
||||
*
|
||||
* Simplify uses a heuristic and doesn't neccesarily remove all removable
|
||||
* vertices under the above criteria, but simplify may never violate these
|
||||
* criteria. Unless the segments or the distance is smaller than the
|
||||
* rounding error of 5 micron.
|
||||
*
|
||||
* Vertices which introduce an error of less than 5 microns are removed
|
||||
* anyway, even if the segments are longer than the smallest line segment.
|
||||
* This makes sure that (practically) colinear line segments are joined into
|
||||
* a single line segment.
|
||||
* \param smallest_line_segment Maximal length of removed line segments.
|
||||
* \param allowed_error_distance If removing a vertex introduces a deviation
|
||||
* from the original path that is more than this distance, the vertex may
|
||||
* not be removed.
|
||||
*/
|
||||
void simplify(Polygons &thiss, const int64_t smallest_line_segment = scaled<coord_t>(0.01), const int64_t allowed_error_distance = scaled<coord_t>(0.005))
|
||||
{
|
||||
const int64_t allowed_error_distance_squared = int64_t(allowed_error_distance) * int64_t(allowed_error_distance);
|
||||
const int64_t smallest_line_segment_squared = int64_t(smallest_line_segment) * int64_t(smallest_line_segment);
|
||||
for (size_t p = 0; p < thiss.size(); p++)
|
||||
{
|
||||
simplify(thiss[p], smallest_line_segment_squared, allowed_error_distance_squared);
|
||||
if (thiss[p].size() < 3)
|
||||
{
|
||||
thiss.erase(thiss.begin() + p);
|
||||
p--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef SparseLineGrid<PolygonsPointIndex, PolygonsPointIndexSegmentLocator> LocToLineGrid;
|
||||
std::unique_ptr<LocToLineGrid> createLocToLineGrid(const Polygons &polygons, int square_size)
|
||||
{
|
||||
unsigned int n_points = 0;
|
||||
for (const auto &poly : polygons)
|
||||
n_points += poly.size();
|
||||
|
||||
auto ret = std::make_unique<LocToLineGrid>(square_size, n_points);
|
||||
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
for (unsigned int point_idx = 0; point_idx < polygons[poly_idx].size(); point_idx++)
|
||||
ret->insert(PolygonsPointIndex(&polygons, poly_idx, point_idx));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Note: Also tries to solve for near-self intersections, when epsilon >= 1
|
||||
*/
|
||||
void fixSelfIntersections(const coord_t epsilon, Polygons &thiss)
|
||||
{
|
||||
if (epsilon < 1) {
|
||||
ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss));
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t half_epsilon = (epsilon + 1) / 2;
|
||||
|
||||
// Points too close to line segments should be moved a little away from those line segments, but less than epsilon,
|
||||
// so at least half-epsilon distance between points can still be guaranteed.
|
||||
constexpr coord_t grid_size = scaled<coord_t>(2.);
|
||||
auto query_grid = createLocToLineGrid(thiss, grid_size);
|
||||
|
||||
const auto move_dist = std::max<int64_t>(2L, half_epsilon - 2);
|
||||
const int64_t half_epsilon_sqrd = half_epsilon * half_epsilon;
|
||||
|
||||
const size_t n = thiss.size();
|
||||
for (size_t poly_idx = 0; poly_idx < n; poly_idx++) {
|
||||
const size_t pathlen = thiss[poly_idx].size();
|
||||
for (size_t point_idx = 0; point_idx < pathlen; ++point_idx) {
|
||||
Point &pt = thiss[poly_idx][point_idx];
|
||||
for (const auto &line : query_grid->getNearby(pt, epsilon)) {
|
||||
const size_t line_next_idx = (line.point_idx + 1) % thiss[line.poly_idx].size();
|
||||
if (poly_idx == line.poly_idx && (point_idx == line.point_idx || point_idx == line_next_idx))
|
||||
continue;
|
||||
|
||||
const Line segment(thiss[line.poly_idx][line.point_idx], thiss[line.poly_idx][line_next_idx]);
|
||||
Point segment_closest_point;
|
||||
segment.distance_to_squared(pt, &segment_closest_point);
|
||||
|
||||
if (half_epsilon_sqrd >= (pt - segment_closest_point).cast<int64_t>().squaredNorm()) {
|
||||
const Point &other = thiss[poly_idx][(point_idx + 1) % pathlen];
|
||||
const Vec2i64 vec = (LinearAlg2D::pointIsLeftOfLine(other, segment.a, segment.b) > 0 ? segment.b - segment.a : segment.a - segment.b).cast<int64_t>();
|
||||
assert(Slic3r::sqr(double(vec.x())) < double(std::numeric_limits<int64_t>::max()));
|
||||
assert(Slic3r::sqr(double(vec.y())) < double(std::numeric_limits<int64_t>::max()));
|
||||
const int64_t len = vec.norm();
|
||||
pt.x() += (-vec.y() * move_dist) / len;
|
||||
pt.y() += (vec.x() * move_dist) / len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(thiss));
|
||||
}
|
||||
|
||||
/*!
|
||||
* Removes overlapping consecutive line segments which don't delimit a positive area.
|
||||
*/
|
||||
void removeDegenerateVerts(Polygons &thiss)
|
||||
{
|
||||
for (size_t poly_idx = 0; poly_idx < thiss.size(); poly_idx++) {
|
||||
Polygon &poly = thiss[poly_idx];
|
||||
Polygon result;
|
||||
|
||||
auto isDegenerate = [](const Point &last, const Point &now, const Point &next) {
|
||||
Vec2i64 last_line = (now - last).cast<int64_t>();
|
||||
Vec2i64 next_line = (next - now).cast<int64_t>();
|
||||
return last_line.dot(next_line) == -1 * last_line.norm() * next_line.norm();
|
||||
};
|
||||
bool isChanged = false;
|
||||
for (size_t idx = 0; idx < poly.size(); idx++) {
|
||||
const Point &last = (result.size() == 0) ? poly.back() : result.back();
|
||||
if (idx + 1 == poly.size() && result.size() == 0)
|
||||
break;
|
||||
|
||||
const Point &next = (idx + 1 == poly.size()) ? result[0] : poly[idx + 1];
|
||||
if (isDegenerate(last, poly[idx], next)) { // lines are in the opposite direction
|
||||
// don't add vert to the result
|
||||
isChanged = true;
|
||||
while (result.size() > 1 && isDegenerate(result[result.size() - 2], result.back(), next))
|
||||
result.points.pop_back();
|
||||
} else {
|
||||
result.points.emplace_back(poly[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isChanged) {
|
||||
if (result.size() > 2) {
|
||||
poly = result;
|
||||
} else {
|
||||
thiss.erase(thiss.begin() + poly_idx);
|
||||
poly_idx--; // effectively the next iteration has the same poly_idx (referring to a new poly which is not yet processed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeSmallAreas(Polygons &thiss, const double min_area_size, const bool remove_holes)
|
||||
{
|
||||
auto to_path = [](const Polygon &poly) -> ClipperLib::Path {
|
||||
ClipperLib::Path out;
|
||||
for (const Point &pt : poly.points)
|
||||
out.emplace_back(ClipperLib::cInt(pt.x()), ClipperLib::cInt(pt.y()));
|
||||
return out;
|
||||
};
|
||||
|
||||
auto new_end = thiss.end();
|
||||
if (remove_holes) {
|
||||
for (auto it = thiss.begin(); it < new_end;) {
|
||||
// All polygons smaller than target are removed by replacing them with a polygon from the back of the vector.
|
||||
if (fabs(ClipperLib::Area(to_path(*it))) < min_area_size) {
|
||||
--new_end;
|
||||
*it = std::move(*new_end);
|
||||
continue; // Don't increment the iterator such that the polygon just swapped in is checked next.
|
||||
}
|
||||
++it;
|
||||
}
|
||||
} else {
|
||||
// For each polygon, computes the signed area, move small outlines at the end of the vector and keep pointer on small holes
|
||||
std::vector<Polygon> small_holes;
|
||||
for (auto it = thiss.begin(); it < new_end;) {
|
||||
if (double area = ClipperLib::Area(to_path(*it)); fabs(area) < min_area_size) {
|
||||
if (area >= 0) {
|
||||
--new_end;
|
||||
if (it < new_end) {
|
||||
std::swap(*new_end, *it);
|
||||
continue;
|
||||
} else { // Don't self-swap the last Path
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
small_holes.push_back(*it);
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
// Removes small holes that have their first point inside one of the removed outlines
|
||||
// Iterating in reverse ensures that unprocessed small holes won't be moved
|
||||
const auto removed_outlines_start = new_end;
|
||||
for (auto hole_it = small_holes.rbegin(); hole_it < small_holes.rend(); hole_it++)
|
||||
for (auto outline_it = removed_outlines_start; outline_it < thiss.end(); outline_it++)
|
||||
if (Polygon(*outline_it).contains(*hole_it->begin())) {
|
||||
new_end--;
|
||||
*hole_it = std::move(*new_end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
thiss.resize(new_end-thiss.begin());
|
||||
}
|
||||
|
||||
void removeColinearEdges(Polygon &poly, const double max_deviation_angle)
|
||||
{
|
||||
// TODO: Can be made more efficient (for example, use pointer-types for process-/skip-indices, so we can swap them without copy).
|
||||
size_t num_removed_in_iteration = 0;
|
||||
do {
|
||||
num_removed_in_iteration = 0;
|
||||
std::vector<bool> process_indices(poly.points.size(), true);
|
||||
|
||||
bool go = true;
|
||||
while (go) {
|
||||
go = false;
|
||||
|
||||
const auto &rpath = poly;
|
||||
const size_t pathlen = rpath.size();
|
||||
if (pathlen <= 3)
|
||||
return;
|
||||
|
||||
std::vector<bool> skip_indices(poly.points.size(), false);
|
||||
|
||||
Polygon new_path;
|
||||
for (size_t point_idx = 0; point_idx < pathlen; ++point_idx) {
|
||||
// Don't iterate directly over process-indices, but do it this way, because there are points _in_ process-indices that should nonetheless
|
||||
// be skipped:
|
||||
if (!process_indices[point_idx]) {
|
||||
new_path.points.push_back(rpath[point_idx]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Should skip the last point for this iteration if the old first was removed (which can be seen from the fact that the new first was skipped):
|
||||
if (point_idx == (pathlen - 1) && skip_indices[0]) {
|
||||
skip_indices[new_path.size()] = true;
|
||||
go = true;
|
||||
new_path.points.push_back(rpath[point_idx]);
|
||||
break;
|
||||
}
|
||||
|
||||
const Point &prev = rpath[(point_idx - 1 + pathlen) % pathlen];
|
||||
const Point &pt = rpath[point_idx];
|
||||
const Point &next = rpath[(point_idx + 1) % pathlen];
|
||||
|
||||
float angle = LinearAlg2D::getAngleLeft(prev, pt, next); // [0 : 2 * pi]
|
||||
if (angle >= float(M_PI)) { angle -= float(M_PI); } // map [pi : 2 * pi] to [0 : pi]
|
||||
|
||||
// Check if the angle is within limits for the point to 'make sense', given the maximum deviation.
|
||||
// If the angle indicates near-parallel segments ignore the point 'pt'
|
||||
if (angle > max_deviation_angle && angle < M_PI - max_deviation_angle) {
|
||||
new_path.points.push_back(pt);
|
||||
} else if (point_idx != (pathlen - 1)) {
|
||||
// Skip the next point, since the current one was removed:
|
||||
skip_indices[new_path.size()] = true;
|
||||
go = true;
|
||||
new_path.points.push_back(next);
|
||||
++point_idx;
|
||||
}
|
||||
}
|
||||
poly = new_path;
|
||||
num_removed_in_iteration += pathlen - poly.points.size();
|
||||
|
||||
process_indices.clear();
|
||||
process_indices.insert(process_indices.end(), skip_indices.begin(), skip_indices.end());
|
||||
}
|
||||
} while (num_removed_in_iteration > 0);
|
||||
}
|
||||
|
||||
void removeColinearEdges(Polygons &thiss, const double max_deviation_angle = 0.0005)
|
||||
{
|
||||
for (int p = 0; p < int(thiss.size()); p++) {
|
||||
removeColinearEdges(thiss[p], max_deviation_angle);
|
||||
if (thiss[p].size() < 3) {
|
||||
thiss.erase(thiss.begin() + p);
|
||||
p--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<VariableWidthLines> &WallToolPaths::generate()
|
||||
{
|
||||
if (this->inset_count < 1)
|
||||
return toolpaths;
|
||||
|
||||
const coord_t smallest_segment = Slic3r::Arachne::meshfix_maximum_resolution;
|
||||
const coord_t allowed_distance = Slic3r::Arachne::meshfix_maximum_deviation;
|
||||
const coord_t epsilon_offset = (allowed_distance / 2) - 1;
|
||||
const double transitioning_angle = Geometry::deg2rad(m_params.wall_transition_angle);
|
||||
constexpr coord_t discretization_step_size = scaled<coord_t>(0.8);
|
||||
|
||||
// Simplify outline for boost::voronoi consumption. Absolutely no self intersections or near-self intersections allowed:
|
||||
// TODO: Open question: Does this indeed fix all (or all-but-one-in-a-million) cases for manifold but otherwise possibly complex polygons?
|
||||
Polygons prepared_outline = offset(offset(offset(outline, -epsilon_offset), epsilon_offset * 2), -epsilon_offset);
|
||||
simplify(prepared_outline, smallest_segment, allowed_distance);
|
||||
fixSelfIntersections(epsilon_offset, prepared_outline);
|
||||
removeDegenerateVerts(prepared_outline);
|
||||
removeColinearEdges(prepared_outline, 0.005);
|
||||
// Removing collinear edges may introduce self intersections, so we need to fix them again
|
||||
fixSelfIntersections(epsilon_offset, prepared_outline);
|
||||
removeDegenerateVerts(prepared_outline);
|
||||
removeSmallAreas(prepared_outline, small_area_length * small_area_length, false);
|
||||
|
||||
// The functions above could produce intersecting polygons that could cause a crash inside Arachne.
|
||||
// Applying Clipper union should be enough to get rid of this issue.
|
||||
// Clipper union also fixed an issue in Arachne that in post-processing Voronoi diagram, some edges
|
||||
// didn't have twin edges. (a non-planar Voronoi diagram probably caused this).
|
||||
prepared_outline = union_(prepared_outline);
|
||||
|
||||
if (area(prepared_outline) <= 0) {
|
||||
assert(toolpaths.empty());
|
||||
return toolpaths;
|
||||
}
|
||||
|
||||
const float external_perimeter_extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(unscale<float>(bead_width_0), float(this->layer_height));
|
||||
const float perimeter_extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(unscale<float>(bead_width_x), float(this->layer_height));
|
||||
|
||||
const coord_t wall_transition_length = scaled<coord_t>(this->m_params.wall_transition_length);
|
||||
|
||||
const double wall_split_middle_threshold = std::clamp(2. * unscaled<double>(this->min_bead_width) / external_perimeter_extrusion_width - 1., 0.01, 0.99); // For an uneven nr. of lines: When to split the middle wall into two.
|
||||
const double wall_add_middle_threshold = std::clamp(unscaled<double>(this->min_bead_width) / perimeter_extrusion_width, 0.01, 0.99); // For an even nr. of lines: When to add a new middle in between the innermost two walls.
|
||||
|
||||
const int wall_distribution_count = this->m_params.wall_distribution_count;
|
||||
const size_t max_bead_count = (inset_count < std::numeric_limits<coord_t>::max() / 2) ? 2 * inset_count : std::numeric_limits<coord_t>::max();
|
||||
const auto beading_strat = BeadingStrategyFactory::makeStrategy
|
||||
(
|
||||
bead_width_0,
|
||||
bead_width_x,
|
||||
wall_transition_length,
|
||||
transitioning_angle,
|
||||
print_thin_walls,
|
||||
min_bead_width,
|
||||
min_feature_size,
|
||||
wall_split_middle_threshold,
|
||||
wall_add_middle_threshold,
|
||||
max_bead_count,
|
||||
wall_0_inset,
|
||||
wall_distribution_count
|
||||
);
|
||||
const coord_t transition_filter_dist = scaled<coord_t>(100.f);
|
||||
const coord_t allowed_filter_deviation = wall_transition_filter_deviation;
|
||||
SkeletalTrapezoidation wall_maker
|
||||
(
|
||||
prepared_outline,
|
||||
*beading_strat,
|
||||
beading_strat->getTransitioningAngle(),
|
||||
discretization_step_size,
|
||||
transition_filter_dist,
|
||||
allowed_filter_deviation,
|
||||
wall_transition_length
|
||||
);
|
||||
wall_maker.generateToolpaths(toolpaths);
|
||||
|
||||
stitchToolPaths(toolpaths, this->bead_width_x);
|
||||
|
||||
removeSmallLines(toolpaths);
|
||||
|
||||
separateOutInnerContour();
|
||||
|
||||
simplifyToolPaths(toolpaths);
|
||||
|
||||
removeEmptyToolPaths(toolpaths);
|
||||
assert(std::is_sorted(toolpaths.cbegin(), toolpaths.cend(),
|
||||
[](const VariableWidthLines& l, const VariableWidthLines& r)
|
||||
{
|
||||
return l.front().inset_idx < r.front().inset_idx;
|
||||
}) && "WallToolPaths should be sorted from the outer 0th to inner_walls");
|
||||
toolpaths_generated = true;
|
||||
return toolpaths;
|
||||
}
|
||||
|
||||
void WallToolPaths::stitchToolPaths(std::vector<VariableWidthLines> &toolpaths, const coord_t bead_width_x)
|
||||
{
|
||||
const coord_t stitch_distance = bead_width_x - 1; //In 0-width contours, junctions can cause up to 1-line-width gaps. Don't stitch more than 1 line width.
|
||||
|
||||
for (unsigned int wall_idx = 0; wall_idx < toolpaths.size(); wall_idx++) {
|
||||
VariableWidthLines& wall_lines = toolpaths[wall_idx];
|
||||
|
||||
VariableWidthLines stitched_polylines;
|
||||
VariableWidthLines closed_polygons;
|
||||
PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::stitch(wall_lines, stitched_polylines, closed_polygons, stitch_distance);
|
||||
#ifdef ARACHNE_STITCH_PATCH_DEBUG
|
||||
for (const ExtrusionLine& line : stitched_polylines) {
|
||||
if ( ! line.is_odd && line.polylineLength() > 3 * stitch_distance && line.size() > 3) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Some even contour lines could not be closed into polygons!";
|
||||
assert(false && "Some even contour lines could not be closed into polygons!");
|
||||
BoundingBox aabb;
|
||||
for (auto line2 : wall_lines)
|
||||
for (auto j : line2)
|
||||
aabb.merge(j.p);
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("contours_before.svg-%d.png", iRun), aabb);
|
||||
std::array<const char *, 8> colors = {"gray", "black", "blue", "green", "lime", "purple", "red", "yellow"};
|
||||
size_t color_idx = 0;
|
||||
for (auto& inset : toolpaths)
|
||||
for (auto& line2 : inset) {
|
||||
// svg.writePolyline(line2.toPolygon(), col);
|
||||
|
||||
Polygon poly = line2.toPolygon();
|
||||
Point last = poly.front();
|
||||
for (size_t idx = 1 ; idx < poly.size(); idx++) {
|
||||
Point here = poly[idx];
|
||||
svg.draw(Line(last, here), colors[color_idx]);
|
||||
// svg.draw_text((last + here) / 2, std::to_string(line2.junctions[idx].region_id).c_str(), "black");
|
||||
last = here;
|
||||
}
|
||||
svg.draw(poly[0], colors[color_idx]);
|
||||
// svg.nextLayer();
|
||||
// svg.writePoints(poly, true, 0.1);
|
||||
// svg.nextLayer();
|
||||
color_idx = (color_idx + 1) % colors.size();
|
||||
}
|
||||
}
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("contours-%d.svg", iRun), aabb);
|
||||
for (auto& inset : toolpaths)
|
||||
for (auto& line2 : inset)
|
||||
svg.draw_outline(line2.toPolygon(), "gray");
|
||||
for (auto& line2 : stitched_polylines) {
|
||||
const char *col = line2.is_odd ? "gray" : "red";
|
||||
if ( ! line2.is_odd)
|
||||
std::cerr << "Non-closed even wall of size: " << line2.size() << " at " << line2.front().p << "\n";
|
||||
if ( ! line2.is_odd)
|
||||
svg.draw(line2.front().p);
|
||||
Polygon poly = line2.toPolygon();
|
||||
Point last = poly.front();
|
||||
for (size_t idx = 1 ; idx < poly.size(); idx++)
|
||||
{
|
||||
Point here = poly[idx];
|
||||
svg.draw(Line(last, here), col);
|
||||
last = here;
|
||||
}
|
||||
}
|
||||
for (auto line2 : closed_polygons)
|
||||
svg.draw(line2.toPolygon());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ARACHNE_STITCH_PATCH_DEBUG
|
||||
wall_lines = stitched_polylines; // replace input toolpaths with stitched polylines
|
||||
|
||||
for (ExtrusionLine& wall_polygon : closed_polygons)
|
||||
{
|
||||
if (wall_polygon.junctions.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// PolylineStitcher, in some cases, produced closed extrusion (polygons),
|
||||
// but the endpoints differ by a small distance. So we reconnect them.
|
||||
// FIXME Lukas H.: Investigate more deeply why it is happening.
|
||||
if (wall_polygon.junctions.front().p != wall_polygon.junctions.back().p &&
|
||||
(wall_polygon.junctions.back().p - wall_polygon.junctions.front().p).cast<double>().norm() < stitch_distance) {
|
||||
wall_polygon.junctions.emplace_back(wall_polygon.junctions.front());
|
||||
}
|
||||
wall_polygon.is_closed = true;
|
||||
wall_lines.emplace_back(std::move(wall_polygon)); // add stitched polygons to result
|
||||
}
|
||||
#ifdef DEBUG
|
||||
for (ExtrusionLine& line : wall_lines)
|
||||
{
|
||||
assert(line.inset_idx == wall_idx);
|
||||
}
|
||||
#endif // DEBUG
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T> bool shorterThan(const T &shape, const coord_t check_length)
|
||||
{
|
||||
const auto *p0 = &shape.back();
|
||||
int64_t length = 0;
|
||||
for (const auto &p1 : shape) {
|
||||
length += (*p0 - p1).template cast<int64_t>().norm();
|
||||
if (length >= check_length)
|
||||
return false;
|
||||
p0 = &p1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WallToolPaths::removeSmallLines(std::vector<VariableWidthLines> &toolpaths)
|
||||
{
|
||||
for (VariableWidthLines &inset : toolpaths) {
|
||||
for (size_t line_idx = 0; line_idx < inset.size(); line_idx++) {
|
||||
ExtrusionLine &line = inset[line_idx];
|
||||
coord_t min_width = std::numeric_limits<coord_t>::max();
|
||||
for (const ExtrusionJunction &j : line)
|
||||
min_width = std::min(min_width, j.w);
|
||||
if (line.is_odd && !line.is_closed && shorterThan(line, min_width / 2)) { // remove line
|
||||
line = std::move(inset.back());
|
||||
inset.erase(--inset.end());
|
||||
line_idx--; // reconsider the current position
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WallToolPaths::simplifyToolPaths(std::vector<VariableWidthLines> &toolpaths)
|
||||
{
|
||||
for (size_t toolpaths_idx = 0; toolpaths_idx < toolpaths.size(); ++toolpaths_idx)
|
||||
{
|
||||
const int64_t maximum_resolution = Slic3r::Arachne::meshfix_maximum_resolution;
|
||||
const int64_t maximum_deviation = Slic3r::Arachne::meshfix_maximum_deviation;
|
||||
const int64_t maximum_extrusion_area_deviation = Slic3r::Arachne::meshfix_maximum_extrusion_area_deviation; // unit: μm²
|
||||
for (auto& line : toolpaths[toolpaths_idx])
|
||||
{
|
||||
line.simplify(maximum_resolution * maximum_resolution, maximum_deviation * maximum_deviation, maximum_extrusion_area_deviation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<VariableWidthLines> &WallToolPaths::getToolPaths()
|
||||
{
|
||||
if (!toolpaths_generated)
|
||||
return generate();
|
||||
return toolpaths;
|
||||
}
|
||||
|
||||
void WallToolPaths::separateOutInnerContour()
|
||||
{
|
||||
//We'll remove all 0-width paths from the original toolpaths and store them separately as polygons.
|
||||
std::vector<VariableWidthLines> actual_toolpaths;
|
||||
actual_toolpaths.reserve(toolpaths.size()); //A bit too much, but the correct order of magnitude.
|
||||
std::vector<VariableWidthLines> contour_paths;
|
||||
contour_paths.reserve(toolpaths.size() / inset_count);
|
||||
inner_contour.clear();
|
||||
for (const VariableWidthLines &inset : toolpaths) {
|
||||
if (inset.empty())
|
||||
continue;
|
||||
bool is_contour = false;
|
||||
for (const ExtrusionLine &line : inset) {
|
||||
for (const ExtrusionJunction &j : line) {
|
||||
if (j.w == 0)
|
||||
is_contour = true;
|
||||
else
|
||||
is_contour = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_contour) {
|
||||
#ifdef DEBUG
|
||||
for (const ExtrusionLine &line : inset)
|
||||
for (const ExtrusionJunction &j : line)
|
||||
assert(j.w == 0);
|
||||
#endif // DEBUG
|
||||
for (const ExtrusionLine &line : inset) {
|
||||
if (line.is_odd)
|
||||
continue; // odd lines don't contribute to the contour
|
||||
else if (line.is_closed) // sometimes an very small even polygonal wall is not stitched into a polygon
|
||||
inner_contour.emplace_back(line.toPolygon());
|
||||
}
|
||||
} else {
|
||||
actual_toolpaths.emplace_back(inset);
|
||||
}
|
||||
}
|
||||
if (!actual_toolpaths.empty())
|
||||
toolpaths = std::move(actual_toolpaths); // Filtered out the 0-width paths.
|
||||
else
|
||||
toolpaths.clear();
|
||||
|
||||
//The output walls from the skeletal trapezoidation have no known winding order, especially if they are joined together from polylines.
|
||||
//They can be in any direction, clockwise or counter-clockwise, regardless of whether the shapes are positive or negative.
|
||||
//To get a correct shape, we need to make the outside contour positive and any holes inside negative.
|
||||
//This can be done by applying the even-odd rule to the shape. This rule is not sensitive to the winding order of the polygon.
|
||||
//The even-odd rule would be incorrect if the polygon self-intersects, but that should never be generated by the skeletal trapezoidation.
|
||||
inner_contour = union_(inner_contour, ClipperLib::PolyFillType::pftEvenOdd);
|
||||
}
|
||||
|
||||
const Polygons& WallToolPaths::getInnerContour()
|
||||
{
|
||||
if (!toolpaths_generated && inset_count > 0)
|
||||
{
|
||||
generate();
|
||||
}
|
||||
else if(inset_count == 0)
|
||||
{
|
||||
return outline;
|
||||
}
|
||||
return inner_contour;
|
||||
}
|
||||
|
||||
bool WallToolPaths::removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths)
|
||||
{
|
||||
toolpaths.erase(std::remove_if(toolpaths.begin(), toolpaths.end(), [](const VariableWidthLines& lines)
|
||||
{
|
||||
return lines.empty();
|
||||
}), toolpaths.end());
|
||||
return toolpaths.empty();
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the order constraints of the insets when printing walls per region / hole.
|
||||
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
|
||||
*
|
||||
* Odd walls should always go after their enclosing wall polygons.
|
||||
*
|
||||
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
|
||||
*/
|
||||
std::unordered_set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>> WallToolPaths::getRegionOrder(const std::vector<ExtrusionLine *> &input, const bool outer_to_inner)
|
||||
{
|
||||
std::unordered_set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>> order_requirements;
|
||||
|
||||
// We build a grid where we map toolpath vertex locations to toolpaths,
|
||||
// so that we can easily find which two toolpaths are next to each other,
|
||||
// which is the requirement for there to be an order constraint.
|
||||
//
|
||||
// We use a PointGrid rather than a LineGrid to save on computation time.
|
||||
// In very rare cases two insets might lie next to each other without having neighboring vertices, e.g.
|
||||
// \ .
|
||||
// | / .
|
||||
// | / .
|
||||
// || .
|
||||
// | \ .
|
||||
// | \ .
|
||||
// / .
|
||||
// However, because of how Arachne works this will likely never be the case for two consecutive insets.
|
||||
// On the other hand one could imagine that two consecutive insets of a very large circle
|
||||
// could be simplify()ed such that the remaining vertices of the two insets don't align.
|
||||
// In those cases the order requirement is not captured,
|
||||
// which means that the PathOrderOptimizer *might* result in a violation of the user set path order.
|
||||
// This problem is expected to be not so severe and happen very sparsely.
|
||||
|
||||
coord_t max_line_w = 0u;
|
||||
for (const ExtrusionLine *line : input) // compute max_line_w
|
||||
for (const ExtrusionJunction &junction : *line)
|
||||
max_line_w = std::max(max_line_w, junction.w);
|
||||
if (max_line_w == 0u)
|
||||
return order_requirements;
|
||||
|
||||
struct LineLoc
|
||||
{
|
||||
ExtrusionJunction j;
|
||||
const ExtrusionLine *line;
|
||||
};
|
||||
struct Locator
|
||||
{
|
||||
Point operator()(const LineLoc &elem) { return elem.j.p; }
|
||||
};
|
||||
|
||||
// How much farther two verts may be apart due to corners.
|
||||
// This distance must be smaller than 2, because otherwise
|
||||
// we could create an order requirement between e.g.
|
||||
// wall 2 of one region and wall 3 of another region,
|
||||
// while another wall 3 of the first region would lie in between those two walls.
|
||||
// However, higher values are better against the limitations of using a PointGrid rather than a LineGrid.
|
||||
constexpr float diagonal_extension = 1.9f;
|
||||
const auto searching_radius = coord_t(max_line_w * diagonal_extension);
|
||||
using GridT = SparsePointGrid<LineLoc, Locator>;
|
||||
GridT grid(searching_radius);
|
||||
|
||||
for (const ExtrusionLine *line : input)
|
||||
for (const ExtrusionJunction &junction : *line) grid.insert(LineLoc{junction, line});
|
||||
for (const std::pair<const SquareGrid::GridPoint, LineLoc> &pair : grid) {
|
||||
const LineLoc &lineloc_here = pair.second;
|
||||
const ExtrusionLine *here = lineloc_here.line;
|
||||
Point loc_here = pair.second.j.p;
|
||||
std::vector<LineLoc> nearby_verts = grid.getNearby(loc_here, searching_radius);
|
||||
for (const LineLoc &lineloc_nearby : nearby_verts) {
|
||||
const ExtrusionLine *nearby = lineloc_nearby.line;
|
||||
if (nearby == here)
|
||||
continue;
|
||||
if (nearby->inset_idx == here->inset_idx)
|
||||
continue;
|
||||
if (nearby->inset_idx > here->inset_idx + 1)
|
||||
continue; // not directly adjacent
|
||||
if (here->inset_idx > nearby->inset_idx + 1)
|
||||
continue; // not directly adjacent
|
||||
if (!shorter_then(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension))
|
||||
continue; // points are too far away from each other
|
||||
if (here->is_odd || nearby->is_odd) {
|
||||
if (here->is_odd && !nearby->is_odd && nearby->inset_idx < here->inset_idx)
|
||||
order_requirements.emplace(std::make_pair(nearby, here));
|
||||
if (nearby->is_odd && !here->is_odd && here->inset_idx < nearby->inset_idx)
|
||||
order_requirements.emplace(std::make_pair(here, nearby));
|
||||
} else if ((nearby->inset_idx < here->inset_idx) == outer_to_inner) {
|
||||
order_requirements.emplace(std::make_pair(nearby, here));
|
||||
} else {
|
||||
assert((nearby->inset_idx > here->inset_idx) == outer_to_inner);
|
||||
order_requirements.emplace(std::make_pair(here, nearby));
|
||||
}
|
||||
}
|
||||
}
|
||||
return order_requirements;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
138
src/libslic3r/Arachne/WallToolPaths.hpp
Normal file
138
src/libslic3r/Arachne/WallToolPaths.hpp
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2020 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef CURAENGINE_WALLTOOLPATHS_H
|
||||
#define CURAENGINE_WALLTOOLPATHS_H
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "BeadingStrategy/BeadingStrategyFactory.hpp"
|
||||
#include "utils/ExtrusionLine.hpp"
|
||||
#include "../Polygon.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
constexpr bool fill_outline_gaps = true;
|
||||
constexpr coord_t meshfix_maximum_resolution = scaled<coord_t>(0.5);
|
||||
constexpr coord_t meshfix_maximum_deviation = scaled<coord_t>(0.025);
|
||||
constexpr coord_t meshfix_maximum_extrusion_area_deviation = scaled<coord_t>(2.);
|
||||
|
||||
class WallToolPathsParams
|
||||
{
|
||||
public:
|
||||
float min_bead_width;
|
||||
float min_feature_size;
|
||||
float wall_transition_length;
|
||||
float wall_transition_angle;
|
||||
float wall_transition_filter_deviation;
|
||||
int wall_distribution_count;
|
||||
};
|
||||
|
||||
class WallToolPaths
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* A class that creates the toolpaths given an outline, nominal bead width and maximum amount of walls
|
||||
* \param outline An outline of the area in which the ToolPaths are to be generated
|
||||
* \param bead_width_0 The bead width of the first wall used in the generation of the toolpaths
|
||||
* \param bead_width_x The bead width of the inner walls used in the generation of the toolpaths
|
||||
* \param inset_count The maximum number of parallel extrusion lines that make up the wall
|
||||
* \param wall_0_inset How far to inset the outer wall, to make it adhere better to other walls.
|
||||
*/
|
||||
WallToolPaths(const Polygons& outline, coord_t bead_width_0, coord_t bead_width_x, size_t inset_count, coord_t wall_0_inset, coordf_t layer_height, const WallToolPathsParams ¶ms);
|
||||
|
||||
/*!
|
||||
* Generates the Toolpaths
|
||||
* \return A reference to the newly create ToolPaths
|
||||
*/
|
||||
const std::vector<VariableWidthLines> &generate();
|
||||
|
||||
/*!
|
||||
* Gets the toolpaths, if this called before \p generate() it will first generate the Toolpaths
|
||||
* \return a reference to the toolpaths
|
||||
*/
|
||||
const std::vector<VariableWidthLines> &getToolPaths();
|
||||
|
||||
/*!
|
||||
* Compute the inner contour of the walls. This contour indicates where the walled area ends and its infill begins.
|
||||
* The inside can then be filled, e.g. with skin/infill for the walls of a part, or with a pattern in the case of
|
||||
* infill with extra infill walls.
|
||||
*/
|
||||
void separateOutInnerContour();
|
||||
|
||||
/*!
|
||||
* Gets the inner contour of the area which is inside of the generated tool
|
||||
* paths.
|
||||
*
|
||||
* If the walls haven't been generated yet, this will lazily call the
|
||||
* \p generate() function to generate the walls with variable width.
|
||||
* The resulting polygon will snugly match the inside of the variable-width
|
||||
* walls where the walls get limited by the LimitedBeadingStrategy to a
|
||||
* maximum wall count.
|
||||
* If there are no walls, the outline will be returned.
|
||||
* \return The inner contour of the generated walls.
|
||||
*/
|
||||
const Polygons& getInnerContour();
|
||||
|
||||
/*!
|
||||
* Removes empty paths from the toolpaths
|
||||
* \param toolpaths the VariableWidthPaths generated with \p generate()
|
||||
* \return true if there are still paths left. If all toolpaths were removed it returns false
|
||||
*/
|
||||
static bool removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths);
|
||||
|
||||
/*!
|
||||
* Get the order constraints of the insets when printing walls per region / hole.
|
||||
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
|
||||
*
|
||||
* Odd walls should always go after their enclosing wall polygons.
|
||||
*
|
||||
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
|
||||
*/
|
||||
static std::unordered_set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>> getRegionOrder(const std::vector<ExtrusionLine *> &input, bool outer_to_inner);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Stitch the polylines together and form closed polygons.
|
||||
*
|
||||
* Works on both toolpaths and inner contours simultaneously.
|
||||
*/
|
||||
static void stitchToolPaths(std::vector<VariableWidthLines> &toolpaths, coord_t bead_width_x);
|
||||
|
||||
/*!
|
||||
* Remove polylines shorter than half the smallest line width along that polyline.
|
||||
*/
|
||||
static void removeSmallLines(std::vector<VariableWidthLines> &toolpaths);
|
||||
|
||||
/*!
|
||||
* Simplifies the variable-width toolpaths by calling the simplify on every line in the toolpath using the provided
|
||||
* settings.
|
||||
* \param settings The settings as provided by the user
|
||||
* \return
|
||||
*/
|
||||
static void simplifyToolPaths(std::vector<VariableWidthLines> &toolpaths);
|
||||
|
||||
private:
|
||||
const Polygons& outline; //<! A reference to the outline polygon that is the designated area
|
||||
coord_t bead_width_0; //<! The nominal or first extrusion line width with which libArachne generates its walls
|
||||
coord_t bead_width_x; //<! The subsequently extrusion line width with which libArachne generates its walls if WallToolPaths was called with the nominal_bead_width Constructor this is the same as bead_width_0
|
||||
size_t inset_count; //<! The maximum number of walls to generate
|
||||
coord_t wall_0_inset; //<! How far to inset the outer wall. Should only be applied when printing the actual walls, not extra infill/skin/support walls.
|
||||
coordf_t layer_height;
|
||||
bool print_thin_walls; //<! Whether to enable the widening beading meta-strategy for thin features
|
||||
coord_t min_feature_size; //<! The minimum size of the features that can be widened by the widening beading meta-strategy. Features thinner than that will not be printed
|
||||
coord_t min_bead_width; //<! The minimum bead size to use when widening thin model features with the widening beading meta-strategy
|
||||
double small_area_length; //<! The length of the small features which are to be filtered out, this is squared into a surface
|
||||
coord_t wall_transition_filter_deviation; //!< The allowed line width deviation induced by filtering
|
||||
bool toolpaths_generated; //<! Are the toolpaths generated
|
||||
std::vector<VariableWidthLines> toolpaths; //<! The generated toolpaths
|
||||
Polygons inner_contour; //<! The inner contour of the generated toolpaths
|
||||
const WallToolPathsParams m_params;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // CURAENGINE_WALLTOOLPATHS_H
|
||||
18
src/libslic3r/Arachne/utils/ExtrusionJunction.cpp
Normal file
18
src/libslic3r/Arachne/utils/ExtrusionJunction.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "ExtrusionJunction.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
bool ExtrusionJunction::operator ==(const ExtrusionJunction& other) const
|
||||
{
|
||||
return p == other.p
|
||||
&& w == other.w
|
||||
&& perimeter_index == other.perimeter_index;
|
||||
}
|
||||
|
||||
ExtrusionJunction::ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index) : p(p), w(w), perimeter_index(perimeter_index) {}
|
||||
|
||||
}
|
||||
59
src/libslic3r/Arachne/utils/ExtrusionJunction.hpp
Normal file
59
src/libslic3r/Arachne/utils/ExtrusionJunction.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
#ifndef UTILS_EXTRUSION_JUNCTION_H
|
||||
#define UTILS_EXTRUSION_JUNCTION_H
|
||||
|
||||
#include "../../Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* This struct represents one vertex in an extruded path.
|
||||
*
|
||||
* It contains information on how wide the extruded path must be at this point,
|
||||
* and which perimeter it represents.
|
||||
*/
|
||||
struct ExtrusionJunction
|
||||
{
|
||||
/*!
|
||||
* The position of the centreline of the path when it reaches this junction.
|
||||
* This is the position that should end up in the g-code eventually.
|
||||
*/
|
||||
Point p;
|
||||
|
||||
/*!
|
||||
* The width of the extruded path at this junction.
|
||||
*/
|
||||
coord_t w;
|
||||
|
||||
/*!
|
||||
* Which perimeter this junction is part of.
|
||||
*
|
||||
* Perimeters are counted from the outside inwards. The outer wall has index
|
||||
* 0.
|
||||
*/
|
||||
size_t perimeter_index;
|
||||
|
||||
ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index);
|
||||
|
||||
bool operator==(const ExtrusionJunction& other) const;
|
||||
};
|
||||
|
||||
inline Point operator-(const ExtrusionJunction& a, const ExtrusionJunction& b)
|
||||
{
|
||||
return a.p - b.p;
|
||||
}
|
||||
|
||||
// Identity function, used to be able to make templated algorithms that do their operations on 'point-like' input.
|
||||
inline const Point& make_point(const ExtrusionJunction& ej)
|
||||
{
|
||||
return ej.p;
|
||||
}
|
||||
|
||||
using LineJunctions = std::vector<ExtrusionJunction>; //<! The junctions along a line without further information. See \ref ExtrusionLine for a more extensive class.
|
||||
|
||||
}
|
||||
#endif // UTILS_EXTRUSION_JUNCTION_H
|
||||
280
src/libslic3r/Arachne/utils/ExtrusionLine.cpp
Normal file
280
src/libslic3r/Arachne/utils/ExtrusionLine.cpp
Normal file
@@ -0,0 +1,280 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ExtrusionLine.hpp"
|
||||
#include "linearAlg2D.hpp"
|
||||
#include "../../VariableWidth.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
ExtrusionLine::ExtrusionLine(const size_t inset_idx, const bool is_odd) : inset_idx(inset_idx), is_odd(is_odd), is_closed(false) {}
|
||||
|
||||
int64_t ExtrusionLine::getLength() const
|
||||
{
|
||||
if (junctions.empty())
|
||||
return 0;
|
||||
|
||||
int64_t len = 0;
|
||||
ExtrusionJunction prev = junctions.front();
|
||||
for (const ExtrusionJunction &next : junctions) {
|
||||
len += (next.p - prev.p).cast<int64_t>().norm();
|
||||
prev = next;
|
||||
}
|
||||
if (is_closed)
|
||||
len += (front().p - back().p).cast<int64_t>().norm();
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
coord_t ExtrusionLine::getMinimalWidth() const
|
||||
{
|
||||
return std::min_element(junctions.cbegin(), junctions.cend(),
|
||||
[](const ExtrusionJunction& l, const ExtrusionJunction& r)
|
||||
{
|
||||
return l.w < r.w;
|
||||
})->w;
|
||||
}
|
||||
|
||||
void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared, const int64_t maximum_extrusion_area_deviation)
|
||||
{
|
||||
const size_t min_path_size = is_closed ? 3 : 2;
|
||||
if (junctions.size() <= min_path_size)
|
||||
return;
|
||||
|
||||
// TODO: allow for the first point to be removed in case of simplifying closed Extrusionlines.
|
||||
|
||||
/* ExtrusionLines are treated as (open) polylines, so in case an ExtrusionLine is actually a closed polygon, its
|
||||
* starting and ending points will be equal (or almost equal). Therefore, the simplification of the ExtrusionLine
|
||||
* should not touch the first and last points. As a result, start simplifying from point at index 1.
|
||||
* */
|
||||
std::vector<ExtrusionJunction> new_junctions;
|
||||
// Starting junction should always exist in the simplified path
|
||||
new_junctions.emplace_back(junctions.front());
|
||||
|
||||
/* Initially, previous_previous is always the same as previous because, for open ExtrusionLines the last junction
|
||||
* cannot be taken into consideration when checking the points at index 1. For closed ExtrusionLines, the first and
|
||||
* last junctions are anyway the same.
|
||||
* */
|
||||
ExtrusionJunction previous_previous = junctions.front();
|
||||
ExtrusionJunction previous = junctions.front();
|
||||
|
||||
/* When removing a vertex, we check the height of the triangle of the area
|
||||
being removed from the original polygon by the simplification. However,
|
||||
when consecutively removing multiple vertices the height of the previously
|
||||
removed vertices w.r.t. the shortcut path changes.
|
||||
In order to not recompute the new height value of previously removed
|
||||
vertices we compute the height of a representative triangle, which covers
|
||||
the same amount of area as the area being cut off. We use the Shoelace
|
||||
formula to accumulate the area under the removed segments. This works by
|
||||
computing the area in a 'fan' where each of the blades of the fan go from
|
||||
the origin to one of the segments. While removing vertices the area in
|
||||
this fan accumulates. By subtracting the area of the blade connected to
|
||||
the short-cutting segment we obtain the total area of the cutoff region.
|
||||
From this area we compute the height of the representative triangle using
|
||||
the standard formula for a triangle area: A = .5*b*h
|
||||
*/
|
||||
const ExtrusionJunction& initial = junctions.at(1);
|
||||
int64_t accumulated_area_removed = int64_t(previous.p.x()) * int64_t(initial.p.y()) - int64_t(previous.p.y()) * int64_t(initial.p.x()); // Twice the Shoelace formula for area of polygon per line segment.
|
||||
|
||||
for (size_t point_idx = 1; point_idx < junctions.size() - 1; point_idx++)
|
||||
{
|
||||
const ExtrusionJunction& current = junctions[point_idx];
|
||||
|
||||
// Spill over in case of overflow, unless the [next] vertex will then be equal to [previous].
|
||||
const bool spill_over = point_idx + 1 == junctions.size() && new_junctions.size() > 1;
|
||||
ExtrusionJunction& next = spill_over ? new_junctions[0] : junctions[point_idx + 1];
|
||||
|
||||
const int64_t removed_area_next = int64_t(current.p.x()) * int64_t(next.p.y()) - int64_t(current.p.y()) * int64_t(next.p.x()); // Twice the Shoelace formula for area of polygon per line segment.
|
||||
const int64_t negative_area_closing = int64_t(next.p.x()) * int64_t(previous.p.y()) - int64_t(next.p.y()) * int64_t(previous.p.x()); // Area between the origin and the short-cutting segment
|
||||
accumulated_area_removed += removed_area_next;
|
||||
|
||||
const int64_t length2 = (current - previous).cast<int64_t>().squaredNorm();
|
||||
if (length2 < scaled<coord_t>(0.025))
|
||||
{
|
||||
// We're allowed to always delete segments of less than 5 micron. The width in this case doesn't matter that much.
|
||||
continue;
|
||||
}
|
||||
|
||||
const int64_t area_removed_so_far = accumulated_area_removed + negative_area_closing; // Close the shortcut area polygon
|
||||
const int64_t base_length_2 = (next - previous).cast<int64_t>().squaredNorm();
|
||||
|
||||
if (base_length_2 == 0) // Two line segments form a line back and forth with no area.
|
||||
{
|
||||
continue; // Remove the junction (vertex).
|
||||
}
|
||||
//We want to check if the height of the triangle formed by previous, current and next vertices is less than allowed_error_distance_squared.
|
||||
//1/2 L = A [actual area is half of the computed shoelace value] // Shoelace formula is .5*(...) , but we simplify the computation and take out the .5
|
||||
//A = 1/2 * b * h [triangle area formula]
|
||||
//L = b * h [apply above two and take out the 1/2]
|
||||
//h = L / b [divide by b]
|
||||
//h^2 = (L / b)^2 [square it]
|
||||
//h^2 = L^2 / b^2 [factor the divisor]
|
||||
const auto height_2 = int64_t(double(area_removed_so_far) * double(area_removed_so_far) / double(base_length_2));
|
||||
coord_t weighted_average_width;
|
||||
const int64_t extrusion_area_error = calculateExtrusionAreaDeviationError(previous, current, next, weighted_average_width);
|
||||
if ((height_2 <= scaled<coord_t>(0.001) //Almost exactly colinear (barring rounding errors).
|
||||
&& Line::distance_to_infinite(current.p, previous.p, next.p) <= scaled<double>(0.001)) // Make sure that height_2 is not small because of cancellation of positive and negative areas
|
||||
// We shouldn't remove middle junctions of colinear segments if the area changed for the C-P segment is exceeding the maximum allowed
|
||||
&& extrusion_area_error <= maximum_extrusion_area_deviation)
|
||||
{
|
||||
// Remove the current junction (vertex).
|
||||
continue;
|
||||
}
|
||||
|
||||
if (length2 < smallest_line_segment_squared
|
||||
&& height_2 <= allowed_error_distance_squared) // Removing the junction (vertex) doesn't introduce too much error.
|
||||
{
|
||||
const int64_t next_length2 = (current - next).cast<int64_t>().squaredNorm();
|
||||
if (next_length2 > 4 * smallest_line_segment_squared)
|
||||
{
|
||||
// Special case; The next line is long. If we were to remove this, it could happen that we get quite noticeable artifacts.
|
||||
// We should instead move this point to a location where both edges are kept and then remove the previous point that we wanted to keep.
|
||||
// By taking the intersection of these two lines, we get a point that preserves the direction (so it makes the corner a bit more pointy).
|
||||
// We just need to be sure that the intersection point does not introduce an artifact itself.
|
||||
Point intersection_point;
|
||||
bool has_intersection = Line(previous_previous.p, previous.p).intersection_infinite(Line(current.p, next.p), &intersection_point);
|
||||
if (!has_intersection
|
||||
|| Line::distance_to_infinite_squared(intersection_point, previous.p, current.p) > double(allowed_error_distance_squared)
|
||||
|| (intersection_point - previous.p).cast<int64_t>().squaredNorm() > smallest_line_segment_squared // The intersection point is way too far from the 'previous'
|
||||
|| (intersection_point - next.p).cast<int64_t>().squaredNorm() > smallest_line_segment_squared) // and 'next' points, so it shouldn't replace 'current'
|
||||
{
|
||||
// We can't find a better spot for it, but the size of the line is more than 5 micron.
|
||||
// So the only thing we can do here is leave it in...
|
||||
}
|
||||
else
|
||||
{
|
||||
// New point seems like a valid one.
|
||||
const ExtrusionJunction new_to_add = ExtrusionJunction(intersection_point, current.w, current.perimeter_index);
|
||||
// If there was a previous point added, remove it.
|
||||
if(!new_junctions.empty())
|
||||
{
|
||||
new_junctions.pop_back();
|
||||
previous = previous_previous;
|
||||
}
|
||||
|
||||
// The junction (vertex) is replaced by the new one.
|
||||
accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
|
||||
previous_previous = previous;
|
||||
previous = new_to_add; // Note that "previous" is only updated if we don't remove the junction (vertex).
|
||||
new_junctions.push_back(new_to_add);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
continue; // Remove the junction (vertex).
|
||||
}
|
||||
}
|
||||
// The junction (vertex) isn't removed.
|
||||
accumulated_area_removed = removed_area_next; // So that in the next iteration it's the area between the origin, [previous] and [current]
|
||||
previous_previous = previous;
|
||||
previous = current; // Note that "previous" is only updated if we don't remove the junction (vertex).
|
||||
new_junctions.push_back(current);
|
||||
}
|
||||
|
||||
// Ending junction (vertex) should always exist in the simplified path
|
||||
new_junctions.emplace_back(junctions.back());
|
||||
|
||||
/* In case this is a closed polygon (instead of a poly-line-segments), the invariant that the first and last points are the same should be enforced.
|
||||
* Since one of them didn't move, and the other can't have been moved further than the constraints, if originally equal, they can simply be equated.
|
||||
*/
|
||||
if ((junctions.front().p - junctions.back().p).cast<int64_t>().squaredNorm() == 0)
|
||||
{
|
||||
new_junctions.back().p = junctions.front().p;
|
||||
}
|
||||
|
||||
junctions = new_junctions;
|
||||
}
|
||||
|
||||
int64_t ExtrusionLine::calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width)
|
||||
{
|
||||
/*
|
||||
* A B C A C
|
||||
* --------------- **************
|
||||
* | | ------------------------------------------
|
||||
* | |--------------------------| B removed | |***************************|
|
||||
* | | | ---------> | | |
|
||||
* | |--------------------------| | |***************************|
|
||||
* | | ------------------------------------------
|
||||
* --------------- ^ **************
|
||||
* ^ B.w + C.w / 2 ^
|
||||
* A.w + B.w / 2 new_width = weighted_average_width
|
||||
*
|
||||
*
|
||||
* ******** denote the total extrusion area deviation error in the consecutive segments as a result of using the
|
||||
* weighted-average width for the entire extrusion line.
|
||||
*
|
||||
* */
|
||||
const int64_t ab_length = (B - A).cast<int64_t>().norm();
|
||||
const int64_t bc_length = (C - B).cast<int64_t>().norm();
|
||||
const coord_t width_diff = std::max(std::abs(B.w - A.w), std::abs(C.w - B.w));
|
||||
if (width_diff > 1)
|
||||
{
|
||||
// Adjust the width only if there is a difference, or else the rounding errors may produce the wrong
|
||||
// weighted average value.
|
||||
const int64_t ab_weight = (A.w + B.w) / 2;
|
||||
const int64_t bc_weight = (B.w + C.w) / 2;
|
||||
assert(((ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast<int64_t>().norm()) <= std::numeric_limits<coord_t>::max());
|
||||
weighted_average_width = (ab_length * ab_weight + bc_length * bc_weight) / (C - A).cast<int64_t>().norm();
|
||||
assert((int64_t(std::abs(ab_weight - weighted_average_width)) * ab_length + int64_t(std::abs(bc_weight - weighted_average_width)) * bc_length) <= double(std::numeric_limits<int64_t>::max()));
|
||||
return std::abs(ab_weight - weighted_average_width) * ab_length + std::abs(bc_weight - weighted_average_width) * bc_length;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the width difference is very small, then select the width of the segment that is longer
|
||||
weighted_average_width = ab_length > bc_length ? A.w : B.w;
|
||||
assert((int64_t(width_diff) * int64_t(bc_length)) <= std::numeric_limits<coord_t>::max());
|
||||
assert((int64_t(width_diff) * int64_t(ab_length)) <= std::numeric_limits<coord_t>::max());
|
||||
return ab_length > bc_length ? width_diff * bc_length : width_diff * ab_length;
|
||||
}
|
||||
}
|
||||
|
||||
bool ExtrusionLine::is_contour() const
|
||||
{
|
||||
if (!this->is_closed)
|
||||
return false;
|
||||
|
||||
Polygon poly;
|
||||
poly.points.reserve(this->junctions.size());
|
||||
for (const ExtrusionJunction &junction : this->junctions)
|
||||
poly.points.emplace_back(junction.p);
|
||||
|
||||
// Arachne produces contour with clockwise orientation and holes with counterclockwise orientation.
|
||||
return poly.is_clockwise();
|
||||
}
|
||||
|
||||
double ExtrusionLine::area() const
|
||||
{
|
||||
assert(this->is_closed);
|
||||
double a = 0.;
|
||||
if (this->junctions.size() >= 3) {
|
||||
Vec2d p1 = this->junctions.back().p.cast<double>();
|
||||
for (const ExtrusionJunction &junction : this->junctions) {
|
||||
Vec2d p2 = junction.p.cast<double>();
|
||||
a += cross2(p1, p2);
|
||||
p1 = p2;
|
||||
}
|
||||
}
|
||||
return 0.5 * a;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
namespace Slic3r {
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow)
|
||||
{
|
||||
for (const ClipperLib_Z::Path &extrusion_path : extrusion_paths) {
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion_path);
|
||||
Slic3r::append(dst, thick_polyline_to_multi_path(thick_polyline, role, flow, scaled<float>(0.05), float(SCALED_EPSILON)).paths);
|
||||
}
|
||||
}
|
||||
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow)
|
||||
{
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(extrusion);
|
||||
Slic3r::append(dst, thick_polyline_to_multi_path(thick_polyline, role, flow, scaled<float>(0.05), float(SCALED_EPSILON)).paths);
|
||||
}
|
||||
} // namespace Slic3r
|
||||
307
src/libslic3r/Arachne/utils/ExtrusionLine.hpp
Normal file
307
src/libslic3r/Arachne/utils/ExtrusionLine.hpp
Normal file
@@ -0,0 +1,307 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
#ifndef UTILS_EXTRUSION_LINE_H
|
||||
#define UTILS_EXTRUSION_LINE_H
|
||||
|
||||
#include "ExtrusionJunction.hpp"
|
||||
#include "../../Polyline.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
#include "../../BoundingBox.hpp"
|
||||
#include "../../ExtrusionEntity.hpp"
|
||||
#include "../../Flow.hpp"
|
||||
#include "../../../clipper/clipper_z.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class ThickPolyline;
|
||||
}
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* Represents a polyline (not just a line) that is to be extruded with variable
|
||||
* line width.
|
||||
*
|
||||
* This polyline is a sequence of \ref ExtrusionJunction, with a bit of metadata
|
||||
* about which inset it represents.
|
||||
*/
|
||||
struct ExtrusionLine
|
||||
{
|
||||
/*!
|
||||
* Which inset this path represents, counted from the outside inwards.
|
||||
*
|
||||
* The outer wall has index 0.
|
||||
*/
|
||||
size_t inset_idx;
|
||||
|
||||
/*!
|
||||
* If a thin piece needs to be printed with an odd number of walls (e.g. 5
|
||||
* walls) then there will be one wall in the middle that is not a loop. This
|
||||
* field indicates whether this path is such a line through the middle, that
|
||||
* has no companion line going back on the other side and is not a closed
|
||||
* loop.
|
||||
*/
|
||||
bool is_odd;
|
||||
|
||||
/*!
|
||||
* Whether this is a closed polygonal path
|
||||
*/
|
||||
bool is_closed;
|
||||
|
||||
/*!
|
||||
* Gets the number of vertices in this polygon.
|
||||
* \return The number of vertices in this polygon.
|
||||
*/
|
||||
size_t size() const { return junctions.size(); }
|
||||
|
||||
/*!
|
||||
* Whether there are no junctions.
|
||||
*/
|
||||
bool empty() const { return junctions.empty(); }
|
||||
|
||||
/*!
|
||||
* The list of vertices along which this path runs.
|
||||
*
|
||||
* Each junction has a width, making this path a variable-width path.
|
||||
*/
|
||||
std::vector<ExtrusionJunction> junctions;
|
||||
|
||||
ExtrusionLine(const size_t inset_idx, const bool is_odd);
|
||||
ExtrusionLine() : inset_idx(-1), is_odd(true), is_closed(false) {}
|
||||
ExtrusionLine(const ExtrusionLine &other) : inset_idx(other.inset_idx), is_odd(other.is_odd), is_closed(other.is_closed), junctions(other.junctions) {}
|
||||
|
||||
ExtrusionLine &operator=(ExtrusionLine &&other)
|
||||
{
|
||||
junctions = std::move(other.junctions);
|
||||
inset_idx = other.inset_idx;
|
||||
is_odd = other.is_odd;
|
||||
is_closed = other.is_closed;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ExtrusionLine &operator=(const ExtrusionLine &other)
|
||||
{
|
||||
junctions = other.junctions;
|
||||
inset_idx = other.inset_idx;
|
||||
is_odd = other.is_odd;
|
||||
is_closed = other.is_closed;
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::vector<ExtrusionJunction>::const_iterator begin() const { return junctions.begin(); }
|
||||
std::vector<ExtrusionJunction>::const_iterator end() const { return junctions.end(); }
|
||||
std::vector<ExtrusionJunction>::const_reverse_iterator rbegin() const { return junctions.rbegin(); }
|
||||
std::vector<ExtrusionJunction>::const_reverse_iterator rend() const { return junctions.rend(); }
|
||||
std::vector<ExtrusionJunction>::const_reference front() const { return junctions.front(); }
|
||||
std::vector<ExtrusionJunction>::const_reference back() const { return junctions.back(); }
|
||||
const ExtrusionJunction &operator[](unsigned int index) const { return junctions[index]; }
|
||||
ExtrusionJunction &operator[](unsigned int index) { return junctions[index]; }
|
||||
std::vector<ExtrusionJunction>::iterator begin() { return junctions.begin(); }
|
||||
std::vector<ExtrusionJunction>::iterator end() { return junctions.end(); }
|
||||
std::vector<ExtrusionJunction>::reference front() { return junctions.front(); }
|
||||
std::vector<ExtrusionJunction>::reference back() { return junctions.back(); }
|
||||
|
||||
template<typename... Args> void emplace_back(Args &&...args) { junctions.emplace_back(args...); }
|
||||
void remove(unsigned int index) { junctions.erase(junctions.begin() + index); }
|
||||
void insert(size_t index, const ExtrusionJunction &p) { junctions.insert(junctions.begin() + index, p); }
|
||||
|
||||
template<class iterator>
|
||||
std::vector<ExtrusionJunction>::iterator insert(std::vector<ExtrusionJunction>::const_iterator pos, iterator first, iterator last)
|
||||
{
|
||||
return junctions.insert(pos, first, last);
|
||||
}
|
||||
|
||||
void clear() { junctions.clear(); }
|
||||
void reverse() { std::reverse(junctions.begin(), junctions.end()); }
|
||||
|
||||
/*!
|
||||
* Sum the total length of this path.
|
||||
*/
|
||||
int64_t getLength() const;
|
||||
int64_t polylineLength() const { return getLength(); }
|
||||
|
||||
/*!
|
||||
* Put all junction locations into a polygon object.
|
||||
*
|
||||
* When this path is not closed the returned Polygon should be handled as a polyline, rather than a polygon.
|
||||
*/
|
||||
Polygon toPolygon() const
|
||||
{
|
||||
Polygon ret;
|
||||
for (const ExtrusionJunction &j : junctions)
|
||||
ret.points.emplace_back(j.p);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the minimal width of this path
|
||||
*/
|
||||
coord_t getMinimalWidth() const;
|
||||
|
||||
/*!
|
||||
* Removes vertices of the ExtrusionLines to make sure that they are not too high
|
||||
* resolution.
|
||||
*
|
||||
* This removes junctions which are connected to line segments that are shorter
|
||||
* than the `smallest_line_segment`, unless that would introduce a deviation
|
||||
* in the contour of more than `allowed_error_distance`.
|
||||
*
|
||||
* Criteria:
|
||||
* 1. Never remove a junction if either of the connected segments is larger than \p smallest_line_segment
|
||||
* 2. Never remove a junction if the distance between that junction and the final resulting polygon would be higher
|
||||
* than \p allowed_error_distance
|
||||
* 3. The direction of segments longer than \p smallest_line_segment always
|
||||
* remains unaltered (but their end points may change if it is connected to
|
||||
* a small segment)
|
||||
* 4. Never remove a junction if it has a distinctively different width than the next junction, as this can
|
||||
* introduce unwanted irregularities on the wall widths.
|
||||
*
|
||||
* Simplify uses a heuristic and doesn't necessarily remove all removable
|
||||
* vertices under the above criteria, but simplify may never violate these
|
||||
* criteria. Unless the segments or the distance is smaller than the
|
||||
* rounding error of 5 micron.
|
||||
*
|
||||
* Vertices which introduce an error of less than 5 microns are removed
|
||||
* anyway, even if the segments are longer than the smallest line segment.
|
||||
* This makes sure that (practically) co-linear line segments are joined into
|
||||
* a single line segment.
|
||||
* \param smallest_line_segment Maximal length of removed line segments.
|
||||
* \param allowed_error_distance If removing a vertex introduces a deviation
|
||||
* from the original path that is more than this distance, the vertex may
|
||||
* not be removed.
|
||||
* \param maximum_extrusion_area_deviation The maximum extrusion area deviation allowed when removing intermediate
|
||||
* junctions from a straight ExtrusionLine
|
||||
*/
|
||||
void simplify(int64_t smallest_line_segment_squared, int64_t allowed_error_distance_squared, int64_t maximum_extrusion_area_deviation);
|
||||
|
||||
/*!
|
||||
* Computes and returns the total area error (in μm²) of the AB and BC segments of an ABC straight ExtrusionLine
|
||||
* when the junction B with a width B.w is removed from the ExtrusionLine. The area changes due to the fact that the
|
||||
* new simplified line AC has a uniform width which equals to the weighted average of the width of the subsegments
|
||||
* (based on their length).
|
||||
*
|
||||
* \param A Start point of the 3-point-straight line
|
||||
* \param B Intermediate point of the 3-point-straight line
|
||||
* \param C End point of the 3-point-straight line
|
||||
* \param weighted_average_width The weighted average of the widths of the two colinear extrusion segments
|
||||
* */
|
||||
static int64_t calculateExtrusionAreaDeviationError(ExtrusionJunction A, ExtrusionJunction B, ExtrusionJunction C, coord_t& weighted_average_width);
|
||||
|
||||
bool is_contour() const;
|
||||
|
||||
double area() const;
|
||||
};
|
||||
|
||||
static inline Slic3r::ThickPolyline to_thick_polyline(const Arachne::ExtrusionLine &line_junctions)
|
||||
{
|
||||
assert(line_junctions.size() >= 2);
|
||||
Slic3r::ThickPolyline out;
|
||||
out.points.emplace_back(line_junctions.front().p);
|
||||
out.width.emplace_back(line_junctions.front().w);
|
||||
out.points.emplace_back(line_junctions[1].p);
|
||||
out.width.emplace_back(line_junctions[1].w);
|
||||
|
||||
auto it_prev = line_junctions.begin() + 1;
|
||||
for (auto it = line_junctions.begin() + 2; it != line_junctions.end(); ++it) {
|
||||
out.points.emplace_back(it->p);
|
||||
out.width.emplace_back(it_prev->w);
|
||||
out.width.emplace_back(it->w);
|
||||
it_prev = it;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline Slic3r::ThickPolyline to_thick_polyline(const ClipperLib_Z::Path &path)
|
||||
{
|
||||
assert(path.size() >= 2);
|
||||
Slic3r::ThickPolyline out;
|
||||
out.points.emplace_back(path.front().x(), path.front().y());
|
||||
out.width.emplace_back(path.front().z());
|
||||
out.points.emplace_back(path[1].x(), path[1].y());
|
||||
out.width.emplace_back(path[1].z());
|
||||
|
||||
auto it_prev = path.begin() + 1;
|
||||
for (auto it = path.begin() + 2; it != path.end(); ++it) {
|
||||
out.points.emplace_back(it->x(), it->y());
|
||||
out.width.emplace_back(it_prev->z());
|
||||
out.width.emplace_back(it->z());
|
||||
it_prev = it;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline Polygon to_polygon(const ExtrusionLine &line)
|
||||
{
|
||||
Polygon out;
|
||||
assert(line.junctions.size() >= 3);
|
||||
assert(line.junctions.front().p == line.junctions.back().p);
|
||||
out.points.reserve(line.junctions.size() - 1);
|
||||
for (auto it = line.junctions.begin(); it != line.junctions.end() - 1; ++it)
|
||||
out.points.emplace_back(it->p);
|
||||
return out;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static BoundingBox get_extents(const ExtrusionLine &extrusion_line)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionJunction &junction : extrusion_line.junctions)
|
||||
bbox.merge(junction.p);
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static BoundingBox get_extents(const std::vector<ExtrusionLine> &extrusion_lines)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionLine &extrusion_line : extrusion_lines)
|
||||
bbox.merge(get_extents(extrusion_line));
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static BoundingBox get_extents(const std::vector<const ExtrusionLine *> &extrusion_lines)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionLine *extrusion_line : extrusion_lines) {
|
||||
assert(extrusion_line != nullptr);
|
||||
bbox.merge(get_extents(*extrusion_line));
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static Points to_points(const ExtrusionLine &extrusion_line)
|
||||
{
|
||||
Points points;
|
||||
points.reserve(extrusion_line.junctions.size());
|
||||
for (const ExtrusionJunction &junction : extrusion_line.junctions)
|
||||
points.emplace_back(junction.p);
|
||||
return points;
|
||||
}
|
||||
|
||||
static std::vector<Points> to_points(const std::vector<const ExtrusionLine *> &extrusion_lines)
|
||||
{
|
||||
std::vector<Points> points;
|
||||
for (const ExtrusionLine *extrusion_line : extrusion_lines) {
|
||||
assert(extrusion_line != nullptr);
|
||||
points.emplace_back(to_points(*extrusion_line));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
#endif
|
||||
|
||||
using VariableWidthLines = std::vector<ExtrusionLine>; //<! The ExtrusionLines generated by libArachne
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const ClipperLib_Z::Paths &extrusion_paths, const ExtrusionRole role, const Flow &flow);
|
||||
void extrusion_paths_append(ExtrusionPaths &dst, const Arachne::ExtrusionLine &extrusion, const ExtrusionRole role, const Flow &flow);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // UTILS_EXTRUSION_LINE_H
|
||||
39
src/libslic3r/Arachne/utils/HalfEdge.hpp
Normal file
39
src/libslic3r/Arachne/utils/HalfEdge.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_HALF_EDGE_H
|
||||
#define UTILS_HALF_EDGE_H
|
||||
|
||||
#include <forward_list>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
template<typename node_data_t, typename edge_data_t, typename derived_node_t, typename derived_edge_t>
|
||||
class HalfEdgeNode;
|
||||
|
||||
|
||||
template<typename node_data_t, typename edge_data_t, typename derived_node_t, typename derived_edge_t>
|
||||
class HalfEdge
|
||||
{
|
||||
using edge_t = derived_edge_t;
|
||||
using node_t = derived_node_t;
|
||||
public:
|
||||
edge_data_t data;
|
||||
edge_t* twin = nullptr;
|
||||
edge_t* next = nullptr;
|
||||
edge_t* prev = nullptr;
|
||||
node_t* from = nullptr;
|
||||
node_t* to = nullptr;
|
||||
HalfEdge(edge_data_t data)
|
||||
: data(data)
|
||||
{}
|
||||
bool operator==(const edge_t& other)
|
||||
{
|
||||
return this == &other;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // UTILS_HALF_EDGE_H
|
||||
29
src/libslic3r/Arachne/utils/HalfEdgeGraph.hpp
Normal file
29
src/libslic3r/Arachne/utils/HalfEdgeGraph.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_HALF_EDGE_GRAPH_H
|
||||
#define UTILS_HALF_EDGE_GRAPH_H
|
||||
|
||||
|
||||
#include <list>
|
||||
#include <cassert>
|
||||
|
||||
|
||||
|
||||
#include "HalfEdge.hpp"
|
||||
#include "HalfEdgeNode.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
template<class node_data_t, class edge_data_t, class derived_node_t, class derived_edge_t> // types of data contained in nodes and edges
|
||||
class HalfEdgeGraph
|
||||
{
|
||||
public:
|
||||
using edge_t = derived_edge_t;
|
||||
using node_t = derived_node_t;
|
||||
std::list<edge_t> edges;
|
||||
std::list<node_t> nodes;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // UTILS_HALF_EDGE_GRAPH_H
|
||||
38
src/libslic3r/Arachne/utils/HalfEdgeNode.hpp
Normal file
38
src/libslic3r/Arachne/utils/HalfEdgeNode.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_HALF_EDGE_NODE_H
|
||||
#define UTILS_HALF_EDGE_NODE_H
|
||||
|
||||
#include <list>
|
||||
|
||||
#include "../../Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
template<typename node_data_t, typename edge_data_t, typename derived_node_t, typename derived_edge_t>
|
||||
class HalfEdge;
|
||||
|
||||
template<typename node_data_t, typename edge_data_t, typename derived_node_t, typename derived_edge_t>
|
||||
class HalfEdgeNode
|
||||
{
|
||||
using edge_t = derived_edge_t;
|
||||
using node_t = derived_node_t;
|
||||
public:
|
||||
node_data_t data;
|
||||
Point p;
|
||||
edge_t* incident_edge = nullptr;
|
||||
HalfEdgeNode(node_data_t data, Point p)
|
||||
: data(data)
|
||||
, p(p)
|
||||
{}
|
||||
|
||||
bool operator==(const node_t& other)
|
||||
{
|
||||
return this == &other;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // UTILS_HALF_EDGE_NODE_H
|
||||
180
src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp
Normal file
180
src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp
Normal file
@@ -0,0 +1,180 @@
|
||||
//Copyright (c) 2018 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_POLYGONS_POINT_INDEX_H
|
||||
#define UTILS_POLYGONS_POINT_INDEX_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../../Point.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
// Identity function, used to be able to make templated algorithms where the input is sometimes points, sometimes things that contain or can be converted to points.
|
||||
inline const Point &make_point(const Point &p) { return p; }
|
||||
|
||||
/*!
|
||||
* A class for iterating over the points in one of the polygons in a \ref Polygons object
|
||||
*/
|
||||
template<typename Paths>
|
||||
class PathsPointIndex
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* The polygons into which this index is indexing.
|
||||
*/
|
||||
const Paths* polygons; // (pointer to const polygons)
|
||||
|
||||
unsigned int poly_idx; //!< The index of the polygon in \ref PolygonsPointIndex::polygons
|
||||
|
||||
unsigned int point_idx; //!< The index of the point in the polygon in \ref PolygonsPointIndex::polygons
|
||||
|
||||
/*!
|
||||
* Constructs an empty point index to no polygon.
|
||||
*
|
||||
* This is used as a placeholder for when there is a zero-construction
|
||||
* needed. Since the `polygons` field is const you can't ever make this
|
||||
* initialisation useful.
|
||||
*/
|
||||
PathsPointIndex() : polygons(nullptr), poly_idx(0), point_idx(0) {}
|
||||
|
||||
/*!
|
||||
* Constructs a new point index to a vertex of a polygon.
|
||||
* \param polygons The Polygons instance to which this index points.
|
||||
* \param poly_idx The index of the sub-polygon to point to.
|
||||
* \param point_idx The index of the vertex in the sub-polygon.
|
||||
*/
|
||||
PathsPointIndex(const Paths *polygons, unsigned int poly_idx, unsigned int point_idx) : polygons(polygons), poly_idx(poly_idx), point_idx(point_idx) {}
|
||||
|
||||
/*!
|
||||
* Copy constructor to copy these indices.
|
||||
*/
|
||||
PathsPointIndex(const PathsPointIndex& original) = default;
|
||||
|
||||
Point p() const
|
||||
{
|
||||
if (!polygons)
|
||||
return {0, 0};
|
||||
|
||||
return make_point((*polygons)[poly_idx][point_idx]);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns whether this point is initialised.
|
||||
*/
|
||||
bool initialized() const { return polygons; }
|
||||
|
||||
/*!
|
||||
* Get the polygon to which this PolygonsPointIndex refers
|
||||
*/
|
||||
const Polygon &getPolygon() const { return (*polygons)[poly_idx]; }
|
||||
|
||||
/*!
|
||||
* Test whether two iterators refer to the same polygon in the same polygon list.
|
||||
*
|
||||
* \param other The PolygonsPointIndex to test for equality
|
||||
* \return Wether the right argument refers to the same polygon in the same ListPolygon as the left argument.
|
||||
*/
|
||||
bool operator==(const PathsPointIndex &other) const
|
||||
{
|
||||
return polygons == other.polygons && poly_idx == other.poly_idx && point_idx == other.point_idx;
|
||||
}
|
||||
bool operator!=(const PathsPointIndex &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
bool operator<(const PathsPointIndex &other) const
|
||||
{
|
||||
return this->p() < other.p();
|
||||
}
|
||||
PathsPointIndex &operator=(const PathsPointIndex &other)
|
||||
{
|
||||
polygons = other.polygons;
|
||||
poly_idx = other.poly_idx;
|
||||
point_idx = other.point_idx;
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator forward (and wrap around at the end)
|
||||
PathsPointIndex &operator++()
|
||||
{
|
||||
point_idx = (point_idx + 1) % (*polygons)[poly_idx].size();
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator backward (and wrap around at the beginning)
|
||||
PathsPointIndex &operator--()
|
||||
{
|
||||
if (point_idx == 0)
|
||||
point_idx = (*polygons)[poly_idx].size();
|
||||
point_idx--;
|
||||
return *this;
|
||||
}
|
||||
//! move the iterator forward (and wrap around at the end)
|
||||
PathsPointIndex next() const
|
||||
{
|
||||
PathsPointIndex ret(*this);
|
||||
++ret;
|
||||
return ret;
|
||||
}
|
||||
//! move the iterator backward (and wrap around at the beginning)
|
||||
PathsPointIndex prev() const
|
||||
{
|
||||
PathsPointIndex ret(*this);
|
||||
--ret;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
using PolygonsPointIndex = PathsPointIndex<Polygons>;
|
||||
|
||||
/*!
|
||||
* Locator to extract a line segment out of a \ref PolygonsPointIndex
|
||||
*/
|
||||
struct PolygonsPointIndexSegmentLocator
|
||||
{
|
||||
std::pair<Point, Point> operator()(const PolygonsPointIndex &val) const
|
||||
{
|
||||
const Polygon &poly = (*val.polygons)[val.poly_idx];
|
||||
Point start = poly[val.point_idx];
|
||||
unsigned int next_point_idx = (val.point_idx + 1) % poly.size();
|
||||
Point end = poly[next_point_idx];
|
||||
return std::pair<Point, Point>(start, end);
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Locator of a \ref PolygonsPointIndex
|
||||
*/
|
||||
template<typename Paths>
|
||||
struct PathsPointIndexLocator
|
||||
{
|
||||
Point operator()(const PathsPointIndex<Paths>& val) const
|
||||
{
|
||||
return make_point(val.p());
|
||||
}
|
||||
};
|
||||
|
||||
using PolygonsPointIndexLocator = PathsPointIndexLocator<Polygons>;
|
||||
|
||||
}//namespace Slic3r::Arachne
|
||||
|
||||
namespace std
|
||||
{
|
||||
/*!
|
||||
* Hash function for \ref PolygonsPointIndex
|
||||
*/
|
||||
template <>
|
||||
struct hash<Slic3r::Arachne::PolygonsPointIndex>
|
||||
{
|
||||
size_t operator()(const Slic3r::Arachne::PolygonsPointIndex& lpi) const
|
||||
{
|
||||
return Slic3r::PointHash{}(lpi.p());
|
||||
}
|
||||
};
|
||||
}//namespace std
|
||||
|
||||
|
||||
|
||||
#endif//UTILS_POLYGONS_POINT_INDEX_H
|
||||
50
src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp
Normal file
50
src/libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_POLYGONS_SEGMENT_INDEX_H
|
||||
#define UTILS_POLYGONS_SEGMENT_INDEX_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "PolygonsPointIndex.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* A class for iterating over the points in one of the polygons in a \ref Polygons object
|
||||
*/
|
||||
class PolygonsSegmentIndex : public PolygonsPointIndex
|
||||
{
|
||||
public:
|
||||
PolygonsSegmentIndex() : PolygonsPointIndex(){};
|
||||
PolygonsSegmentIndex(const Polygons *polygons, unsigned int poly_idx, unsigned int point_idx) : PolygonsPointIndex(polygons, poly_idx, point_idx){};
|
||||
|
||||
Point from() const { return PolygonsPointIndex::p(); }
|
||||
|
||||
Point to() const { return PolygonsSegmentIndex::next().p(); }
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
namespace boost::polygon {
|
||||
|
||||
template<> struct geometry_concept<Slic3r::Arachne::PolygonsSegmentIndex>
|
||||
{
|
||||
typedef segment_concept type;
|
||||
};
|
||||
|
||||
template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
|
||||
{
|
||||
typedef coord_t coordinate_type;
|
||||
typedef Slic3r::Point point_type;
|
||||
|
||||
static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir)
|
||||
{
|
||||
return dir.to_int() ? CSegment.to() : CSegment.from();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace boost::polygon
|
||||
|
||||
#endif//UTILS_POLYGONS_SEGMENT_INDEX_H
|
||||
42
src/libslic3r/Arachne/utils/PolylineStitcher.cpp
Normal file
42
src/libslic3r/Arachne/utils/PolylineStitcher.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "PolylineStitcher.hpp"
|
||||
#include "ExtrusionLine.hpp"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
template<> bool PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::canReverse(const PathsPointIndex<VariableWidthLines> &ppi)
|
||||
{
|
||||
if ((*ppi.polygons)[ppi.poly_idx].is_odd)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<Polygons, Polygon, Point>::canReverse(const PathsPointIndex<Polygons> &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::canConnect(const ExtrusionLine &a, const ExtrusionLine &b)
|
||||
{
|
||||
return a.is_odd == b.is_odd;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<Polygons, Polygon, Point>::canConnect(const Polygon &, const Polygon &)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<VariableWidthLines, ExtrusionLine, ExtrusionJunction>::isOdd(const ExtrusionLine &line)
|
||||
{
|
||||
return line.is_odd;
|
||||
}
|
||||
|
||||
template<> bool PolylineStitcher<Polygons, Polygon, Point>::isOdd(const Polygon &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
234
src/libslic3r/Arachne/utils/PolylineStitcher.hpp
Normal file
234
src/libslic3r/Arachne/utils/PolylineStitcher.hpp
Normal file
@@ -0,0 +1,234 @@
|
||||
//Copyright (c) 2022 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_POLYLINE_STITCHER_H
|
||||
#define UTILS_POLYLINE_STITCHER_H
|
||||
|
||||
#include "SparsePointGrid.hpp"
|
||||
#include "PolygonsPointIndex.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
#include <unordered_set>
|
||||
#include <cassert>
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
* Class for stitching polylines into longer polylines or into polygons
|
||||
*/
|
||||
template<typename Paths, typename Path, typename Junction>
|
||||
class PolylineStitcher
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Stitch together the separate \p lines into \p result_lines and if they
|
||||
* can be closed into \p result_polygons.
|
||||
*
|
||||
* Only introduce new segments shorter than \p max_stitch_distance, and
|
||||
* larger than \p snap_distance but always try to take the shortest
|
||||
* connection possible.
|
||||
*
|
||||
* Only stitch polylines into closed polygons if they are larger than 3 *
|
||||
* \p max_stitch_distance, in order to prevent small segments to
|
||||
* accidentally get closed into a polygon.
|
||||
*
|
||||
* \warning Tiny polylines (smaller than 3 * max_stitch_distance) will not
|
||||
* be closed into polygons.
|
||||
*
|
||||
* \note Resulting polylines and polygons are added onto the existing
|
||||
* containers, so you can directly output onto a polygons container with
|
||||
* existing polygons in it. However, you shouldn't call this function with
|
||||
* the same parameter in \p lines as \p result_lines, because that would
|
||||
* duplicate (some of) the polylines.
|
||||
* \param lines The lines to stitch together.
|
||||
* \param result_lines[out] The stitched parts that are not closed polygons
|
||||
* will be stored in here.
|
||||
* \param result_polygons[out] The stitched parts that were closed as
|
||||
* polygons will be stored in here.
|
||||
* \param max_stitch_distance The maximum distance that will be bridged to
|
||||
* connect two lines.
|
||||
* \param snap_distance Points closer than this distance are considered to
|
||||
* be the same point.
|
||||
*/
|
||||
static void stitch(const Paths& lines, Paths& result_lines, Paths& result_polygons, coord_t max_stitch_distance = scaled<coord_t>(0.1), coord_t snap_distance = scaled<coord_t>(0.01))
|
||||
{
|
||||
if (lines.empty())
|
||||
return;
|
||||
|
||||
SparsePointGrid<PathsPointIndex<Paths>, PathsPointIndexLocator<Paths>> grid(max_stitch_distance, lines.size() * 2);
|
||||
|
||||
// populate grid
|
||||
for (size_t line_idx = 0; line_idx < lines.size(); line_idx++)
|
||||
{
|
||||
const auto line = lines[line_idx];
|
||||
grid.insert(PathsPointIndex<Paths>(&lines, line_idx, 0));
|
||||
grid.insert(PathsPointIndex<Paths>(&lines, line_idx, line.size() - 1));
|
||||
}
|
||||
|
||||
std::vector<bool> processed(lines.size(), false);
|
||||
|
||||
for (size_t line_idx = 0; line_idx < lines.size(); line_idx++)
|
||||
{
|
||||
if (processed[line_idx])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
processed[line_idx] = true;
|
||||
const auto line = lines[line_idx];
|
||||
bool should_close = isOdd(line);
|
||||
|
||||
Path chain = line;
|
||||
bool closest_is_closing_polygon = false;
|
||||
for (bool go_in_reverse_direction : { false, true }) // first go in the unreversed direction, to try to prevent the chain.reverse() operation.
|
||||
{ // NOTE: Implementation only works for this order; we currently only re-reverse the chain when it's closed.
|
||||
if (go_in_reverse_direction)
|
||||
{ // try extending chain in the other direction
|
||||
chain.reverse();
|
||||
}
|
||||
int64_t chain_length = chain.polylineLength();
|
||||
|
||||
while (true)
|
||||
{
|
||||
Point from = make_point(chain.back());
|
||||
|
||||
PathsPointIndex<Paths> closest;
|
||||
coord_t closest_distance = std::numeric_limits<coord_t>::max();
|
||||
grid.processNearby(from, max_stitch_distance,
|
||||
std::function<bool (const PathsPointIndex<Paths>&)> (
|
||||
[from, &chain, &closest, &closest_is_closing_polygon, &closest_distance, &processed, &chain_length, go_in_reverse_direction, max_stitch_distance, snap_distance, should_close]
|
||||
(const PathsPointIndex<Paths>& nearby)->bool
|
||||
{
|
||||
bool is_closing_segment = false;
|
||||
coord_t dist = (nearby.p().template cast<int64_t>() - from.template cast<int64_t>()).norm();
|
||||
if (dist > max_stitch_distance)
|
||||
{
|
||||
return true; // keep looking
|
||||
}
|
||||
if ((nearby.p().template cast<int64_t>() - make_point(chain.front()).template cast<int64_t>()).squaredNorm() < snap_distance * snap_distance)
|
||||
{
|
||||
if (chain_length + dist < 3 * max_stitch_distance // prevent closing of small poly, cause it might be able to continue making a larger polyline
|
||||
|| chain.size() <= 2) // don't make 2 vert polygons
|
||||
{
|
||||
return true; // look for a better next line
|
||||
}
|
||||
is_closing_segment = true;
|
||||
if (!should_close)
|
||||
{
|
||||
dist += scaled<coord_t>(0.01); // prefer continuing polyline over closing a polygon; avoids closed zigzags from being printed separately
|
||||
// continue to see if closing segment is also the closest
|
||||
// there might be a segment smaller than [max_stitch_distance] which closes the polygon better
|
||||
}
|
||||
else
|
||||
{
|
||||
dist -= scaled<coord_t>(0.01); //Prefer closing the polygon if it's 100% even lines. Used to create closed contours.
|
||||
//Continue to see if closing segment is also the closest.
|
||||
}
|
||||
}
|
||||
else if (processed[nearby.poly_idx])
|
||||
{ // it was already moved to output
|
||||
return true; // keep looking for a connection
|
||||
}
|
||||
bool nearby_would_be_reversed = nearby.point_idx != 0;
|
||||
nearby_would_be_reversed = nearby_would_be_reversed != go_in_reverse_direction; // flip nearby_would_be_reversed when searching in the reverse direction
|
||||
if (!canReverse(nearby) && nearby_would_be_reversed)
|
||||
{ // connecting the segment would reverse the polygon direction
|
||||
return true; // keep looking for a connection
|
||||
}
|
||||
if (!canConnect(chain, (*nearby.polygons)[nearby.poly_idx]))
|
||||
{
|
||||
return true; // keep looking for a connection
|
||||
}
|
||||
if (dist < closest_distance)
|
||||
{
|
||||
closest_distance = dist;
|
||||
closest = nearby;
|
||||
closest_is_closing_polygon = is_closing_segment;
|
||||
}
|
||||
if (dist < snap_distance)
|
||||
{ // we have found a good enough next line
|
||||
return false; // stop looking for alternatives
|
||||
}
|
||||
return true; // keep processing elements
|
||||
})
|
||||
);
|
||||
|
||||
if (!closest.initialized() // we couldn't find any next line
|
||||
|| closest_is_closing_polygon // we closed the polygon
|
||||
)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
coord_t segment_dist = (make_point(chain.back()).template cast<int64_t>() - closest.p().template cast<int64_t>()).norm();
|
||||
assert(segment_dist <= max_stitch_distance + scaled<coord_t>(0.01));
|
||||
const size_t old_size = chain.size();
|
||||
if (closest.point_idx == 0)
|
||||
{
|
||||
auto start_pos = (*closest.polygons)[closest.poly_idx].begin();
|
||||
if (segment_dist < snap_distance)
|
||||
{
|
||||
++start_pos;
|
||||
}
|
||||
chain.insert(chain.end(), start_pos, (*closest.polygons)[closest.poly_idx].end());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto start_pos = (*closest.polygons)[closest.poly_idx].rbegin();
|
||||
if (segment_dist < snap_distance)
|
||||
{
|
||||
++start_pos;
|
||||
}
|
||||
chain.insert(chain.end(), start_pos, (*closest.polygons)[closest.poly_idx].rend());
|
||||
}
|
||||
for(size_t i = old_size; i < chain.size(); ++i) //Update chain length.
|
||||
{
|
||||
chain_length += (make_point(chain[i]).template cast<int64_t>() - make_point(chain[i - 1]).template cast<int64_t>()).norm();
|
||||
}
|
||||
should_close = should_close & !isOdd((*closest.polygons)[closest.poly_idx]); //If we connect an even to an odd line, we should no longer try to close it.
|
||||
assert( ! processed[closest.poly_idx]);
|
||||
processed[closest.poly_idx] = true;
|
||||
}
|
||||
if (closest_is_closing_polygon)
|
||||
{
|
||||
if (go_in_reverse_direction)
|
||||
{ // re-reverse chain to retain original direction
|
||||
// NOTE: not sure if this code could ever be reached, since if a polygon can be closed that should be already possible in the forward direction
|
||||
chain.reverse();
|
||||
}
|
||||
|
||||
break; // don't consider reverse direction
|
||||
}
|
||||
}
|
||||
if (closest_is_closing_polygon)
|
||||
{
|
||||
result_polygons.emplace_back(chain);
|
||||
}
|
||||
else
|
||||
{
|
||||
PathsPointIndex<Paths> ppi_here(&lines, line_idx, 0);
|
||||
if ( ! canReverse(ppi_here))
|
||||
{ // Since closest_is_closing_polygon is false we went through the second iterations of the for-loop, where go_in_reverse_direction is true
|
||||
// the polyline isn't allowed to be reversed, so we re-reverse it.
|
||||
chain.reverse();
|
||||
}
|
||||
result_lines.emplace_back(chain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Whether a polyline is allowed to be reversed. (Not true for wall polylines which are not odd)
|
||||
*/
|
||||
static bool canReverse(const PathsPointIndex<Paths> &polyline);
|
||||
|
||||
/*!
|
||||
* Whether two paths are allowed to be connected.
|
||||
* (Not true for an odd and an even wall.)
|
||||
*/
|
||||
static bool canConnect(const Path &a, const Path &b);
|
||||
|
||||
static bool isOdd(const Path &line);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
#endif // UTILS_POLYLINE_STITCHER_H
|
||||
133
src/libslic3r/Arachne/utils/SparseGrid.hpp
Normal file
133
src/libslic3r/Arachne/utils/SparseGrid.hpp
Normal file
@@ -0,0 +1,133 @@
|
||||
//Copyright (c) 2016 Scott Lenser
|
||||
//Copyright (c) 2018 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_SPARSE_GRID_H
|
||||
#define UTILS_SPARSE_GRID_H
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "../../Point.hpp"
|
||||
#include "SquareGrid.hpp"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
|
||||
*
|
||||
* \note This is an abstract template class which doesn't have any functions to insert elements.
|
||||
* \see SparsePointGrid
|
||||
*
|
||||
* \tparam ElemT The element type to store.
|
||||
*/
|
||||
template<class ElemT> class SparseGrid : public SquareGrid
|
||||
{
|
||||
public:
|
||||
using Elem = ElemT;
|
||||
|
||||
using GridPoint = SquareGrid::GridPoint;
|
||||
using grid_coord_t = SquareGrid::grid_coord_t;
|
||||
using GridMap = std::unordered_multimap<GridPoint, Elem, PointHash>;
|
||||
|
||||
using iterator = typename GridMap::iterator;
|
||||
using const_iterator = typename GridMap::const_iterator;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparseGrid(coord_t cell_size, size_t elem_reserve=0U, float max_load_factor=1.0f);
|
||||
|
||||
iterator begin() { return m_grid.begin(); }
|
||||
iterator end() { return m_grid.end(); }
|
||||
const_iterator begin() const { return m_grid.begin(); }
|
||||
const_iterator end() const { return m_grid.end(); }
|
||||
|
||||
/*! \brief Returns all data within radius of query_pt.
|
||||
*
|
||||
* Finds all elements with location within radius of \p query_pt. May
|
||||
* return additional elements that are beyond radius.
|
||||
*
|
||||
* Average running time is a*(1 + 2 * radius / cell_size)**2 +
|
||||
* b*cnt where a and b are proportionality constance and cnt is
|
||||
* the number of returned items. The search will return items in
|
||||
* an area of (2*radius + cell_size)**2 on average. The max range
|
||||
* of an item from the query_point is radius + cell_size.
|
||||
*
|
||||
* \param[in] query_pt The point to search around.
|
||||
* \param[in] radius The search radius.
|
||||
* \return Vector of elements found
|
||||
*/
|
||||
std::vector<Elem> getNearby(const Point &query_pt, coord_t radius) const;
|
||||
|
||||
/*! \brief Process elements from cells that might contain sought after points.
|
||||
*
|
||||
* Processes elements from cell that might have elements within \p
|
||||
* radius of \p query_pt. Processes all elements that are within
|
||||
* radius of query_pt. May process elements that are up to radius +
|
||||
* cell_size from query_pt.
|
||||
*
|
||||
* \param[in] query_pt The point to search around.
|
||||
* \param[in] radius The search radius.
|
||||
* \param[in] process_func Processes each element. process_func(elem) is
|
||||
* called for each element in the cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing after this function
|
||||
*/
|
||||
bool processNearby(const Point &query_pt, coord_t radius, const std::function<bool(const ElemT &)> &process_func) const;
|
||||
|
||||
protected:
|
||||
/*! \brief Process elements from the cell indicated by \p grid_pt.
|
||||
*
|
||||
* \param[in] grid_pt The grid coordinates of the cell.
|
||||
* \param[in] process_func Processes each element. process_func(elem) is
|
||||
* called for each element in the cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing a next cell.
|
||||
*/
|
||||
bool processFromCell(const GridPoint &grid_pt, const std::function<bool(const Elem &)> &process_func) const;
|
||||
|
||||
/*! \brief Map from grid locations (GridPoint) to elements (Elem). */
|
||||
GridMap m_grid;
|
||||
};
|
||||
|
||||
template<class ElemT> SparseGrid<ElemT>::SparseGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor) : SquareGrid(cell_size)
|
||||
{
|
||||
// Must be before the reserve call.
|
||||
m_grid.max_load_factor(max_load_factor);
|
||||
if (elem_reserve != 0U)
|
||||
m_grid.reserve(elem_reserve);
|
||||
}
|
||||
|
||||
template<class ElemT> bool SparseGrid<ElemT>::processFromCell(const GridPoint &grid_pt, const std::function<bool(const Elem &)> &process_func) const
|
||||
{
|
||||
auto grid_range = m_grid.equal_range(grid_pt);
|
||||
for (auto iter = grid_range.first; iter != grid_range.second; ++iter)
|
||||
if (!process_func(iter->second))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class ElemT>
|
||||
bool SparseGrid<ElemT>::processNearby(const Point &query_pt, coord_t radius, const std::function<bool(const Elem &)> &process_func) const
|
||||
{
|
||||
return SquareGrid::processNearby(query_pt, radius, [&process_func, this](const GridPoint &grid_pt) { return processFromCell(grid_pt, process_func); });
|
||||
}
|
||||
|
||||
template<class ElemT> std::vector<typename SparseGrid<ElemT>::Elem> SparseGrid<ElemT>::getNearby(const Point &query_pt, coord_t radius) const
|
||||
{
|
||||
std::vector<Elem> ret;
|
||||
const std::function<bool(const Elem &)> process_func = [&ret](const Elem &elem) {
|
||||
ret.push_back(elem);
|
||||
return true;
|
||||
};
|
||||
processNearby(query_pt, radius, process_func);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_SPARSE_GRID_H
|
||||
77
src/libslic3r/Arachne/utils/SparseLineGrid.hpp
Normal file
77
src/libslic3r/Arachne/utils/SparseLineGrid.hpp
Normal file
@@ -0,0 +1,77 @@
|
||||
//Copyright (c) 2018 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
#ifndef UTILS_SPARSE_LINE_GRID_H
|
||||
#define UTILS_SPARSE_LINE_GRID_H
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "SparseGrid.hpp"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
|
||||
*
|
||||
* \tparam ElemT The element type to store.
|
||||
* \tparam Locator The functor to get the start and end locations from ElemT.
|
||||
* must have: std::pair<Point, Point> operator()(const ElemT &elem) const
|
||||
* which returns the location associated with val.
|
||||
*/
|
||||
template<class ElemT, class Locator> class SparseLineGrid : public SparseGrid<ElemT>
|
||||
{
|
||||
public:
|
||||
using Elem = ElemT;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparseLineGrid(coord_t cell_size, size_t elem_reserve = 0U, float max_load_factor = 1.0f);
|
||||
|
||||
/*! \brief Inserts elem into the sparse grid.
|
||||
*
|
||||
* \param[in] elem The element to be inserted.
|
||||
*/
|
||||
void insert(const Elem &elem);
|
||||
|
||||
protected:
|
||||
using GridPoint = typename SparseGrid<ElemT>::GridPoint;
|
||||
|
||||
/*! \brief Accessor for getting locations from elements. */
|
||||
Locator m_locator;
|
||||
};
|
||||
|
||||
template<class ElemT, class Locator>
|
||||
SparseLineGrid<ElemT, Locator>::SparseLineGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor)
|
||||
: SparseGrid<ElemT>(cell_size, elem_reserve, max_load_factor) {}
|
||||
|
||||
template<class ElemT, class Locator> void SparseLineGrid<ElemT, Locator>::insert(const Elem &elem)
|
||||
{
|
||||
const std::pair<Point, Point> line = m_locator(elem);
|
||||
using GridMap = std::unordered_multimap<GridPoint, Elem, PointHash>;
|
||||
// below is a workaround for the fact that lambda functions cannot access private or protected members
|
||||
// first we define a lambda which works on any GridMap and then we bind it to the actual protected GridMap of the parent class
|
||||
std::function<bool(GridMap *, const GridPoint)> process_cell_func_ = [&elem](GridMap *m_grid, const GridPoint grid_loc) {
|
||||
m_grid->emplace(grid_loc, elem);
|
||||
return true;
|
||||
};
|
||||
using namespace std::placeholders; // for _1, _2, _3...
|
||||
GridMap *m_grid = &(this->m_grid);
|
||||
std::function<bool(const GridPoint)> process_cell_func(std::bind(process_cell_func_, m_grid, _1));
|
||||
|
||||
SparseGrid<ElemT>::processLineCells(line, process_cell_func);
|
||||
}
|
||||
|
||||
#undef SGI_TEMPLATE
|
||||
#undef SGI_THIS
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_SPARSE_LINE_GRID_H
|
||||
90
src/libslic3r/Arachne/utils/SparsePointGrid.hpp
Normal file
90
src/libslic3r/Arachne/utils/SparsePointGrid.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2016 Scott Lenser
|
||||
// Copyright (c) 2020 Ultimaker B.V.
|
||||
// CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_SPARSE_POINT_GRID_H
|
||||
#define UTILS_SPARSE_POINT_GRID_H
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "SparseGrid.hpp"
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
/*! \brief Sparse grid which can locate spatially nearby elements efficiently.
|
||||
*
|
||||
* \tparam ElemT The element type to store.
|
||||
* \tparam Locator The functor to get the location from ElemT. Locator
|
||||
* must have: Point operator()(const ElemT &elem) const
|
||||
* which returns the location associated with val.
|
||||
*/
|
||||
template<class ElemT, class Locator> class SparsePointGrid : public SparseGrid<ElemT>
|
||||
{
|
||||
public:
|
||||
using Elem = ElemT;
|
||||
|
||||
/*! \brief Constructs a sparse grid with the specified cell size.
|
||||
*
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
* Typical values would be around 0.5-2x of expected query radius.
|
||||
* \param[in] elem_reserve Number of elements to research space for.
|
||||
* \param[in] max_load_factor Maximum average load factor before rehashing.
|
||||
*/
|
||||
SparsePointGrid(coord_t cell_size, size_t elem_reserve = 0U, float max_load_factor = 1.0f);
|
||||
|
||||
/*! \brief Inserts elem into the sparse grid.
|
||||
*
|
||||
* \param[in] elem The element to be inserted.
|
||||
*/
|
||||
void insert(const Elem &elem);
|
||||
|
||||
/*!
|
||||
* Get just any element that's within a certain radius of a point.
|
||||
*
|
||||
* Rather than giving a vector of nearby elements, this function just gives
|
||||
* a single element, any element, in no particular order.
|
||||
* \param query_pt The point to query for an object nearby.
|
||||
* \param radius The radius of what is considered "nearby".
|
||||
*/
|
||||
const ElemT *getAnyNearby(const Point &query_pt, coord_t radius);
|
||||
|
||||
protected:
|
||||
using GridPoint = typename SparseGrid<ElemT>::GridPoint;
|
||||
|
||||
/*! \brief Accessor for getting locations from elements. */
|
||||
Locator m_locator;
|
||||
};
|
||||
|
||||
template<class ElemT, class Locator>
|
||||
SparsePointGrid<ElemT, Locator>::SparsePointGrid(coord_t cell_size, size_t elem_reserve, float max_load_factor) : SparseGrid<ElemT>(cell_size, elem_reserve, max_load_factor) {}
|
||||
|
||||
template<class ElemT, class Locator>
|
||||
void SparsePointGrid<ElemT, Locator>::insert(const Elem &elem)
|
||||
{
|
||||
Point loc = m_locator(elem);
|
||||
GridPoint grid_loc = SparseGrid<ElemT>::toGridPoint(loc.template cast<int64_t>());
|
||||
|
||||
SparseGrid<ElemT>::m_grid.emplace(grid_loc, elem);
|
||||
}
|
||||
|
||||
template<class ElemT, class Locator>
|
||||
const ElemT *SparsePointGrid<ElemT, Locator>::getAnyNearby(const Point &query_pt, coord_t radius)
|
||||
{
|
||||
const ElemT *ret = nullptr;
|
||||
const std::function<bool(const ElemT &)> &process_func = [&ret, query_pt, radius, this](const ElemT &maybe_nearby) {
|
||||
if (shorter_then(m_locator(maybe_nearby) - query_pt, radius)) {
|
||||
ret = &maybe_nearby;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
SparseGrid<ElemT>::processNearby(query_pt, radius, process_func);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_SPARSE_POINT_GRID_H
|
||||
147
src/libslic3r/Arachne/utils/SquareGrid.cpp
Normal file
147
src/libslic3r/Arachne/utils/SquareGrid.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "SquareGrid.hpp"
|
||||
#include "../../Point.hpp"
|
||||
|
||||
using namespace Slic3r::Arachne;
|
||||
|
||||
|
||||
SquareGrid::SquareGrid(coord_t cell_size) : cell_size(cell_size)
|
||||
{
|
||||
assert(cell_size > 0U);
|
||||
}
|
||||
|
||||
|
||||
SquareGrid::GridPoint SquareGrid::toGridPoint(const Vec2i64 &point) const
|
||||
{
|
||||
return Point(toGridCoord(point.x()), toGridCoord(point.y()));
|
||||
}
|
||||
|
||||
|
||||
SquareGrid::grid_coord_t SquareGrid::toGridCoord(const int64_t &coord) const
|
||||
{
|
||||
// This mapping via truncation results in the cells with
|
||||
// GridPoint.x==0 being twice as large and similarly for
|
||||
// GridPoint.y==0. This doesn't cause any incorrect behavior,
|
||||
// just changes the running time slightly. The change in running
|
||||
// time from this is probably not worth doing a proper floor
|
||||
// operation.
|
||||
return coord / cell_size;
|
||||
}
|
||||
|
||||
coord_t SquareGrid::toLowerCoord(const grid_coord_t& grid_coord) const
|
||||
{
|
||||
// This mapping via truncation results in the cells with
|
||||
// GridPoint.x==0 being twice as large and similarly for
|
||||
// GridPoint.y==0. This doesn't cause any incorrect behavior,
|
||||
// just changes the running time slightly. The change in running
|
||||
// time from this is probably not worth doing a proper floor
|
||||
// operation.
|
||||
return grid_coord * cell_size;
|
||||
}
|
||||
|
||||
|
||||
bool SquareGrid::processLineCells(const std::pair<Point, Point> line, const std::function<bool (GridPoint)>& process_cell_func)
|
||||
{
|
||||
return static_cast<const SquareGrid*>(this)->processLineCells(line, process_cell_func);
|
||||
}
|
||||
|
||||
|
||||
bool SquareGrid::processLineCells(const std::pair<Point, Point> line, const std::function<bool (GridPoint)>& process_cell_func) const
|
||||
{
|
||||
Point start = line.first;
|
||||
Point end = line.second;
|
||||
if (end.x() < start.x())
|
||||
{ // make sure X increases between start and end
|
||||
std::swap(start, end);
|
||||
}
|
||||
|
||||
const GridPoint start_cell = toGridPoint(start.cast<int64_t>());
|
||||
const GridPoint end_cell = toGridPoint(end.cast<int64_t>());
|
||||
const int64_t y_diff = int64_t(end.y() - start.y());
|
||||
const grid_coord_t y_dir = nonzeroSign(y_diff);
|
||||
|
||||
/* This line drawing algorithm iterates over the range of Y coordinates, and
|
||||
for each Y coordinate computes the range of X coordinates crossed in one
|
||||
unit of Y. These ranges are rounded to be inclusive, so effectively this
|
||||
creates a "fat" line, marking more cells than a strict one-cell-wide path.*/
|
||||
grid_coord_t x_cell_start = start_cell.x();
|
||||
for (grid_coord_t cell_y = start_cell.y(); cell_y * y_dir <= end_cell.y() * y_dir; cell_y += y_dir)
|
||||
{ // for all Y from start to end
|
||||
// nearest y coordinate of the cells in the next row
|
||||
const coord_t nearest_next_y = toLowerCoord(cell_y + ((nonzeroSign(cell_y) == y_dir || cell_y == 0) ? y_dir : coord_t(0)));
|
||||
grid_coord_t x_cell_end; // the X coord of the last cell to include from this row
|
||||
if (y_diff == 0)
|
||||
{
|
||||
x_cell_end = end_cell.x();
|
||||
}
|
||||
else
|
||||
{
|
||||
const int64_t area = int64_t(end.x() - start.x()) * int64_t(nearest_next_y - start.y());
|
||||
// corresponding_x: the x coordinate corresponding to nearest_next_y
|
||||
int64_t corresponding_x = int64_t(start.x()) + area / y_diff;
|
||||
x_cell_end = toGridCoord(corresponding_x + ((corresponding_x < 0) && ((area % y_diff) != 0)));
|
||||
if (x_cell_end < start_cell.x())
|
||||
{ // process at least one cell!
|
||||
x_cell_end = x_cell_start;
|
||||
}
|
||||
}
|
||||
|
||||
for (grid_coord_t cell_x = x_cell_start; cell_x <= x_cell_end; ++cell_x)
|
||||
{
|
||||
GridPoint grid_loc(cell_x, cell_y);
|
||||
if (! process_cell_func(grid_loc))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (grid_loc == end_cell)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// TODO: this causes at least a one cell overlap for each row, which
|
||||
// includes extra cells when crossing precisely on the corners
|
||||
// where positive slope where x > 0 and negative slope where x < 0
|
||||
x_cell_start = x_cell_end;
|
||||
}
|
||||
assert(false && "We should have returned already before here!");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SquareGrid::processNearby
|
||||
(
|
||||
const Point &query_pt,
|
||||
coord_t radius,
|
||||
const std::function<bool (const GridPoint&)>& process_func
|
||||
) const
|
||||
{
|
||||
const Point min_loc(query_pt.x() - radius, query_pt.y() - radius);
|
||||
const Point max_loc(query_pt.x() + radius, query_pt.y() + radius);
|
||||
|
||||
GridPoint min_grid = toGridPoint(min_loc.cast<int64_t>());
|
||||
GridPoint max_grid = toGridPoint(max_loc.cast<int64_t>());
|
||||
|
||||
for (coord_t grid_y = min_grid.y(); grid_y <= max_grid.y(); ++grid_y)
|
||||
{
|
||||
for (coord_t grid_x = min_grid.x(); grid_x <= max_grid.x(); ++grid_x)
|
||||
{
|
||||
GridPoint grid_pt(grid_x,grid_y);
|
||||
if (!process_func(grid_pt))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SquareGrid::grid_coord_t SquareGrid::nonzeroSign(const grid_coord_t z) const
|
||||
{
|
||||
return (z >= 0) - (z < 0);
|
||||
}
|
||||
|
||||
coord_t SquareGrid::getCellSize() const
|
||||
{
|
||||
return cell_size;
|
||||
}
|
||||
110
src/libslic3r/Arachne/utils/SquareGrid.hpp
Normal file
110
src/libslic3r/Arachne/utils/SquareGrid.hpp
Normal file
@@ -0,0 +1,110 @@
|
||||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_SQUARE_GRID_H
|
||||
#define UTILS_SQUARE_GRID_H
|
||||
|
||||
#include "../../Point.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
namespace Slic3r::Arachne {
|
||||
|
||||
/*!
|
||||
* Helper class to calculate coordinates on a square grid, and providing some
|
||||
* utility functions to process grids.
|
||||
*
|
||||
* Doesn't contain any data, except cell size. The purpose is only to
|
||||
* automatically generate coordinates on a grid, and automatically feed them to
|
||||
* functions.
|
||||
* The grid is theoretically infinite (bar integer limits).
|
||||
*/
|
||||
class SquareGrid
|
||||
{
|
||||
public:
|
||||
/*! \brief Constructs a grid with the specified cell size.
|
||||
* \param[in] cell_size The size to use for a cell (square) in the grid.
|
||||
*/
|
||||
SquareGrid(const coord_t cell_size);
|
||||
|
||||
/*!
|
||||
* Get the cell size this grid was created for.
|
||||
*/
|
||||
coord_t getCellSize() const;
|
||||
|
||||
using GridPoint = Point;
|
||||
using grid_coord_t = coord_t;
|
||||
|
||||
/*! \brief Process cells along a line indicated by \p line.
|
||||
*
|
||||
* \param line The line along which to process cells.
|
||||
* \param process_func Processes each cell. ``process_func(elem)`` is called
|
||||
* for each cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing after this function.
|
||||
*/
|
||||
bool processLineCells(const std::pair<Point, Point> line, const std::function<bool (GridPoint)>& process_cell_func);
|
||||
|
||||
/*! \brief Process cells along a line indicated by \p line.
|
||||
*
|
||||
* \param line The line along which to process cells
|
||||
* \param process_func Processes each cell. ``process_func(elem)`` is called
|
||||
* for each cell. Processing stops if function returns false.
|
||||
* \return Whether we need to continue processing after this function.
|
||||
*/
|
||||
bool processLineCells(const std::pair<Point, Point> line, const std::function<bool (GridPoint)>& process_cell_func) const;
|
||||
|
||||
/*! \brief Process cells that might contain sought after points.
|
||||
*
|
||||
* Processes cells that might be within a square with twice \p radius as
|
||||
* width, centered around \p query_pt.
|
||||
* May process elements that are up to radius + cell_size from query_pt.
|
||||
* \param query_pt The point to search around.
|
||||
* \param radius The search radius.
|
||||
* \param process_func Processes each cell. ``process_func(loc)`` is called
|
||||
* for each cell coord within range. Processing stops if function returns
|
||||
* ``false``.
|
||||
* \return Whether we need to continue processing after this function.
|
||||
*/
|
||||
bool processNearby(const Point &query_pt, coord_t radius, const std::function<bool(const GridPoint &)> &process_func) const;
|
||||
|
||||
/*! \brief Compute the grid coordinates of a point.
|
||||
* \param point The actual location.
|
||||
* \return The grid coordinates that correspond to \p point.
|
||||
*/
|
||||
GridPoint toGridPoint(const Vec2i64 &point) const;
|
||||
|
||||
/*! \brief Compute the grid coordinate of a real space coordinate.
|
||||
* \param coord The actual location.
|
||||
* \return The grid coordinate that corresponds to \p coord.
|
||||
*/
|
||||
grid_coord_t toGridCoord(const int64_t &coord) const;
|
||||
|
||||
/*! \brief Compute the lowest coord in a grid cell.
|
||||
* The lowest point is the point in the grid cell closest to the origin.
|
||||
*
|
||||
* \param grid_coord The grid coordinate.
|
||||
* \return The print space coordinate that corresponds to \p grid_coord.
|
||||
*/
|
||||
coord_t toLowerCoord(const grid_coord_t &grid_coord) const;
|
||||
|
||||
protected:
|
||||
/*! \brief The cell (square) size. */
|
||||
coord_t cell_size;
|
||||
|
||||
/*!
|
||||
* Compute the sign of a number.
|
||||
*
|
||||
* The number 0 will result in a positive sign (1).
|
||||
* \param z The number to find the sign of.
|
||||
* \return 1 if the number is positive or 0, or -1 if the number is
|
||||
* negative.
|
||||
*/
|
||||
grid_coord_t nonzeroSign(grid_coord_t z) const;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif //UTILS_SQUARE_GRID_H
|
||||
122
src/libslic3r/Arachne/utils/linearAlg2D.hpp
Normal file
122
src/libslic3r/Arachne/utils/linearAlg2D.hpp
Normal file
@@ -0,0 +1,122 @@
|
||||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef UTILS_LINEAR_ALG_2D_H
|
||||
#define UTILS_LINEAR_ALG_2D_H
|
||||
|
||||
#include "../../Point.hpp"
|
||||
|
||||
namespace Slic3r::Arachne::LinearAlg2D
|
||||
{
|
||||
|
||||
/*!
|
||||
* Test whether a point is inside a corner.
|
||||
* Whether point \p query_point is left of the corner abc.
|
||||
* Whether the \p query_point is in the circle half left of ab and left of bc, rather than to the right.
|
||||
*
|
||||
* Test whether the \p query_point is inside of a polygon w.r.t a single corner.
|
||||
*/
|
||||
inline static bool isInsideCorner(const Point &a, const Point &b, const Point &c, const Vec2i64 &query_point)
|
||||
{
|
||||
// Visualisation for the algorithm below:
|
||||
//
|
||||
// query
|
||||
// |
|
||||
// |
|
||||
// |
|
||||
// perp-----------b
|
||||
// / \ (note that the lines
|
||||
// / \ AB and AC are normalized
|
||||
// / \ to 10000 units length)
|
||||
// a c
|
||||
//
|
||||
|
||||
auto normal = [](const Point &p0, coord_t len) -> Point {
|
||||
int64_t _len = p0.cast<int64_t>().norm();
|
||||
if (_len < 1)
|
||||
return {len, 0};
|
||||
return (p0.cast<int64_t>() * int64_t(len) / _len).cast<coord_t>();
|
||||
};
|
||||
|
||||
auto rotate_90_degree_ccw = [](const Vec2d &p) -> Vec2d {
|
||||
return {-p.y(), p.x()};
|
||||
};
|
||||
|
||||
constexpr coord_t normal_length = 10000; //Create a normal vector of reasonable length in order to reduce rounding error.
|
||||
const Point ba = normal(a - b, normal_length);
|
||||
const Point bc = normal(c - b, normal_length);
|
||||
const Vec2d bq = query_point.cast<double>() - b.cast<double>();
|
||||
const Vec2d perpendicular = rotate_90_degree_ccw(bq); //The query projects to this perpendicular to coordinate 0.
|
||||
|
||||
const double project_a_perpendicular = ba.cast<double>().dot(perpendicular); //Project vertex A on the perpendicular line.
|
||||
const double project_c_perpendicular = bc.cast<double>().dot(perpendicular); //Project vertex C on the perpendicular line.
|
||||
if ((project_a_perpendicular > 0.) != (project_c_perpendicular > 0.)) //Query is between A and C on the projection.
|
||||
{
|
||||
return project_a_perpendicular > 0.; //Due to the winding order of corner ABC, this means that the query is inside.
|
||||
}
|
||||
else //Beyond either A or C, but it could still be inside of the polygon.
|
||||
{
|
||||
const double project_a_parallel = ba.cast<double>().dot(bq); //Project not on the perpendicular, but on the original.
|
||||
const double project_c_parallel = bc.cast<double>().dot(bq);
|
||||
|
||||
//Either:
|
||||
// * A is to the right of B (project_a_perpendicular > 0) and C is below A (project_c_parallel < project_a_parallel), or
|
||||
// * A is to the left of B (project_a_perpendicular < 0) and C is above A (project_c_parallel > project_a_parallel).
|
||||
return (project_c_parallel < project_a_parallel) == (project_a_perpendicular > 0.);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* Returns the determinant of the 2D matrix defined by the the vectors ab and ap as rows.
|
||||
*
|
||||
* The returned value is zero for \p p lying (approximately) on the line going through \p a and \p b
|
||||
* The value is positive for values lying to the left and negative for values lying to the right when looking from \p a to \p b.
|
||||
*
|
||||
* \param p the point to check
|
||||
* \param a the from point of the line
|
||||
* \param b the to point of the line
|
||||
* \return a positive value when \p p lies to the left of the line from \p a to \p b
|
||||
*/
|
||||
static inline int64_t pointIsLeftOfLine(const Point &p, const Point &a, const Point &b)
|
||||
{
|
||||
return int64_t(b.x() - a.x()) * int64_t(p.y() - a.y()) - int64_t(b.y() - a.y()) * int64_t(p.x() - a.x());
|
||||
}
|
||||
|
||||
/*!
|
||||
* Compute the angle between two consecutive line segments.
|
||||
*
|
||||
* The angle is computed from the left side of b when looking from a.
|
||||
*
|
||||
* c
|
||||
* \ .
|
||||
* \ b
|
||||
* angle|
|
||||
* |
|
||||
* a
|
||||
*
|
||||
* \param a start of first line segment
|
||||
* \param b end of first segment and start of second line segment
|
||||
* \param c end of second line segment
|
||||
* \return the angle in radians between 0 and 2 * pi of the corner in \p b
|
||||
*/
|
||||
static inline float getAngleLeft(const Point &a, const Point &b, const Point &c)
|
||||
{
|
||||
const Vec2i64 ba = (a - b).cast<int64_t>();
|
||||
const Vec2i64 bc = (c - b).cast<int64_t>();
|
||||
const int64_t dott = ba.dot(bc); // dot product
|
||||
const int64_t det = cross2(ba, bc); // determinant
|
||||
if (det == 0) {
|
||||
if ((ba.x() != 0 && (ba.x() > 0) == (bc.x() > 0)) || (ba.x() == 0 && (ba.y() > 0) == (bc.y() > 0)))
|
||||
return 0; // pointy bit
|
||||
else
|
||||
return float(M_PI); // straight bit
|
||||
}
|
||||
const float angle = -atan2(double(det), double(dott)); // from -pi to pi
|
||||
if (angle >= 0)
|
||||
return angle;
|
||||
else
|
||||
return M_PI * 2 + angle;
|
||||
}
|
||||
|
||||
}//namespace Slic3r::Arachne
|
||||
#endif//UTILS_LINEAR_ALG_2D_H
|
||||
153
src/libslic3r/ArcFitter.cpp
Normal file
153
src/libslic3r/ArcFitter.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#include "ArcFitter.hpp"
|
||||
#include "Polyline.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void ArcFitter::do_arc_fitting(const Points& points, std::vector<PathFittingData>& result, double tolerance)
|
||||
{
|
||||
#ifdef DEBUG_ARC_FITTING
|
||||
static int irun = 0;
|
||||
BoundingBox bbox_svg;
|
||||
bbox_svg.merge(get_extents(points));
|
||||
Polyline temp = Polyline(points);
|
||||
{
|
||||
std::stringstream stri;
|
||||
stri << "debug_arc_fitting_" << irun << ".svg";
|
||||
SVG svg(stri.str(), bbox_svg);
|
||||
svg.draw(points, "blue", 50000);
|
||||
svg.draw(temp, "red", 1);
|
||||
svg.Close();
|
||||
}
|
||||
++ irun;
|
||||
#endif
|
||||
|
||||
result.clear();
|
||||
result.reserve(points.size() / 2); //worst case size
|
||||
if (points.size() < 3) {
|
||||
PathFittingData data;
|
||||
data.start_point_index = 0;
|
||||
data.end_point_index = points.size() - 1;
|
||||
data.path_type = EMovePathType::Linear_move;
|
||||
result.push_back(data);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t front_index = 0;
|
||||
size_t back_index = 0;
|
||||
ArcSegment last_arc;
|
||||
bool can_fit = false;
|
||||
Points current_segment;
|
||||
current_segment.reserve(points.size());
|
||||
ArcSegment target_arc;
|
||||
for (size_t i = 0; i < points.size(); i++) {
|
||||
//QDS: point in stack is not enough, build stack first
|
||||
back_index = i;
|
||||
current_segment.push_back(points[i]);
|
||||
if (back_index - front_index < 2)
|
||||
continue;
|
||||
|
||||
can_fit = ArcSegment::try_create_arc(current_segment, target_arc, Polyline(current_segment).length(),
|
||||
DEFAULT_SCALED_MAX_RADIUS,
|
||||
tolerance,
|
||||
DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE);
|
||||
if (can_fit) {
|
||||
//QDS: can be fit as arc, then save arc data temperarily
|
||||
last_arc = target_arc;
|
||||
if (back_index == points.size() - 1) {
|
||||
result.emplace_back(std::move(PathFittingData{ front_index,
|
||||
back_index,
|
||||
last_arc.direction == ArcDirection::Arc_Dir_CCW ? EMovePathType::Arc_move_ccw : EMovePathType::Arc_move_cw,
|
||||
last_arc }));
|
||||
front_index = back_index;
|
||||
}
|
||||
} else {
|
||||
if (back_index - front_index > 2) {
|
||||
//QDS: althought current point_stack can't be fit as arc,
|
||||
//but previous must can be fit if removing the top in stack, so save last arc
|
||||
result.emplace_back(std::move(PathFittingData{ front_index,
|
||||
back_index - 1,
|
||||
last_arc.direction == ArcDirection::Arc_Dir_CCW ? EMovePathType::Arc_move_ccw : EMovePathType::Arc_move_cw,
|
||||
last_arc }));
|
||||
} else {
|
||||
//QDS: save the first segment as line move when 3 point-line can't be fit as arc move
|
||||
if (result.empty() || result.back().path_type != EMovePathType::Linear_move)
|
||||
result.emplace_back(std::move(PathFittingData{front_index, front_index + 1, EMovePathType::Linear_move, ArcSegment()}));
|
||||
else if(result.back().path_type == EMovePathType::Linear_move)
|
||||
result.back().end_point_index = front_index + 1;
|
||||
}
|
||||
front_index = back_index - 1;
|
||||
current_segment.clear();
|
||||
current_segment.push_back(points[front_index]);
|
||||
current_segment.push_back(points[front_index + 1]);
|
||||
}
|
||||
}
|
||||
//QDS: handle the remain data
|
||||
if (front_index != back_index) {
|
||||
if (result.empty() || result.back().path_type != EMovePathType::Linear_move)
|
||||
result.emplace_back(std::move(PathFittingData{front_index, back_index, EMovePathType::Linear_move, ArcSegment()}));
|
||||
else if (result.back().path_type == EMovePathType::Linear_move)
|
||||
result.back().end_point_index = back_index;
|
||||
}
|
||||
result.shrink_to_fit();
|
||||
}
|
||||
|
||||
void ArcFitter::do_arc_fitting_and_simplify(Points& points, std::vector<PathFittingData>& result, double tolerance)
|
||||
{
|
||||
//QDS: 1 do arc fit first
|
||||
if (abs(tolerance) > SCALED_EPSILON)
|
||||
ArcFitter::do_arc_fitting(points, result, tolerance);
|
||||
else
|
||||
result.push_back(PathFittingData{ 0, points.size() - 1, EMovePathType::Linear_move, ArcSegment() });
|
||||
|
||||
//QDS: 2 for straight part which can't fit arc, use DP simplify
|
||||
//for arc part, only need to keep start and end point
|
||||
if (result.size() == 1 && result[0].path_type == EMovePathType::Linear_move) {
|
||||
//QDS: all are straight segment, directly use DP simplify
|
||||
points = MultiPoint::_douglas_peucker(points, tolerance);
|
||||
result[0].end_point_index = points.size() - 1;
|
||||
return;
|
||||
} else {
|
||||
//QDS: has both arc part and straight part, we should spilit the straight part out and do DP simplify
|
||||
Points simplified_points;
|
||||
simplified_points.reserve(points.size());
|
||||
simplified_points.push_back(points[0]);
|
||||
std::vector<size_t> reduce_count(result.size(), 0);
|
||||
for (size_t i = 0; i < result.size(); i++)
|
||||
{
|
||||
size_t start_index = result[i].start_point_index;
|
||||
size_t end_index = result[i].end_point_index;
|
||||
//QDS: get the straight and arc part, and do simplifing independently.
|
||||
//Why: It's obvious that we need to use DP to simplify straight part to reduce point.
|
||||
//For arc part, theoretically, we only need to keep the start and end point, and
|
||||
//delete all other point. But when considering wipe operation, we must keep the original
|
||||
//point data and shouldn't reduce too much by only saving start and end point.
|
||||
Points straight_or_arc_part;
|
||||
straight_or_arc_part.reserve(end_index - start_index + 1);
|
||||
for (size_t j = start_index; j <= end_index; j++)
|
||||
straight_or_arc_part.push_back(points[j]);
|
||||
straight_or_arc_part = MultiPoint::_douglas_peucker(straight_or_arc_part, tolerance);
|
||||
//QDS: how many point has been reduced
|
||||
reduce_count[i] = end_index - start_index + 1 - straight_or_arc_part.size();
|
||||
//QDS: save the simplified result
|
||||
for (size_t j = 1; j < straight_or_arc_part.size(); j++) {
|
||||
simplified_points.push_back(straight_or_arc_part[j]);
|
||||
}
|
||||
}
|
||||
//QDS: save and will return the simplified_points
|
||||
points = simplified_points;
|
||||
//QDS: modify the index in result because the point index must be changed to match the simplified points
|
||||
for (size_t j = 1; j < reduce_count.size(); j++)
|
||||
reduce_count[j] += reduce_count[j - 1];
|
||||
for (size_t j = 0; j < result.size(); j++)
|
||||
{
|
||||
result[j].end_point_index -= reduce_count[j];
|
||||
if (j != result.size() - 1)
|
||||
result[j + 1].start_point_index = result[j].end_point_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
53
src/libslic3r/ArcFitter.hpp
Normal file
53
src/libslic3r/ArcFitter.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef slic3r_ArcFitter_hpp_
|
||||
#define slic3r_ArcFitter_hpp_
|
||||
|
||||
#include "Circle.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//QDS: linear move(G0 and G1) or arc move(G2 and G3).
|
||||
enum class EMovePathType : unsigned char
|
||||
{
|
||||
Noop_move,
|
||||
Linear_move,
|
||||
Arc_move_cw,
|
||||
Arc_move_ccw,
|
||||
Count
|
||||
};
|
||||
|
||||
//QDS
|
||||
struct PathFittingData{
|
||||
size_t start_point_index;
|
||||
size_t end_point_index;
|
||||
EMovePathType path_type;
|
||||
// QDS: only valid when path_type is arc move
|
||||
// Used to store detail information of arc segment
|
||||
ArcSegment arc_data;
|
||||
|
||||
bool is_linear_move() {
|
||||
return (path_type == EMovePathType::Linear_move);
|
||||
}
|
||||
bool is_arc_move() {
|
||||
return (path_type == EMovePathType::Arc_move_ccw || path_type == EMovePathType::Arc_move_cw);
|
||||
}
|
||||
bool reverse_arc_path() {
|
||||
if (!is_arc_move() || !arc_data.reverse())
|
||||
return false;
|
||||
path_type = (arc_data.direction == ArcDirection::Arc_Dir_CCW) ? EMovePathType::Arc_move_ccw : EMovePathType::Arc_move_cw;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class ArcFitter {
|
||||
public:
|
||||
//QDS: this function is used to check the point list and return which part can fit as arc, which part should be line
|
||||
static void do_arc_fitting(const Points& points, std::vector<PathFittingData> &result, double tolerance);
|
||||
//QDS: this function is used to check the point list and return which part can fit as arc, which part should be line.
|
||||
//By the way, it also use DP simplify to reduce point of straight part and only keep the start and end point of arc.
|
||||
static void do_arc_fitting_and_simplify(Points& points, std::vector<PathFittingData>& result, double tolerance);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
1147
src/libslic3r/Arrange.cpp
Normal file
1147
src/libslic3r/Arrange.cpp
Normal file
File diff suppressed because it is too large
Load Diff
217
src/libslic3r/Arrange.hpp
Normal file
217
src/libslic3r/Arrange.hpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#ifndef ARRANGE_HPP
|
||||
#define ARRANGE_HPP
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
#define BED_SHRINK_SEQ_PRINT 5
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class BoundingBox;
|
||||
|
||||
namespace arrangement {
|
||||
|
||||
/// A geometry abstraction for a circular print bed. Similarly to BoundingBox.
|
||||
class CircleBed {
|
||||
Point center_;
|
||||
double radius_;
|
||||
public:
|
||||
|
||||
inline CircleBed(): center_(0, 0), radius_(std::nan("")) {}
|
||||
explicit inline CircleBed(const Point& c, double r): center_(c), radius_(r) {}
|
||||
|
||||
inline double radius() const { return radius_; }
|
||||
inline const Point& center() const { return center_; }
|
||||
};
|
||||
|
||||
/// Representing an unbounded bed.
|
||||
struct InfiniteBed {
|
||||
Point center;
|
||||
explicit InfiniteBed(const Point &p = {0, 0}): center{p} {}
|
||||
};
|
||||
|
||||
/// A logical bed representing an object not being arranged. Either the arrange
|
||||
/// has not yet successfully run on this ArrangePolygon or it could not fit the
|
||||
/// object due to overly large size or invalid geometry.
|
||||
static const constexpr int UNARRANGED = -1;
|
||||
|
||||
/// Input/Output structure for the arrange() function. The poly field will not
|
||||
/// be modified during arrangement. Instead, the translation and rotation fields
|
||||
/// will mark the needed transformation for the polygon to be in the arranged
|
||||
/// position. These can also be set to an initial offset and rotation.
|
||||
///
|
||||
/// The bed_idx field will indicate the logical bed into which the
|
||||
/// polygon belongs: UNARRANGED means no place for the polygon
|
||||
/// (also the initial state before arrange), 0..N means the index of the bed.
|
||||
/// Zero is the physical bed, larger than zero means a virtual bed.
|
||||
struct ArrangePolygon {
|
||||
ExPolygon poly; /// The 2D silhouette to be arranged
|
||||
Vec2crd translation{0, 0}; /// The translation of the poly
|
||||
double rotation{0.0}; /// The rotation of the poly in radians
|
||||
coord_t inflation = 0; /// Arrange with inflated polygon
|
||||
int bed_idx{UNARRANGED}; /// To which logical bed does poly belong...
|
||||
int priority{0};
|
||||
//QDS: add locked_plate to indicate whether it is in the locked plate
|
||||
int locked_plate{ -1 };
|
||||
bool is_virt_object{ false };
|
||||
bool is_extrusion_cali_object{ false };
|
||||
bool is_wipe_tower{ false };
|
||||
bool has_tree_support{false};
|
||||
//QDS: add row/col for sudoku-style layout
|
||||
int row{0};
|
||||
int col{0};
|
||||
std::vector<int> extrude_ids{}; /// extruder_id for least extruder switch
|
||||
int filament_temp_type{ -1 };
|
||||
int bed_temp{0}; ///bed temperature for different material judge
|
||||
int print_temp{0}; ///print temperature for different material judge
|
||||
int first_bed_temp{ 0 }; ///first layer bed temperature for different material judge
|
||||
int first_print_temp{ 0 }; ///first layer print temperature for different material judge
|
||||
int vitrify_temp{ 0 }; // max bed temperature for material compatibility, which is usually the filament vitrification temp
|
||||
int itemid{ 0 }; // item id in the vector, used for accessing all possible params like extrude_id
|
||||
int is_applied{ 0 }; // transform has been applied
|
||||
double height{ 0 }; // item height
|
||||
double brim_width{ 0 }; // brim width
|
||||
std::string name;
|
||||
|
||||
// If empty, any rotation is allowed (currently unsupported)
|
||||
// If only a zero is there, no rotation is allowed
|
||||
std::vector<double> allowed_rotations = {0.};
|
||||
|
||||
/// Optional setter function which can store arbitrary data in its closure
|
||||
std::function<void(const ArrangePolygon&)> setter = nullptr;
|
||||
|
||||
/// Helper function to call the setter with the arrange data arguments
|
||||
void apply() {
|
||||
if (setter && !is_applied) {
|
||||
setter(*this);
|
||||
is_applied = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Test if arrange() was called previously and gave a successful result.
|
||||
bool is_arranged() const { return bed_idx != UNARRANGED; }
|
||||
|
||||
inline ExPolygon transformed_poly() const
|
||||
{
|
||||
ExPolygon ret = poly;
|
||||
ret.rotate(rotation);
|
||||
ret.translate(translation.x(), translation.y());
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
using ArrangePolygons = std::vector<ArrangePolygon>;
|
||||
|
||||
struct ArrangeParams {
|
||||
|
||||
/// The minimum distance which is allowed for any
|
||||
/// pair of items on the print bed in any direction.
|
||||
coord_t min_obj_distance = 0;
|
||||
|
||||
/// The accuracy of optimization.
|
||||
/// Goes from 0.0 to 1.0 and scales performance as well
|
||||
float accuracy = 1.f;
|
||||
|
||||
/// Allow parallel execution.
|
||||
bool parallel = true;
|
||||
|
||||
bool allow_rotations = false;
|
||||
|
||||
bool do_final_align = true;
|
||||
|
||||
//QDS: add specific arrange params
|
||||
bool allow_multi_materials_on_same_plate = true;
|
||||
bool avoid_extrusion_cali_region = true;
|
||||
bool is_seq_print = false;
|
||||
bool align_to_y_axis = false;
|
||||
float bed_shrink_x = 0.1;
|
||||
float bed_shrink_y = 0.1;
|
||||
float brim_skirt_distance = 0;
|
||||
float clearance_height_to_rod = 0;
|
||||
float clearance_height_to_lid = 0;
|
||||
float cleareance_radius = 0;
|
||||
float nozzle_height = 0;
|
||||
float printable_height = 256.0;
|
||||
Vec2d align_center{ 0.5,0.5 };
|
||||
|
||||
ArrangePolygons excluded_regions; // regions cant't be used
|
||||
ArrangePolygons nonprefered_regions; // regions can be used but not prefered
|
||||
|
||||
/// Progress indicator callback called when an object gets packed.
|
||||
/// The unsigned argument is the number of items remaining to pack.
|
||||
std::function<void(unsigned, std::string)> progressind = [](unsigned st, std::string str = "") {
|
||||
std::cout << "st=" << st << ", " << str << std::endl;
|
||||
};
|
||||
|
||||
std::function<void(const ArrangePolygon &)> on_packed;
|
||||
|
||||
/// A predicate returning true if abort is needed.
|
||||
std::function<bool(void)> stopcondition;
|
||||
|
||||
ArrangeParams() = default;
|
||||
explicit ArrangeParams(coord_t md) : min_obj_distance(md) {}
|
||||
// to json format
|
||||
std::string to_json() const{
|
||||
std::string ret = "{";
|
||||
ret += "\"min_obj_distance\":" + std::to_string(min_obj_distance) + ",";
|
||||
ret += "\"accuracy\":" + std::to_string(accuracy) + ",";
|
||||
ret += "\"parallel\":" + std::to_string(parallel) + ",";
|
||||
ret += "\"allow_rotations\":" + std::to_string(allow_rotations) + ",";
|
||||
ret += "\"do_final_align\":" + std::to_string(do_final_align) + ",";
|
||||
ret += "\"allow_multi_materials_on_same_plate\":" + std::to_string(allow_multi_materials_on_same_plate) + ",";
|
||||
ret += "\"avoid_extrusion_cali_region\":" + std::to_string(avoid_extrusion_cali_region) + ",";
|
||||
ret += "\"is_seq_print\":" + std::to_string(is_seq_print) + ",";
|
||||
ret += "\"bed_shrink_x\":" + std::to_string(bed_shrink_x) + ",";
|
||||
ret += "\"bed_shrink_y\":" + std::to_string(bed_shrink_y) + ",";
|
||||
ret += "\"brim_skirt_distance\":" + std::to_string(brim_skirt_distance) + ",";
|
||||
ret += "\"clearance_height_to_rod\":" + std::to_string(clearance_height_to_rod) + ",";
|
||||
ret += "\"clearance_height_to_lid\":" + std::to_string(clearance_height_to_lid) + ",";
|
||||
ret += "\"cleareance_radius\":" + std::to_string(cleareance_radius) + ",";
|
||||
ret += "\"printable_height\":" + std::to_string(printable_height) + ",";
|
||||
return ret;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void update_arrange_params(ArrangeParams& params, const DynamicPrintConfig* print_cfg, const ArrangePolygons& selected);
|
||||
|
||||
void update_selected_items_inflation(ArrangePolygons& selected, const DynamicPrintConfig* print_cfg, ArrangeParams& params);
|
||||
|
||||
void update_unselected_items_inflation(ArrangePolygons& unselected, const DynamicPrintConfig* print_cfg, const ArrangeParams& params);
|
||||
|
||||
void update_selected_items_axis_align(ArrangePolygons& selected, const DynamicPrintConfig* print_cfg, const ArrangeParams& params);
|
||||
|
||||
Points get_shrink_bedpts(const DynamicPrintConfig* print_cfg, const ArrangeParams& params);
|
||||
|
||||
/**
|
||||
* \brief Arranges the input polygons.
|
||||
*
|
||||
* WARNING: Currently, only convex polygons are supported by the libnest2d
|
||||
* library which is used to do the arrangement. This might change in the future
|
||||
* this is why the interface contains a general polygon capable to have holes.
|
||||
*
|
||||
* \param items Input vector of ArrangePolygons. The transformation, rotation
|
||||
* and bin_idx fields will be changed after the call finished and can be used
|
||||
* to apply the result on the input polygon.
|
||||
*/
|
||||
template<class TBed> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const TBed &bed, const ArrangeParams ¶ms = {});
|
||||
|
||||
// A dispatch function that determines the bed shape from a set of points.
|
||||
template<> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Points &bed, const ArrangeParams ¶ms);
|
||||
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms);
|
||||
|
||||
inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
|
||||
}} // namespace Slic3r::arrangement
|
||||
|
||||
#endif // MODELARRANGE_HPP
|
||||
84
src/libslic3r/BlacklistedLibraryCheck.cpp
Normal file
84
src/libslic3r/BlacklistedLibraryCheck.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#include "BlacklistedLibraryCheck.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <psapi.h>
|
||||
# endif //WIN32
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
//only dll name with .dll suffix - currently case sensitive
|
||||
const std::vector<std::wstring> BlacklistedLibraryCheck::blacklist({ L"NahimicOSD.dll", L"SS2OSD.dll", L"amhook.dll", L"AMHook.dll" });
|
||||
|
||||
bool BlacklistedLibraryCheck::get_blacklisted(std::vector<std::wstring>& names)
|
||||
{
|
||||
if (m_found.empty())
|
||||
return false;
|
||||
for (const auto& lib : m_found)
|
||||
names.emplace_back(lib);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::wstring BlacklistedLibraryCheck::get_blacklisted_string()
|
||||
{
|
||||
std::wstring ret;
|
||||
for (const auto& lib : m_found)
|
||||
ret += lib + L"\n";
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool BlacklistedLibraryCheck::perform_check()
|
||||
{
|
||||
// Get the pseudo-handle for the current process.
|
||||
HANDLE hCurrentProcess = GetCurrentProcess();
|
||||
|
||||
// Get a list of all the modules in this process.
|
||||
HMODULE hMods[1024];
|
||||
DWORD cbNeeded;
|
||||
if (EnumProcessModulesEx(hCurrentProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL))
|
||||
{
|
||||
//printf("Total Dlls: %d\n", cbNeeded / sizeof(HMODULE));
|
||||
for (unsigned int i = 0; i < cbNeeded / sizeof(HMODULE); ++ i)
|
||||
{
|
||||
wchar_t szModName[MAX_PATH];
|
||||
// Get the full path to the module's file.
|
||||
if (GetModuleFileNameExW(hCurrentProcess, hMods[i], szModName, MAX_PATH))
|
||||
{
|
||||
// Add to list if blacklisted
|
||||
if (BlacklistedLibraryCheck::is_blacklisted(szModName)) {
|
||||
//wprintf(L"Contains library: %s\n", szModName);
|
||||
if (std::find(m_found.begin(), m_found.end(), szModName) == m_found.end())
|
||||
m_found.emplace_back(szModName);
|
||||
}
|
||||
//wprintf(L"%s\n", szModName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//printf("\n");
|
||||
return !m_found.empty();
|
||||
}
|
||||
|
||||
bool BlacklistedLibraryCheck::is_blacklisted(const std::wstring &dllpath)
|
||||
{
|
||||
std::wstring dllname = boost::filesystem::path(dllpath).filename().wstring();
|
||||
//std::transform(dllname.begin(), dllname.end(), dllname.begin(), std::tolower);
|
||||
if (std::find(BlacklistedLibraryCheck::blacklist.begin(), BlacklistedLibraryCheck::blacklist.end(), dllname) != BlacklistedLibraryCheck::blacklist.end()) {
|
||||
//std::wprintf(L"%s is blacklisted\n", dllname.c_str());
|
||||
return true;
|
||||
}
|
||||
//std::wprintf(L"%s is NOT blacklisted\n", dllname.c_str());
|
||||
return false;
|
||||
}
|
||||
bool BlacklistedLibraryCheck::is_blacklisted(const std::string &dllpath)
|
||||
{
|
||||
return BlacklistedLibraryCheck::is_blacklisted(boost::nowide::widen(dllpath));
|
||||
}
|
||||
|
||||
#endif //WIN32
|
||||
|
||||
} // namespace Slic3r
|
||||
46
src/libslic3r/BlacklistedLibraryCheck.hpp
Normal file
46
src/libslic3r/BlacklistedLibraryCheck.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef slic3r_BlacklistedLibraryCheck_hpp_
|
||||
#define slic3r_BlacklistedLibraryCheck_hpp_
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#endif //WIN32
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef WIN32
|
||||
class BlacklistedLibraryCheck
|
||||
{
|
||||
public:
|
||||
static BlacklistedLibraryCheck& get_instance()
|
||||
{
|
||||
static BlacklistedLibraryCheck instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
private:
|
||||
BlacklistedLibraryCheck() = default;
|
||||
|
||||
std::vector<std::wstring> m_found;
|
||||
public:
|
||||
BlacklistedLibraryCheck(BlacklistedLibraryCheck const&) = delete;
|
||||
void operator=(BlacklistedLibraryCheck const&) = delete;
|
||||
// returns all found blacklisted dlls
|
||||
bool get_blacklisted(std::vector<std::wstring>& names);
|
||||
std::wstring get_blacklisted_string();
|
||||
// returns true if enumerating found blacklisted dll
|
||||
bool perform_check();
|
||||
|
||||
// UTF-8 encoded path
|
||||
static bool is_blacklisted(const std::string &dllpath);
|
||||
static bool is_blacklisted(const std::wstring &dllpath);
|
||||
private:
|
||||
static const std::vector<std::wstring> blacklist;
|
||||
};
|
||||
|
||||
#endif //WIN32
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif //slic3r_BlacklistedLibraryCheck_hpp_
|
||||
289
src/libslic3r/BoundingBox.cpp
Normal file
289
src/libslic3r/BoundingBox.cpp
Normal file
@@ -0,0 +1,289 @@
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
template BoundingBoxBase<Point>::BoundingBoxBase(const std::vector<Point> &points);
|
||||
template BoundingBoxBase<Vec2d>::BoundingBoxBase(const std::vector<Vec2d> &points);
|
||||
|
||||
template BoundingBox3Base<Vec3d>::BoundingBox3Base(const std::vector<Vec3d> &points);
|
||||
|
||||
void BoundingBox::polygon(Polygon* polygon) const
|
||||
{
|
||||
polygon->points.clear();
|
||||
polygon->points.resize(4);
|
||||
polygon->points[0](0) = this->min(0);
|
||||
polygon->points[0](1) = this->min(1);
|
||||
polygon->points[1](0) = this->max(0);
|
||||
polygon->points[1](1) = this->min(1);
|
||||
polygon->points[2](0) = this->max(0);
|
||||
polygon->points[2](1) = this->max(1);
|
||||
polygon->points[3](0) = this->min(0);
|
||||
polygon->points[3](1) = this->max(1);
|
||||
}
|
||||
|
||||
Polygon BoundingBox::polygon() const
|
||||
{
|
||||
Polygon p;
|
||||
this->polygon(&p);
|
||||
return p;
|
||||
}
|
||||
|
||||
BoundingBox BoundingBox::rotated(double angle) const
|
||||
{
|
||||
BoundingBox out;
|
||||
out.merge(this->min.rotated(angle));
|
||||
out.merge(this->max.rotated(angle));
|
||||
out.merge(Point(this->min(0), this->max(1)).rotated(angle));
|
||||
out.merge(Point(this->max(0), this->min(1)).rotated(angle));
|
||||
return out;
|
||||
}
|
||||
|
||||
BoundingBox BoundingBox::rotated(double angle, const Point ¢er) const
|
||||
{
|
||||
BoundingBox out;
|
||||
out.merge(this->min.rotated(angle, center));
|
||||
out.merge(this->max.rotated(angle, center));
|
||||
out.merge(Point(this->min(0), this->max(1)).rotated(angle, center));
|
||||
out.merge(Point(this->max(0), this->min(1)).rotated(angle, center));
|
||||
return out;
|
||||
}
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBoxBase<PointClass>::scale(double factor)
|
||||
{
|
||||
this->min *= factor;
|
||||
this->max *= factor;
|
||||
}
|
||||
template void BoundingBoxBase<Point>::scale(double factor);
|
||||
template void BoundingBoxBase<Vec2d>::scale(double factor);
|
||||
template void BoundingBoxBase<Vec3d>::scale(double factor);
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBoxBase<PointClass>::merge(const PointClass &point)
|
||||
{
|
||||
if (this->defined) {
|
||||
this->min = this->min.cwiseMin(point);
|
||||
this->max = this->max.cwiseMax(point);
|
||||
} else {
|
||||
this->min = point;
|
||||
this->max = point;
|
||||
this->defined = true;
|
||||
}
|
||||
}
|
||||
template void BoundingBoxBase<Point>::merge(const Point &point);
|
||||
template void BoundingBoxBase<Vec2f>::merge(const Vec2f &point);
|
||||
template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBoxBase<PointClass>::merge(const std::vector<PointClass> &points)
|
||||
{
|
||||
this->merge(BoundingBoxBase(points));
|
||||
}
|
||||
template void BoundingBoxBase<Point>::merge(const Points &points);
|
||||
template void BoundingBoxBase<Vec2d>::merge(const Pointfs &points);
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBoxBase<PointClass>::merge(const BoundingBoxBase<PointClass> &bb)
|
||||
{
|
||||
assert(bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1));
|
||||
if (bb.defined) {
|
||||
if (this->defined) {
|
||||
this->min = this->min.cwiseMin(bb.min);
|
||||
this->max = this->max.cwiseMax(bb.max);
|
||||
} else {
|
||||
this->min = bb.min;
|
||||
this->max = bb.max;
|
||||
this->defined = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
template void BoundingBoxBase<Point>::merge(const BoundingBoxBase<Point> &bb);
|
||||
template void BoundingBoxBase<Vec2f>::merge(const BoundingBoxBase<Vec2f> &bb);
|
||||
template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
|
||||
|
||||
//QDS
|
||||
template <class PointClass>
|
||||
Polygon BoundingBox3Base<PointClass>::polygon(bool is_scaled) const
|
||||
{
|
||||
Polygon polygon;
|
||||
polygon.points.clear();
|
||||
polygon.points.resize(4);
|
||||
double scale_factor = 1 / (is_scaled ? SCALING_FACTOR : 1);
|
||||
polygon.points[0](0) = this->min(0) * scale_factor;
|
||||
polygon.points[0](1) = this->min(1) * scale_factor;
|
||||
polygon.points[1](0) = this->max(0) * scale_factor;
|
||||
polygon.points[1](1) = this->min(1) * scale_factor;
|
||||
polygon.points[2](0) = this->max(0) * scale_factor;
|
||||
polygon.points[2](1) = this->max(1) * scale_factor;
|
||||
polygon.points[3](0) = this->min(0) * scale_factor;
|
||||
polygon.points[3](1) = this->max(1) * scale_factor;
|
||||
return polygon;
|
||||
}
|
||||
template Polygon BoundingBox3Base<Vec3f>::polygon(bool is_scaled) const;
|
||||
template Polygon BoundingBox3Base<Vec3d>::polygon(bool is_scaled) const;
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBox3Base<PointClass>::merge(const PointClass &point)
|
||||
{
|
||||
if (this->defined) {
|
||||
this->min = this->min.cwiseMin(point);
|
||||
this->max = this->max.cwiseMax(point);
|
||||
} else {
|
||||
this->min = point;
|
||||
this->max = point;
|
||||
this->defined = true;
|
||||
}
|
||||
}
|
||||
template void BoundingBox3Base<Vec3f>::merge(const Vec3f &point);
|
||||
template void BoundingBox3Base<Vec3d>::merge(const Vec3d &point);
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBox3Base<PointClass>::merge(const std::vector<PointClass> &points)
|
||||
{
|
||||
this->merge(BoundingBox3Base(points));
|
||||
}
|
||||
template void BoundingBox3Base<Vec3d>::merge(const Pointf3s &points);
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBox3Base<PointClass>::merge(const BoundingBox3Base<PointClass> &bb)
|
||||
{
|
||||
assert(bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1) || bb.min(2) >= bb.max(2));
|
||||
if (bb.defined) {
|
||||
if (this->defined) {
|
||||
this->min = this->min.cwiseMin(bb.min);
|
||||
this->max = this->max.cwiseMax(bb.max);
|
||||
} else {
|
||||
this->min = bb.min;
|
||||
this->max = bb.max;
|
||||
this->defined = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
template void BoundingBox3Base<Vec3d>::merge(const BoundingBox3Base<Vec3d> &bb);
|
||||
|
||||
template <class PointClass> PointClass
|
||||
BoundingBoxBase<PointClass>::size() const
|
||||
{
|
||||
return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1));
|
||||
}
|
||||
template Point BoundingBoxBase<Point>::size() const;
|
||||
template Vec2f BoundingBoxBase<Vec2f>::size() const;
|
||||
template Vec2d BoundingBoxBase<Vec2d>::size() const;
|
||||
|
||||
template <class PointClass> PointClass
|
||||
BoundingBox3Base<PointClass>::size() const
|
||||
{
|
||||
return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1), this->max(2) - this->min(2));
|
||||
}
|
||||
template Vec3f BoundingBox3Base<Vec3f>::size() const;
|
||||
template Vec3d BoundingBox3Base<Vec3d>::size() const;
|
||||
|
||||
template <class PointClass> double BoundingBoxBase<PointClass>::radius() const
|
||||
{
|
||||
assert(this->defined);
|
||||
double x = this->max(0) - this->min(0);
|
||||
double y = this->max(1) - this->min(1);
|
||||
return 0.5 * sqrt(x*x+y*y);
|
||||
}
|
||||
template double BoundingBoxBase<Point>::radius() const;
|
||||
template double BoundingBoxBase<Vec2d>::radius() const;
|
||||
|
||||
template <class PointClass> double BoundingBox3Base<PointClass>::radius() const
|
||||
{
|
||||
double x = this->max(0) - this->min(0);
|
||||
double y = this->max(1) - this->min(1);
|
||||
double z = this->max(2) - this->min(2);
|
||||
return 0.5 * sqrt(x*x+y*y+z*z);
|
||||
}
|
||||
template double BoundingBox3Base<Vec3d>::radius() const;
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBoxBase<PointClass>::offset(coordf_t delta)
|
||||
{
|
||||
PointClass v(delta, delta);
|
||||
this->min -= v;
|
||||
this->max += v;
|
||||
}
|
||||
template void BoundingBoxBase<Point>::offset(coordf_t delta);
|
||||
template void BoundingBoxBase<Vec2d>::offset(coordf_t delta);
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBox3Base<PointClass>::offset(coordf_t delta)
|
||||
{
|
||||
PointClass v(delta, delta, delta);
|
||||
this->min -= v;
|
||||
this->max += v;
|
||||
}
|
||||
template void BoundingBox3Base<Vec3d>::offset(coordf_t delta);
|
||||
|
||||
template <class PointClass> PointClass
|
||||
BoundingBoxBase<PointClass>::center() const
|
||||
{
|
||||
return (this->min + this->max) / 2;
|
||||
}
|
||||
template Point BoundingBoxBase<Point>::center() const;
|
||||
template Vec2f BoundingBoxBase<Vec2f>::center() const;
|
||||
template Vec2d BoundingBoxBase<Vec2d>::center() const;
|
||||
|
||||
template <class PointClass> PointClass
|
||||
BoundingBox3Base<PointClass>::center() const
|
||||
{
|
||||
return (this->min + this->max) / 2;
|
||||
}
|
||||
template Vec3f BoundingBox3Base<Vec3f>::center() const;
|
||||
template Vec3d BoundingBox3Base<Vec3d>::center() const;
|
||||
|
||||
template <class PointClass> coordf_t
|
||||
BoundingBox3Base<PointClass>::max_size() const
|
||||
{
|
||||
PointClass s = size();
|
||||
return std::max(s(0), std::max(s(1), s(2)));
|
||||
}
|
||||
template coordf_t BoundingBox3Base<Vec3f>::max_size() const;
|
||||
template coordf_t BoundingBox3Base<Vec3d>::max_size() const;
|
||||
|
||||
void BoundingBox::align_to_grid(const coord_t cell_size)
|
||||
{
|
||||
if (this->defined) {
|
||||
min(0) = Slic3r::align_to_grid(min(0), cell_size);
|
||||
min(1) = Slic3r::align_to_grid(min(1), cell_size);
|
||||
}
|
||||
}
|
||||
|
||||
BoundingBoxf3 BoundingBoxf3::transformed(const Transform3d& matrix) const
|
||||
{
|
||||
typedef Eigen::Matrix<double, 3, 8, Eigen::DontAlign> Vertices;
|
||||
|
||||
Vertices src_vertices;
|
||||
src_vertices(0, 0) = min(0); src_vertices(1, 0) = min(1); src_vertices(2, 0) = min(2);
|
||||
src_vertices(0, 1) = max(0); src_vertices(1, 1) = min(1); src_vertices(2, 1) = min(2);
|
||||
src_vertices(0, 2) = max(0); src_vertices(1, 2) = max(1); src_vertices(2, 2) = min(2);
|
||||
src_vertices(0, 3) = min(0); src_vertices(1, 3) = max(1); src_vertices(2, 3) = min(2);
|
||||
src_vertices(0, 4) = min(0); src_vertices(1, 4) = min(1); src_vertices(2, 4) = max(2);
|
||||
src_vertices(0, 5) = max(0); src_vertices(1, 5) = min(1); src_vertices(2, 5) = max(2);
|
||||
src_vertices(0, 6) = max(0); src_vertices(1, 6) = max(1); src_vertices(2, 6) = max(2);
|
||||
src_vertices(0, 7) = min(0); src_vertices(1, 7) = max(1); src_vertices(2, 7) = max(2);
|
||||
|
||||
Vertices dst_vertices = matrix * src_vertices.colwise().homogeneous();
|
||||
|
||||
Vec3d v_min(dst_vertices(0, 0), dst_vertices(1, 0), dst_vertices(2, 0));
|
||||
Vec3d v_max = v_min;
|
||||
|
||||
for (int i = 1; i < 8; ++i)
|
||||
{
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
v_min(j) = std::min(v_min(j), dst_vertices(j, i));
|
||||
v_max(j) = std::max(v_max(j), dst_vertices(j, i));
|
||||
}
|
||||
}
|
||||
|
||||
return BoundingBoxf3(v_min, v_max);
|
||||
}
|
||||
|
||||
}
|
||||
284
src/libslic3r/BoundingBox.hpp
Normal file
284
src/libslic3r/BoundingBox.hpp
Normal file
@@ -0,0 +1,284 @@
|
||||
#ifndef slic3r_BoundingBox_hpp_
|
||||
#define slic3r_BoundingBox_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Exception.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include <ostream>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
template <class PointClass>
|
||||
class BoundingBoxBase
|
||||
{
|
||||
public:
|
||||
PointClass min;
|
||||
PointClass max;
|
||||
bool defined;
|
||||
|
||||
BoundingBoxBase() : min(PointClass::Zero()), max(PointClass::Zero()), defined(false) {}
|
||||
BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) :
|
||||
min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
|
||||
BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
|
||||
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
|
||||
|
||||
|
||||
template<class It, class = IteratorOnly<It>>
|
||||
BoundingBoxBase(It from, It to)
|
||||
{
|
||||
construct(*this, from, to);
|
||||
}
|
||||
|
||||
BoundingBoxBase(const std::vector<PointClass> &points)
|
||||
: BoundingBoxBase(points.begin(), points.end())
|
||||
{}
|
||||
|
||||
void reset() { this->defined = false; this->min = PointClass::Zero(); this->max = PointClass::Zero(); }
|
||||
void merge(const PointClass &point);
|
||||
void merge(const std::vector<PointClass> &points);
|
||||
void merge(const BoundingBoxBase<PointClass> &bb);
|
||||
void scale(double factor);
|
||||
PointClass size() const;
|
||||
double radius() const;
|
||||
double area() const { return double(this->max(0) - this->min(0)) * (this->max(1) - this->min(1)); } // QDS
|
||||
void translate(coordf_t x, coordf_t y) { assert(this->defined); PointClass v(x, y); this->min += v; this->max += v; }
|
||||
void translate(const Vec2d& v0) { PointClass v(v0.x(), v0.y()); this->min += v; this->max += v; }
|
||||
void offset(coordf_t delta);
|
||||
BoundingBoxBase<PointClass> inflated(coordf_t delta) const throw() { BoundingBoxBase<PointClass> out(*this); out.offset(delta); return out; }
|
||||
PointClass center() const;
|
||||
bool contains(const PointClass &point) const {
|
||||
return point(0) >= this->min(0) && point(0) <= this->max(0)
|
||||
&& point(1) >= this->min(1) && point(1) <= this->max(1);
|
||||
}
|
||||
bool contains(const BoundingBoxBase<PointClass> &other) const {
|
||||
return contains(other.min) && contains(other.max);
|
||||
}
|
||||
bool overlap(const BoundingBoxBase<PointClass> &other) const {
|
||||
return ! (this->max(0) < other.min(0) || this->min(0) > other.max(0) ||
|
||||
this->max(1) < other.min(1) || this->min(1) > other.max(1));
|
||||
}
|
||||
PointClass operator[](size_t idx) const
|
||||
{
|
||||
switch (idx) {
|
||||
case 0: return min; break;
|
||||
case 1: return PointClass(max(0), min(1)); break;
|
||||
case 2: return max; break;
|
||||
case 3: return PointClass(min(0), max(1)); break;
|
||||
default: return PointClass(); break;
|
||||
}
|
||||
return PointClass();
|
||||
}
|
||||
bool operator==(const BoundingBoxBase<PointClass> &rhs) { return this->min == rhs.min && this->max == rhs.max; }
|
||||
bool operator!=(const BoundingBoxBase<PointClass> &rhs) { return ! (*this == rhs); }
|
||||
friend std::ostream &operator<<(std::ostream &os, const BoundingBoxBase &bbox)
|
||||
{
|
||||
os << "[" << bbox.max(0) - bbox.min(0) << " x " << bbox.max(1) - bbox.min(1) << "] from (" << bbox.min(0) << ", " << bbox.min(1) << ")";
|
||||
return os;
|
||||
}
|
||||
|
||||
private:
|
||||
// to access construct()
|
||||
friend BoundingBox get_extents<false>(const Points &pts);
|
||||
friend BoundingBox get_extents<true>(const Points &pts);
|
||||
|
||||
// if IncludeBoundary, then a bounding box is defined even for a single point.
|
||||
// otherwise a bounding box is only defined if it has a positive area.
|
||||
// The output bounding box is expected to be set to "undefined" initially.
|
||||
template<bool IncludeBoundary = false, class BoundingBoxType, class It, class = IteratorOnly<It>>
|
||||
static void construct(BoundingBoxType &out, It from, It to)
|
||||
{
|
||||
if (from != to) {
|
||||
auto it = from;
|
||||
out.min = it->template cast<typename PointClass::Scalar>();
|
||||
out.max = out.min;
|
||||
for (++ it; it != to; ++ it) {
|
||||
auto vec = it->template cast<typename PointClass::Scalar>();
|
||||
out.min = out.min.cwiseMin(vec);
|
||||
out.max = out.max.cwiseMax(vec);
|
||||
}
|
||||
out.defined = IncludeBoundary || (out.min.x() < out.max.x() && out.min.y() < out.max.y());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class PointClass>
|
||||
class BoundingBox3Base : public BoundingBoxBase<PointClass>
|
||||
{
|
||||
public:
|
||||
BoundingBox3Base() : BoundingBoxBase<PointClass>() {}
|
||||
BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) :
|
||||
BoundingBoxBase<PointClass>(pmin, pmax)
|
||||
{ if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; }
|
||||
BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
|
||||
BoundingBoxBase<PointClass>(p1, p1) { merge(p2); merge(p3); }
|
||||
|
||||
template<class It, class = IteratorOnly<It> > BoundingBox3Base(It from, It to)
|
||||
{
|
||||
if (from == to)
|
||||
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor");
|
||||
|
||||
auto it = from;
|
||||
this->min = it->template cast<typename PointClass::Scalar>();
|
||||
this->max = this->min;
|
||||
for (++ it; it != to; ++ it) {
|
||||
auto vec = it->template cast<typename PointClass::Scalar>();
|
||||
this->min = this->min.cwiseMin(vec);
|
||||
this->max = this->max.cwiseMax(vec);
|
||||
}
|
||||
this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2));
|
||||
}
|
||||
|
||||
BoundingBox3Base(const std::vector<PointClass> &points)
|
||||
: BoundingBox3Base(points.begin(), points.end())
|
||||
{}
|
||||
|
||||
Polygon polygon(bool is_scaled = false) const;//QDS: 2D footprint polygon
|
||||
void merge(const PointClass &point);
|
||||
void merge(const std::vector<PointClass> &points);
|
||||
void merge(const BoundingBox3Base<PointClass> &bb);
|
||||
PointClass size() const;
|
||||
double radius() const;
|
||||
void translate(coordf_t x, coordf_t y, coordf_t z) { assert(this->defined); PointClass v(x, y, z); this->min += v; this->max += v; }
|
||||
void translate(const Vec3d &v) { this->min += v; this->max += v; }
|
||||
void offset(coordf_t delta);
|
||||
BoundingBox3Base<PointClass> inflated(coordf_t delta) const throw() { BoundingBox3Base<PointClass> out(*this); out.offset(delta); return out; }
|
||||
PointClass center() const;
|
||||
coordf_t max_size() const;
|
||||
|
||||
bool contains(const PointClass &point) const {
|
||||
return BoundingBoxBase<PointClass>::contains(point) && point(2) >= this->min(2) && point(2) <= this->max(2);
|
||||
}
|
||||
|
||||
bool contains(const BoundingBox3Base<PointClass>& other) const {
|
||||
return contains(other.min) && contains(other.max);
|
||||
}
|
||||
|
||||
bool intersects(const BoundingBox3Base<PointClass>& other) const {
|
||||
return (this->min(0) < other.max(0)) && (this->max(0) > other.min(0)) && (this->min(1) < other.max(1)) && (this->max(1) > other.min(1)) && (this->min(2) < other.max(2)) && (this->max(2) > other.min(2));
|
||||
}
|
||||
};
|
||||
|
||||
// Will prevent warnings caused by non existing definition of template in hpp
|
||||
extern template void BoundingBoxBase<Point>::scale(double factor);
|
||||
extern template void BoundingBoxBase<Vec2d>::scale(double factor);
|
||||
extern template void BoundingBoxBase<Vec3d>::scale(double factor);
|
||||
extern template void BoundingBoxBase<Point>::offset(coordf_t delta);
|
||||
extern template void BoundingBoxBase<Vec2d>::offset(coordf_t delta);
|
||||
extern template void BoundingBoxBase<Point>::merge(const Point &point);
|
||||
extern template void BoundingBoxBase<Vec2f>::merge(const Vec2f &point);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
|
||||
extern template void BoundingBoxBase<Point>::merge(const Points &points);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const Pointfs &points);
|
||||
extern template void BoundingBoxBase<Point>::merge(const BoundingBoxBase<Point> &bb);
|
||||
extern template void BoundingBoxBase<Vec2f>::merge(const BoundingBoxBase<Vec2f> &bb);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
|
||||
extern template Point BoundingBoxBase<Point>::size() const;
|
||||
extern template Vec2f BoundingBoxBase<Vec2f>::size() const;
|
||||
extern template Vec2d BoundingBoxBase<Vec2d>::size() const;
|
||||
extern template double BoundingBoxBase<Point>::radius() const;
|
||||
extern template double BoundingBoxBase<Vec2d>::radius() const;
|
||||
extern template Point BoundingBoxBase<Point>::center() const;
|
||||
extern template Vec2f BoundingBoxBase<Vec2f>::center() const;
|
||||
extern template Vec2d BoundingBoxBase<Vec2d>::center() const;
|
||||
extern template void BoundingBox3Base<Vec3f>::merge(const Vec3f &point);
|
||||
extern template void BoundingBox3Base<Vec3d>::merge(const Vec3d &point);
|
||||
extern template void BoundingBox3Base<Vec3d>::merge(const Pointf3s &points);
|
||||
extern template void BoundingBox3Base<Vec3d>::merge(const BoundingBox3Base<Vec3d> &bb);
|
||||
extern template Vec3f BoundingBox3Base<Vec3f>::size() const;
|
||||
extern template Vec3d BoundingBox3Base<Vec3d>::size() const;
|
||||
extern template double BoundingBox3Base<Vec3d>::radius() const;
|
||||
extern template void BoundingBox3Base<Vec3d>::offset(coordf_t delta);
|
||||
extern template Vec3f BoundingBox3Base<Vec3f>::center() const;
|
||||
extern template Vec3d BoundingBox3Base<Vec3d>::center() const;
|
||||
extern template coordf_t BoundingBox3Base<Vec3f>::max_size() const;
|
||||
extern template coordf_t BoundingBox3Base<Vec3d>::max_size() const;
|
||||
|
||||
class BoundingBox : public BoundingBoxBase<Point>
|
||||
{
|
||||
public:
|
||||
void polygon(Polygon* polygon) const;
|
||||
Polygon polygon() const;
|
||||
BoundingBox rotated(double angle) const;
|
||||
BoundingBox rotated(double angle, const Point ¢er) const;
|
||||
void rotate(double angle) { (*this) = this->rotated(angle); }
|
||||
void rotate(double angle, const Point ¢er) { (*this) = this->rotated(angle, center); }
|
||||
// Align the min corner to a grid of cell_size x cell_size cells,
|
||||
// to encompass the original bounding box.
|
||||
void align_to_grid(const coord_t cell_size);
|
||||
|
||||
BoundingBox() : BoundingBoxBase<Point>() {}
|
||||
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point>(pmin, pmax) {}
|
||||
BoundingBox(const Points &points) : BoundingBoxBase<Point>(points) {}
|
||||
|
||||
BoundingBox inflated(coordf_t delta) const throw() { BoundingBox out(*this); out.offset(delta); return out; }
|
||||
|
||||
friend BoundingBox get_extents_rotated(const Points &points, double angle);
|
||||
};
|
||||
|
||||
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
|
||||
{
|
||||
public:
|
||||
BoundingBox3() : BoundingBox3Base<Vec3crd>() {}
|
||||
BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base<Vec3crd>(pmin, pmax) {}
|
||||
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>(points) {}
|
||||
};
|
||||
|
||||
class BoundingBoxf : public BoundingBoxBase<Vec2d>
|
||||
{
|
||||
public:
|
||||
BoundingBoxf() : BoundingBoxBase<Vec2d>() {}
|
||||
BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase<Vec2d>(pmin, pmax) {}
|
||||
BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {}
|
||||
};
|
||||
|
||||
class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
|
||||
{
|
||||
public:
|
||||
using BoundingBox3Base::BoundingBox3Base;
|
||||
|
||||
BoundingBoxf3 transformed(const Transform3d& matrix) const;
|
||||
};
|
||||
|
||||
template<typename VT>
|
||||
inline bool empty(const BoundingBoxBase<VT> &bb)
|
||||
{
|
||||
return ! bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1);
|
||||
}
|
||||
|
||||
template<typename VT>
|
||||
inline bool empty(const BoundingBox3Base<VT> &bb)
|
||||
{
|
||||
return ! bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1) || bb.min(2) >= bb.max(2);
|
||||
}
|
||||
|
||||
inline BoundingBox scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; }
|
||||
inline BoundingBox3 scaled(const BoundingBoxf3 &bb) { return {scaled(bb.min), scaled(bb.max)}; }
|
||||
inline BoundingBoxf unscaled(const BoundingBox &bb) { return {unscaled(bb.min), unscaled(bb.max)}; }
|
||||
inline BoundingBoxf3 unscaled(const BoundingBox3 &bb) { return {unscaled(bb.min), unscaled(bb.max)}; }
|
||||
|
||||
template<class Tout, class Tin>
|
||||
auto cast(const BoundingBoxBase<Tin> &b)
|
||||
{
|
||||
return BoundingBoxBase<Vec<3, Tout>>{b.min.template cast<Tout>(),
|
||||
b.max.template cast<Tout>()};
|
||||
}
|
||||
|
||||
template<class Tout, class Tin>
|
||||
auto cast(const BoundingBox3Base<Tin> &b)
|
||||
{
|
||||
return BoundingBox3Base<Vec<3, Tout>>{b.min.template cast<Tout>(),
|
||||
b.max.template cast<Tout>()};
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
// Serialization through the Cereal library
|
||||
namespace cereal {
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBox &bb) { archive(bb.min, bb.max, bb.defined); }
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBox3 &bb) { archive(bb.min, bb.max, bb.defined); }
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBoxf &bb) { archive(bb.min, bb.max, bb.defined); }
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBoxf3 &bb) { archive(bb.min, bb.max, bb.defined); }
|
||||
}
|
||||
|
||||
#endif
|
||||
387
src/libslic3r/BridgeDetector.cpp
Normal file
387
src/libslic3r/BridgeDetector.cpp
Normal file
@@ -0,0 +1,387 @@
|
||||
#include "BridgeDetector.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
BridgeDetector::BridgeDetector(
|
||||
ExPolygon _expolygon,
|
||||
const ExPolygons &_lower_slices,
|
||||
coord_t _spacing) :
|
||||
// The original infill polygon, not inflated.
|
||||
expolygons(expolygons_owned),
|
||||
// All surfaces of the object supporting this region.
|
||||
lower_slices(_lower_slices),
|
||||
spacing(_spacing)
|
||||
{
|
||||
this->expolygons_owned.push_back(std::move(_expolygon));
|
||||
initialize();
|
||||
}
|
||||
|
||||
BridgeDetector::BridgeDetector(
|
||||
const ExPolygons &_expolygons,
|
||||
const ExPolygons &_lower_slices,
|
||||
coord_t _spacing) :
|
||||
// The original infill polygon, not inflated.
|
||||
expolygons(_expolygons),
|
||||
// All surfaces of the object supporting this region.
|
||||
lower_slices(_lower_slices),
|
||||
spacing(_spacing)
|
||||
{
|
||||
initialize();
|
||||
}
|
||||
|
||||
void BridgeDetector::initialize()
|
||||
{
|
||||
// 5 degrees stepping
|
||||
this->resolution = PI/36.0;
|
||||
// output angle not known
|
||||
this->angle = -1.;
|
||||
|
||||
// Outset our bridge by an arbitrary amout; we'll use this outer margin for detecting anchors.
|
||||
Polygons grown = offset(this->expolygons, float(this->spacing));
|
||||
|
||||
// Detect possible anchoring edges of this bridging region.
|
||||
// Detect what edges lie on lower slices by turning bridge contour and holes
|
||||
// into polylines and then clipping them with each lower slice's contour.
|
||||
// Currently _edges are only used to set a candidate direction of the bridge (see bridge_direction_candidates()).
|
||||
Polygons contours;
|
||||
contours.reserve(this->lower_slices.size());
|
||||
for (const ExPolygon &expoly : this->lower_slices)
|
||||
contours.push_back(expoly.contour);
|
||||
this->_edges = intersection_pl(to_polylines(grown), contours);
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
printf(" bridge has %zu support(s)\n", this->_edges.size());
|
||||
#endif
|
||||
|
||||
// detect anchors as intersection between our bridge expolygon and the lower slices
|
||||
// safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some edges
|
||||
this->_anchor_regions = intersection_ex(grown, union_safety_offset(this->lower_slices));
|
||||
|
||||
/*
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output("bridge.svg",
|
||||
expolygons => [ $self->expolygon ],
|
||||
red_expolygons => $self->lower_slices,
|
||||
polylines => $self->_edges,
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
bool BridgeDetector::detect_angle(double bridge_direction_override)
|
||||
{
|
||||
if (this->_edges.empty() || this->_anchor_regions.empty())
|
||||
// The bridging region is completely in the air, there are no anchors available at the layer below.
|
||||
return false;
|
||||
|
||||
std::vector<BridgeDirection> candidates;
|
||||
if (bridge_direction_override == 0.) {
|
||||
std::vector<double> angles = bridge_direction_candidates();
|
||||
candidates.reserve(angles.size());
|
||||
for (size_t i = 0; i < angles.size(); ++ i)
|
||||
candidates.emplace_back(BridgeDirection(angles[i]));
|
||||
} else
|
||||
candidates.emplace_back(BridgeDirection(bridge_direction_override));
|
||||
|
||||
/* Outset the bridge expolygon by half the amount we used for detecting anchors;
|
||||
we'll use this one to clip our test lines and be sure that their endpoints
|
||||
are inside the anchors and not on their contours leading to false negatives. */
|
||||
Polygons clip_area = offset(this->expolygons, 0.5f * float(this->spacing));
|
||||
|
||||
/* we'll now try several directions using a rudimentary visibility check:
|
||||
bridge in several directions and then sum the length of lines having both
|
||||
endpoints within anchors */
|
||||
|
||||
bool have_coverage = false;
|
||||
for (size_t i_angle = 0; i_angle < candidates.size(); ++ i_angle)
|
||||
{
|
||||
const double angle = candidates[i_angle].angle;
|
||||
|
||||
Lines lines;
|
||||
{
|
||||
// Get an oriented bounding box around _anchor_regions.
|
||||
BoundingBox bbox = get_extents_rotated(this->_anchor_regions, - angle);
|
||||
// Cover the region with line segments.
|
||||
lines.reserve((bbox.max(1) - bbox.min(1) + this->spacing) / this->spacing);
|
||||
double s = sin(angle);
|
||||
double c = cos(angle);
|
||||
//FIXME Vojtech: The lines shall be spaced half the line width from the edge, but then
|
||||
// some of the test cases fail. Need to adjust the test cases then?
|
||||
// for (coord_t y = bbox.min(1) + this->spacing / 2; y <= bbox.max(1); y += this->spacing)
|
||||
for (coord_t y = bbox.min(1); y <= bbox.max(1); y += this->spacing)
|
||||
lines.push_back(Line(
|
||||
Point((coord_t)round(c * bbox.min(0) - s * y), (coord_t)round(c * y + s * bbox.min(0))),
|
||||
Point((coord_t)round(c * bbox.max(0) - s * y), (coord_t)round(c * y + s * bbox.max(0)))));
|
||||
}
|
||||
|
||||
double total_length = 0;
|
||||
double max_length = 0;
|
||||
{
|
||||
Lines clipped_lines = intersection_ln(lines, clip_area);
|
||||
size_t archored_line_num = 0;
|
||||
for (size_t i = 0; i < clipped_lines.size(); ++i) {
|
||||
const Line &line = clipped_lines[i];
|
||||
if (expolygons_contain(this->_anchor_regions, line.a) && expolygons_contain(this->_anchor_regions, line.b)) {
|
||||
// This line could be anchored.
|
||||
double len = line.length();
|
||||
total_length += len;
|
||||
max_length = std::max(max_length, len);
|
||||
archored_line_num++;
|
||||
}
|
||||
}
|
||||
if (clipped_lines.size() > 0 && archored_line_num > 0) {
|
||||
candidates[i_angle].archored_percent = (double)archored_line_num / (double)clipped_lines.size();
|
||||
}
|
||||
}
|
||||
if (total_length == 0.)
|
||||
continue;
|
||||
|
||||
have_coverage = true;
|
||||
// Sum length of bridged lines.
|
||||
candidates[i_angle].coverage = total_length;
|
||||
/* The following produces more correct results in some cases and more broken in others.
|
||||
TODO: investigate, as it looks more reliable than line clipping. */
|
||||
// $directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0;
|
||||
// max length of bridged lines
|
||||
candidates[i_angle].max_length = max_length;
|
||||
}
|
||||
|
||||
// if no direction produced coverage, then there's no bridge direction
|
||||
if (! have_coverage)
|
||||
return false;
|
||||
|
||||
// sort directions by coverage - most coverage first
|
||||
std::sort(candidates.begin(), candidates.end());
|
||||
|
||||
// if any other direction is within extrusion width of coverage, prefer it if shorter
|
||||
// TODO: There are two options here - within width of the angle with most coverage, or within width of the currently perferred?
|
||||
size_t i_best = 0;
|
||||
// for (size_t i = 1; i < candidates.size() && abs(candidates[i_best].archored_percent - candidates[i].archored_percent) < EPSILON; ++ i)
|
||||
for (size_t i = 1; i < candidates.size() && candidates[i_best].coverage - candidates[i].coverage < this->spacing; ++ i)
|
||||
if (candidates[i].max_length < candidates[i_best].max_length)
|
||||
i_best = i;
|
||||
|
||||
this->angle = candidates[i_best].angle;
|
||||
if (this->angle >= PI)
|
||||
this->angle -= PI;
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
printf(" Optimal infill angle is %d degrees\n", (int)Slic3r::Geometry::rad2deg(this->angle));
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<double> BridgeDetector::bridge_direction_candidates() const
|
||||
{
|
||||
// we test angles according to configured resolution
|
||||
std::vector<double> angles;
|
||||
for (int i = 0; i <= PI/this->resolution; ++i)
|
||||
angles.push_back(i * this->resolution);
|
||||
|
||||
// we also test angles of each bridge contour
|
||||
{
|
||||
Lines lines = to_lines(this->expolygons);
|
||||
for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line)
|
||||
angles.push_back(line->direction());
|
||||
}
|
||||
|
||||
/* we also test angles of each open supporting edge
|
||||
(this finds the optimal angle for C-shaped supports) */
|
||||
for (const Polyline &edge : this->_edges)
|
||||
if (edge.first_point() != edge.last_point())
|
||||
angles.push_back(Line(edge.first_point(), edge.last_point()).direction());
|
||||
|
||||
// remove duplicates
|
||||
double min_resolution = PI/180.0; // 1 degree
|
||||
std::sort(angles.begin(), angles.end());
|
||||
for (size_t i = 1; i < angles.size(); ++i) {
|
||||
if (Slic3r::Geometry::directions_parallel(angles[i], angles[i-1], min_resolution)) {
|
||||
angles.erase(angles.begin() + i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
/* compare first value with last one and remove the greatest one (PI)
|
||||
in case they are parallel (PI, 0) */
|
||||
if (Slic3r::Geometry::directions_parallel(angles.front(), angles.back(), min_resolution))
|
||||
angles.pop_back();
|
||||
|
||||
return angles;
|
||||
}
|
||||
|
||||
/*
|
||||
static void get_trapezoids(const ExPolygon &expoly, Polygons* polygons) const
|
||||
{
|
||||
ExPolygons expp;
|
||||
expp.push_back(expoly);
|
||||
boost::polygon::get_trapezoids(*polygons, expp);
|
||||
}
|
||||
|
||||
void ExPolygon::get_trapezoids(ExPolygon clone, Polygons* polygons, double angle) const
|
||||
{
|
||||
clone.rotate(PI/2 - angle, Point(0,0));
|
||||
clone.get_trapezoids(polygons);
|
||||
for (Polygons::iterator polygon = polygons->begin(); polygon != polygons->end(); ++polygon)
|
||||
polygon->rotate(-(PI/2 - angle), Point(0,0));
|
||||
}
|
||||
*/
|
||||
|
||||
// This algorithm may return more trapezoids than necessary
|
||||
// (i.e. it may break a single trapezoid in several because
|
||||
// other parts of the object have x coordinates in the middle)
|
||||
static void get_trapezoids2(const ExPolygon& expoly, Polygons* polygons)
|
||||
{
|
||||
Polygons src_polygons = to_polygons(expoly);
|
||||
// get all points of this ExPolygon
|
||||
const Points pp = to_points(src_polygons);
|
||||
|
||||
// build our bounding box
|
||||
BoundingBox bb(pp);
|
||||
|
||||
// get all x coordinates
|
||||
std::vector<coord_t> xx;
|
||||
xx.reserve(pp.size());
|
||||
for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p)
|
||||
xx.push_back(p->x());
|
||||
std::sort(xx.begin(), xx.end());
|
||||
|
||||
// find trapezoids by looping from first to next-to-last coordinate
|
||||
Polygons rectangle;
|
||||
rectangle.emplace_back(Polygon());
|
||||
for (std::vector<coord_t>::const_iterator x = xx.begin(); x != xx.end()-1; ++x) {
|
||||
coord_t next_x = *(x + 1);
|
||||
if (*x != next_x) {
|
||||
// intersect with rectangle
|
||||
// append results to return value
|
||||
rectangle.front() = { { *x, bb.min.y() }, { next_x, bb.min.y() }, { next_x, bb.max.y() }, { *x, bb.max.y() } };
|
||||
polygons_append(*polygons, intersection(rectangle, src_polygons));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void get_trapezoids2(const ExPolygon &expoly, Polygons* polygons, double angle)
|
||||
{
|
||||
ExPolygon clone = expoly;
|
||||
clone.rotate(PI/2 - angle, Point(0,0));
|
||||
get_trapezoids2(clone, polygons);
|
||||
for (Polygon &polygon : *polygons)
|
||||
polygon.rotate(-(PI/2 - angle), Point(0,0));
|
||||
}
|
||||
|
||||
// Coverage is currently only used by the unit tests. It is extremely slow and unreliable!
|
||||
Polygons BridgeDetector::coverage(double angle) const
|
||||
{
|
||||
if (angle == -1)
|
||||
angle = this->angle;
|
||||
|
||||
Polygons covered;
|
||||
|
||||
if (angle != -1) {
|
||||
// Get anchors, convert them to Polygons and rotate them.
|
||||
Polygons anchors = to_polygons(this->_anchor_regions);
|
||||
polygons_rotate(anchors, PI/2.0 - angle);
|
||||
|
||||
for (ExPolygon expolygon : this->expolygons) {
|
||||
// Clone our expolygon and rotate it so that we work with vertical lines.
|
||||
expolygon.rotate(PI/2.0 - angle);
|
||||
// Outset the bridge expolygon by half the amount we used for detecting anchors;
|
||||
// we'll use this one to generate our trapezoids and be sure that their vertices
|
||||
// are inside the anchors and not on their contours leading to false negatives.
|
||||
for (ExPolygon &expoly : offset_ex(expolygon, 0.5f * float(this->spacing))) {
|
||||
// Compute trapezoids according to a vertical orientation
|
||||
Polygons trapezoids;
|
||||
get_trapezoids2(expoly, &trapezoids, PI/2.0);
|
||||
for (const Polygon &trapezoid : trapezoids) {
|
||||
// not nice, we need a more robust non-numeric check
|
||||
size_t n_supported = 0;
|
||||
for (const Line &supported_line : intersection_ln(trapezoid.lines(), anchors))
|
||||
if (supported_line.length() >= this->spacing)
|
||||
++ n_supported;
|
||||
if (n_supported >= 2)
|
||||
covered.push_back(std::move(trapezoid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids
|
||||
// instead of exact overlaps.
|
||||
covered = union_(covered);
|
||||
// Intersect trapezoids with actual bridge area to remove extra margins and append it to result.
|
||||
polygons_rotate(covered, -(PI/2.0 - angle));
|
||||
covered = intersection(this->expolygons, covered);
|
||||
#if 0
|
||||
{
|
||||
my @lines = map @{$_->lines}, @$trapezoids;
|
||||
$_->rotate(-(PI/2 - $angle), [0,0]) for @lines;
|
||||
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"coverage_" . rad2deg($angle) . ".svg",
|
||||
expolygons => [$self->expolygon],
|
||||
green_expolygons => $self->_anchor_regions,
|
||||
red_expolygons => $coverage,
|
||||
lines => \@lines,
|
||||
);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return covered;
|
||||
}
|
||||
|
||||
/* This method returns the bridge edges (as polylines) that are not supported
|
||||
but would allow the entire bridge area to be bridged with detected angle
|
||||
if supported too */
|
||||
void
|
||||
BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const
|
||||
{
|
||||
if (angle == -1) angle = this->angle;
|
||||
if (angle == -1) return;
|
||||
|
||||
Polygons grown_lower = offset(this->lower_slices, float(this->spacing));
|
||||
|
||||
for (ExPolygons::const_iterator it_expoly = this->expolygons.begin(); it_expoly != this->expolygons.end(); ++ it_expoly) {
|
||||
// get unsupported bridge edges (both contour and holes)
|
||||
Lines unsupported_lines = to_lines(diff_pl(to_polylines(*it_expoly), grown_lower));
|
||||
/* Split into individual segments and filter out edges parallel to the bridging angle
|
||||
TODO: angle tolerance should probably be based on segment length and flow width,
|
||||
so that we build supports whenever there's a chance that at least one or two bridge
|
||||
extrusions would be anchored within such length (i.e. a slightly non-parallel bridging
|
||||
direction might still benefit from anchors if long enough)
|
||||
double angle_tolerance = PI / 180.0 * 5.0; */
|
||||
for (const Line &line : unsupported_lines)
|
||||
if (! Slic3r::Geometry::directions_parallel(line.direction(), angle)) {
|
||||
unsupported->emplace_back(Polyline());
|
||||
unsupported->back().points.emplace_back(line.a);
|
||||
unsupported->back().points.emplace_back(line.b);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"unsupported_" . rad2deg($angle) . ".svg",
|
||||
expolygons => [$self->expolygon],
|
||||
green_expolygons => $self->_anchor_regions,
|
||||
red_expolygons => union_ex($grown_lower),
|
||||
no_arrows => 1,
|
||||
polylines => \@bridge_edges,
|
||||
red_polylines => $unsupported,
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
Polylines
|
||||
BridgeDetector::unsupported_edges(double angle) const
|
||||
{
|
||||
Polylines pp;
|
||||
this->unsupported_edges(angle, &pp);
|
||||
return pp;
|
||||
}
|
||||
|
||||
}
|
||||
175
src/libslic3r/BridgeDetector.hpp
Normal file
175
src/libslic3r/BridgeDetector.hpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#ifndef slic3r_BridgeDetector_hpp_
|
||||
#define slic3r_BridgeDetector_hpp_
|
||||
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include "PrincipalComponents2D.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "ExPolygon.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// The bridge detector optimizes a direction of bridges over a region or a set of regions.
|
||||
// A bridge direction is considered optimal, if the length of the lines strang over the region is maximal.
|
||||
// This is optimal if the bridge is supported in a single direction only, but
|
||||
// it may not likely be optimal, if the bridge region is supported from all sides. Then an optimal
|
||||
// solution would find a direction with shortest bridges.
|
||||
// The bridge orientation is measured CCW from the X axis.
|
||||
class BridgeDetector {
|
||||
public:
|
||||
// The non-grown holes.
|
||||
const ExPolygons &expolygons;
|
||||
// In case the caller gaves us the input polygons by a value, make a copy.
|
||||
ExPolygons expolygons_owned;
|
||||
// Lower slices, all regions.
|
||||
const ExPolygons &lower_slices;
|
||||
// Scaled extrusion width of the infill.
|
||||
coord_t spacing;
|
||||
// Angle resolution for the brute force search of the best bridging angle.
|
||||
double resolution;
|
||||
// The final optimal angle.
|
||||
double angle;
|
||||
|
||||
BridgeDetector(ExPolygon _expolygon, const ExPolygons &_lower_slices, coord_t _extrusion_width);
|
||||
BridgeDetector(const ExPolygons &_expolygons, const ExPolygons &_lower_slices, coord_t _extrusion_width);
|
||||
// If bridge_direction_override != 0, then the angle is used instead of auto-detect.
|
||||
bool detect_angle(double bridge_direction_override = 0.);
|
||||
// Coverage is currently only used by the unit tests. It is extremely slow and unreliable!
|
||||
Polygons coverage(double angle = -1) const;
|
||||
void unsupported_edges(double angle, Polylines* unsupported) const;
|
||||
Polylines unsupported_edges(double angle = -1) const;
|
||||
|
||||
private:
|
||||
// Suppress warning "assignment operator could not be generated"
|
||||
BridgeDetector& operator=(const BridgeDetector &);
|
||||
|
||||
void initialize();
|
||||
|
||||
struct BridgeDirection {
|
||||
BridgeDirection(double a = -1.) : angle(a), coverage(0.), max_length(0.), archored_percent(0.){}
|
||||
// the best direction is the one causing most lines to be bridged (thus most coverage)
|
||||
bool operator<(const BridgeDirection &other) const {
|
||||
// Initial sort by coverage only - comparator must obey strict weak ordering
|
||||
return this->coverage > other.coverage;//this->archored_percent > other.archored_percent;
|
||||
};
|
||||
double angle;
|
||||
double coverage;
|
||||
double max_length;
|
||||
double archored_percent;
|
||||
};
|
||||
|
||||
// Get possible briging direction candidates.
|
||||
std::vector<double> bridge_direction_candidates() const;
|
||||
|
||||
// Open lines representing the supporting edges.
|
||||
Polylines _edges;
|
||||
// Closed polygons representing the supporting areas.
|
||||
ExPolygons _anchor_regions;
|
||||
};
|
||||
|
||||
//return ideal bridge direction and unsupported bridge endpoints distance.
|
||||
inline std::tuple<Vec2d, double> detect_bridging_direction(const Lines &floating_edges, const Polygons &overhang_area)
|
||||
{
|
||||
if (floating_edges.empty()) {
|
||||
// consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges
|
||||
auto [pc1, pc2] = compute_principal_components(overhang_area);
|
||||
if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
|
||||
return {Vec2d{1.0,0.0}, 0.0};
|
||||
} else {
|
||||
return {pc2.normalized().cast<double>(), 0.0};
|
||||
}
|
||||
}
|
||||
|
||||
// Overhang is not fully surrounded by anchors, in that case, find such direction that will minimize the number of bridge ends/180turns in the air
|
||||
std::unordered_map<double, Vec2d> directions{};
|
||||
for (const Line &l : floating_edges) {
|
||||
Vec2d normal = l.normal().cast<double>().normalized();
|
||||
double quantized_angle = std::ceil(std::atan2(normal.y(),normal.x()) * 1000.0);
|
||||
directions.emplace(quantized_angle, normal);
|
||||
}
|
||||
std::vector<std::pair<Vec2d, double>> direction_costs{};
|
||||
// it is acutally cost of a perpendicular bridge direction - we find the minimal cost and then return the perpendicular dir
|
||||
for (const auto& d : directions) {
|
||||
direction_costs.emplace_back(d.second, 0.0);
|
||||
}
|
||||
|
||||
for (const Line &l : floating_edges) {
|
||||
Vec2d line = (l.b - l.a).cast<double>();
|
||||
for (auto &dir_cost : direction_costs) {
|
||||
// the dot product already contains the length of the line. dir_cost.first is normalized.
|
||||
dir_cost.second += std::abs(line.dot(dir_cost.first));
|
||||
}
|
||||
}
|
||||
|
||||
Vec2d result_dir = Vec2d::Ones();
|
||||
double min_cost = std::numeric_limits<double>::max();
|
||||
for (const auto &cost : direction_costs) {
|
||||
if (cost.second < min_cost) {
|
||||
// now flip the orientation back and return the direction of the bridge extrusions
|
||||
result_dir = Vec2d{cost.first.y(), -cost.first.x()};
|
||||
min_cost = cost.second;
|
||||
}
|
||||
}
|
||||
|
||||
return {result_dir, min_cost};
|
||||
};
|
||||
|
||||
//return ideal bridge direction and unsupported bridge endpoints distance.
|
||||
inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area)
|
||||
{
|
||||
Polygons overhang_area = diff(to_cover, anchors_area);
|
||||
Polylines floating_polylines = diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON)));
|
||||
|
||||
if (floating_polylines.empty()) {
|
||||
// consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges
|
||||
auto [pc1, pc2] = compute_principal_components(overhang_area);
|
||||
if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
|
||||
return {Vec2d{1.0,0.0}, 0.0};
|
||||
} else {
|
||||
return {pc2.normalized().cast<double>(), 0.0};
|
||||
}
|
||||
}
|
||||
|
||||
// Overhang is not fully surrounded by anchors, in that case, find such direction that will minimize the number of bridge ends/180turns in the air
|
||||
Lines floating_edges = to_lines(floating_polylines);
|
||||
std::unordered_map<double, Vec2d> directions{};
|
||||
for (const Line &l : floating_edges) {
|
||||
Vec2d normal = l.normal().cast<double>().normalized();
|
||||
double quantized_angle = std::ceil(std::atan2(normal.y(),normal.x()) * 1000.0);
|
||||
directions.emplace(quantized_angle, normal);
|
||||
}
|
||||
std::vector<std::pair<Vec2d, double>> direction_costs{};
|
||||
// it is acutally cost of a perpendicular bridge direction - we find the minimal cost and then return the perpendicular dir
|
||||
for (const auto& d : directions) {
|
||||
direction_costs.emplace_back(d.second, 0.0);
|
||||
}
|
||||
|
||||
for (const Line &l : floating_edges) {
|
||||
Vec2d line = (l.b - l.a).cast<double>();
|
||||
for (auto &dir_cost : direction_costs) {
|
||||
// the dot product already contains the length of the line. dir_cost.first is normalized.
|
||||
dir_cost.second += std::abs(line.dot(dir_cost.first));
|
||||
}
|
||||
}
|
||||
|
||||
Vec2d result_dir = Vec2d::Ones();
|
||||
double min_cost = std::numeric_limits<double>::max();
|
||||
for (const auto &cost : direction_costs) {
|
||||
if (cost.second < min_cost) {
|
||||
// now flip the orientation back and return the direction of the bridge extrusions
|
||||
result_dir = Vec2d{cost.first.y(), -cost.first.x()};
|
||||
min_cost = cost.second;
|
||||
}
|
||||
}
|
||||
|
||||
return {result_dir, min_cost};
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1871
src/libslic3r/Brim.cpp
Normal file
1871
src/libslic3r/Brim.cpp
Normal file
File diff suppressed because it is too large
Load Diff
30
src/libslic3r/Brim.hpp
Normal file
30
src/libslic3r/Brim.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef slic3r_Brim_hpp_
|
||||
#define slic3r_Brim_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
|
||||
#include<map>
|
||||
#include<vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Print;
|
||||
class ExtrusionEntityCollection;
|
||||
class PrintTryCancel;
|
||||
class ObjectID;
|
||||
|
||||
// Produce brim lines around those objects, that have the brim enabled.
|
||||
// Collect islands_area to be merged into the final 1st layer convex hull.
|
||||
ExtrusionEntityCollection make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_area);
|
||||
void make_brim(const Print& print, PrintTryCancel try_cancel,
|
||||
Polygons& islands_area, std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
|
||||
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
|
||||
std::vector<unsigned int>& printExtruders);
|
||||
|
||||
// QDS: automatically make brim
|
||||
ExtrusionEntityCollection make_brim_auto(const Print &print, PrintTryCancel try_cancel, Polygons &islands_area);
|
||||
|
||||
} // Slic3r
|
||||
|
||||
#endif // slic3r_Brim_hpp_
|
||||
434
src/libslic3r/BuildVolume.cpp
Normal file
434
src/libslic3r/BuildVolume.cpp
Normal file
@@ -0,0 +1,434 @@
|
||||
#include "BuildVolume.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "Geometry/ConvexHull.hpp"
|
||||
#include "GCode/GCodeProcessor.hpp"
|
||||
#include "Point.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
BuildVolume::BuildVolume(const std::vector<Vec2d> &printable_area, const double printable_height) : m_bed_shape(printable_area), m_max_print_height(printable_height)
|
||||
{
|
||||
assert(printable_height >= 0);
|
||||
|
||||
m_polygon = Polygon::new_scale(printable_area);
|
||||
|
||||
// Calcuate various metrics of the input polygon.
|
||||
m_convex_hull = Geometry::convex_hull(m_polygon.points);
|
||||
m_bbox = get_extents(m_convex_hull);
|
||||
m_area = m_polygon.area();
|
||||
|
||||
BoundingBoxf bboxf = get_extents(printable_area);
|
||||
m_bboxf = BoundingBoxf3{ to_3d(bboxf.min, 0.), to_3d(bboxf.max, printable_height) };
|
||||
|
||||
if (printable_area.size() >= 4 && std::abs((m_area - double(m_bbox.size().x()) * double(m_bbox.size().y()))) < sqr(SCALED_EPSILON)) {
|
||||
// Square print bed, use the bounding box for collision detection.
|
||||
m_type = Type::Rectangle;
|
||||
m_circle.center = 0.5 * (m_bbox.min.cast<double>() + m_bbox.max.cast<double>());
|
||||
m_circle.radius = 0.5 * m_bbox.size().cast<double>().norm();
|
||||
} else if (printable_area.size() > 3) {
|
||||
// Circle was discretized, formatted into text with limited accuracy, thus the circle was deformed.
|
||||
// RANSAC is slightly more accurate than the iterative Taubin / Newton method with such an input.
|
||||
// m_circle = Geometry::circle_taubin_newton(printable_area);
|
||||
m_circle = Geometry::circle_ransac(printable_area);
|
||||
bool is_circle = true;
|
||||
#ifndef NDEBUG
|
||||
// Measuring maximum absolute error of interpolating an input polygon with circle.
|
||||
double max_error = 0;
|
||||
#endif // NDEBUG
|
||||
Vec2d prev = printable_area.back();
|
||||
for (const Vec2d &p : printable_area) {
|
||||
#ifndef NDEBUG
|
||||
max_error = std::max(max_error, std::abs((p - m_circle.center).norm() - m_circle.radius));
|
||||
#endif // NDEBUG
|
||||
if (// Polygon vertices must lie very close the circle.
|
||||
std::abs((p - m_circle.center).norm() - m_circle.radius) > 0.005 ||
|
||||
// Midpoints of polygon edges must not undercat more than 3mm. This corresponds to 72 edges per circle generated by BedShapePanel::update_shape().
|
||||
m_circle.radius - (0.5 * (prev + p) - m_circle.center).norm() > 3.) {
|
||||
is_circle = false;
|
||||
break;
|
||||
}
|
||||
prev = p;
|
||||
}
|
||||
if (is_circle) {
|
||||
m_type = Type::Circle;
|
||||
m_circle.center = scaled<double>(m_circle.center);
|
||||
m_circle.radius = scaled<double>(m_circle.radius);
|
||||
}
|
||||
}
|
||||
|
||||
if (printable_area.size() >= 3 && m_type == Type::Invalid) {
|
||||
// Circle check is not used for Convex / Custom shapes, fill it with something reasonable.
|
||||
m_circle = Geometry::smallest_enclosing_circle_welzl(m_convex_hull.points);
|
||||
m_type = (m_convex_hull.area() - m_area) < sqr(SCALED_EPSILON) ? Type::Convex : Type::Custom;
|
||||
// Initialize the top / bottom decomposition for inside convex polygon check. Do it with two different epsilons applied.
|
||||
auto convex_decomposition = [](const Polygon &in, double epsilon) {
|
||||
Polygon src = expand(in, float(epsilon)).front();
|
||||
std::vector<Vec2d> pts;
|
||||
pts.reserve(src.size());
|
||||
for (const Point &pt : src.points)
|
||||
pts.emplace_back(unscaled<double>(pt.cast<double>().eval()));
|
||||
return Geometry::decompose_convex_polygon_top_bottom(pts);
|
||||
};
|
||||
m_top_bottom_convex_hull_decomposition_scene = convex_decomposition(m_convex_hull, SceneEpsilon);
|
||||
m_top_bottom_convex_hull_decomposition_bed = convex_decomposition(m_convex_hull, BedEpsilon);
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "BuildVolume printable_area clasified as: " << this->type_name();
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Tests intersections of projected triangles, not just their vertices against a bounding box.
|
||||
// This test also correctly evaluates collision of a non-convex object with the bounding box.
|
||||
// Not used, slower than simple bounding box collision check and nobody complained about the inaccuracy of the simple test.
|
||||
static inline BuildVolume::ObjectState rectangle_test(const indexed_triangle_set &its, const Transform3f &trafo, const Vec2f min, const Vec2f max, const float max_z)
|
||||
{
|
||||
bool inside = false;
|
||||
bool outside = false;
|
||||
|
||||
auto sign = [](const Vec3f& pt) -> char { return pt.z() > 0 ? 1 : pt.z() < 0 ? -1 : 0; };
|
||||
|
||||
// Returns true if both inside and outside are set, thus early exit.
|
||||
auto test_intersection = [&inside, &outside, min, max, max_z](const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) -> bool {
|
||||
// First test whether the triangle is completely inside or outside the bounding box.
|
||||
Vec3f pmin = p1.cwiseMin(p2).cwiseMin(p3);
|
||||
Vec3f pmax = p1.cwiseMax(p2).cwiseMax(p3);
|
||||
bool tri_inside = false;
|
||||
bool tri_outside = false;
|
||||
if (pmax.x() < min.x() || pmin.x() > max.x() || pmax.y() < min.y() || pmin.y() > max.y()) {
|
||||
// Separated by one of the rectangle sides.
|
||||
tri_outside = true;
|
||||
} else if (pmin.x() >= min.x() && pmax.x() <= max.x() && pmin.y() >= min.y() && pmax.y() <= max.y()) {
|
||||
// Fully inside the rectangle.
|
||||
tri_inside = true;
|
||||
} else {
|
||||
// Bounding boxes overlap. Test triangle sides against the bbox corners.
|
||||
Vec2f v1(- p2.y() + p1.y(), p2.x() - p1.x());
|
||||
Vec2f v2(- p2.y() + p2.y(), p3.x() - p2.x());
|
||||
Vec2f v3(- p1.y() + p3.y(), p1.x() - p3.x());
|
||||
bool ccw = cross2(v1, v2) > 0;
|
||||
for (const Vec2f &p : { Vec2f{ min.x(), min.y() }, Vec2f{ min.x(), max.y() }, Vec2f{ max.x(), min.y() }, Vec2f{ max.x(), max.y() } }) {
|
||||
auto dot = v1.dot(p);
|
||||
if (ccw ? dot >= 0 : dot <= 0)
|
||||
tri_inside = true;
|
||||
else
|
||||
tri_outside = true;
|
||||
}
|
||||
}
|
||||
inside |= tri_inside;
|
||||
outside |= tri_outside;
|
||||
return inside && outside;
|
||||
};
|
||||
|
||||
// Edge crosses the z plane. Calculate intersection point with the plane.
|
||||
auto clip_edge = [](const Vec3f &p1, const Vec3f &p2) -> Vec3f {
|
||||
const float t = (world_min_z - p1.z()) / (p2.z() - p1.z());
|
||||
return { p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z };
|
||||
};
|
||||
|
||||
// Clip at (p1, p2), p3 must be on the clipping plane.
|
||||
// Returns true if both inside and outside are set, thus early exit.
|
||||
auto clip_and_test1 = [&test_intersection, &clip_edge](const Vec3f &p1, const Vec3f &p2, const Vec3f &p3, bool p1above) -> bool {
|
||||
Vec3f pa = clip_edge(p1, p2);
|
||||
return p1above ? test_intersection(p1, pa, p3) : test_intersection(pa, p2, p3);
|
||||
};
|
||||
|
||||
// Clip at (p1, p2) and (p2, p3).
|
||||
// Returns true if both inside and outside are set, thus early exit.
|
||||
auto clip_and_test2 = [&test_intersection, &clip_edge](const Vec3f &p1, const Vec3f &p2, const Vec3f &p3, bool p2above) -> bool {
|
||||
Vec3f pa = clip_edge(p1, p2);
|
||||
Vec3f pb = clip_edge(p2, p3);
|
||||
return p2above ? test_intersection(pa, p2, pb) : test_intersection(p1, pa, p3) || test_intersection(p3, pa, pb);
|
||||
};
|
||||
|
||||
for (const stl_triangle_vertex_indices &tri : its.indices) {
|
||||
const Vec3f pts[3] = { trafo * its.vertices[tri(0)], trafo * its.vertices[tri(1)], trafo * its.vertices[tri(2)] };
|
||||
char signs[3] = { sign(pts[0]), sign(pts[1]), sign(pts[2]) };
|
||||
bool clips[3] = { signs[0] * signs[1] == -1, signs[1] * signs[2] == -1, signs[2] * signs[0] == -1 };
|
||||
if (clips[0]) {
|
||||
if (clips[1]) {
|
||||
// Clipping at (pt0, pt1) and (pt1, pt2).
|
||||
if (clip_and_test2(pts[0], pts[1], pts[2], signs[1] > 0))
|
||||
break;
|
||||
} else if (clips[2]) {
|
||||
// Clipping at (pt0, pt1) and (pt0, pt2).
|
||||
if (clip_and_test2(pts[2], pts[0], pts[1], signs[0] > 0))
|
||||
break;
|
||||
} else {
|
||||
// Clipping at (pt0, pt1), pt2 must be on the clipping plane.
|
||||
if (clip_and_test1(pts[0], pts[1], pts[2], signs[0] > 0))
|
||||
break;
|
||||
}
|
||||
} else if (clips[1]) {
|
||||
if (clips[2]) {
|
||||
// Clipping at (pt1, pt2) and (pt0, pt2).
|
||||
if (clip_and_test2(pts[0], pts[1], pts[2], signs[1] > 0))
|
||||
break;
|
||||
} else {
|
||||
// Clipping at (pt1, pt2), pt0 must be on the clipping plane.
|
||||
if (clip_and_test1(pts[1], pts[2], pts[0], signs[1] > 0))
|
||||
break;
|
||||
}
|
||||
} else if (clips[2]) {
|
||||
// Clipping at (pt0, pt2), pt1 must be on the clipping plane.
|
||||
if (clip_and_test1(pts[2], pts[0], pts[1], signs[2] > 0))
|
||||
break;
|
||||
} else if (signs[0] >= 0 && signs[1] >= 0 && signs[2] >= 0) {
|
||||
// The triangle is above or on the clipping plane.
|
||||
if (test_intersection(pts[0], pts[1], pts[2]))
|
||||
break;
|
||||
}
|
||||
}
|
||||
return inside ? (outside ? BuildVolume::ObjectState::Colliding : BuildVolume::ObjectState::Inside) : BuildVolume::ObjectState::Outside;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Trim the input transformed triangle mesh with print bed and test the remaining vertices with is_inside callback.
|
||||
// Return inside / colliding / outside state.
|
||||
template<typename InsideFn>
|
||||
BuildVolume::ObjectState object_state_templ(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed, InsideFn is_inside)
|
||||
{
|
||||
size_t num_inside = 0;
|
||||
size_t num_above = 0;
|
||||
bool inside = false;
|
||||
bool outside = false;
|
||||
static constexpr const auto world_min_z = float(-BuildVolume::SceneEpsilon);
|
||||
|
||||
if (may_be_below_bed)
|
||||
{
|
||||
// Slower test, needs to clip the object edges with the print bed plane.
|
||||
// 1) Allocate transformed vertices with their position with respect to print bed surface.
|
||||
std::vector<char> sides;
|
||||
sides.reserve(its.vertices.size());
|
||||
|
||||
const auto sign = [](const stl_vertex& pt) { return pt.z() > world_min_z ? 1 : pt.z() < world_min_z ? -1 : 0; };
|
||||
|
||||
for (const stl_vertex &v : its.vertices) {
|
||||
const stl_vertex pt = trafo * v;
|
||||
const int s = sign(pt);
|
||||
sides.emplace_back(s);
|
||||
if (s >= 0) {
|
||||
// Vertex above or on print bed surface. Test whether it is inside the build volume.
|
||||
++ num_above;
|
||||
if (is_inside(pt))
|
||||
++ num_inside;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_above == 0)
|
||||
// Special case, the object is completely below the print bed, thus it is outside,
|
||||
// however we want to allow an object to be still printable if some of its parts are completely below the print bed.
|
||||
return BuildVolume::ObjectState::Below;
|
||||
|
||||
// 2) Calculate intersections of triangle edges with the build surface.
|
||||
inside = num_inside > 0;
|
||||
outside = num_inside < num_above;
|
||||
if (num_above < its.vertices.size() && ! (inside && outside)) {
|
||||
// Not completely above the build surface and status may still change by testing edges intersecting the build platform.
|
||||
for (const stl_triangle_vertex_indices &tri : its.indices) {
|
||||
const int s[3] = { sides[tri(0)], sides[tri(1)], sides[tri(2)] };
|
||||
if (std::min(s[0], std::min(s[1], s[2])) < 0 && std::max(s[0], std::max(s[1], s[2])) > 0) {
|
||||
// Some edge of this triangle intersects the build platform. Calculate the intersection.
|
||||
int iprev = 2;
|
||||
for (int iedge = 0; iedge < 3; ++ iedge) {
|
||||
if (s[iprev] * s[iedge] == -1) {
|
||||
// edge intersects the build surface. Calculate intersection point.
|
||||
const stl_vertex p1 = trafo * its.vertices[tri(iprev)];
|
||||
const stl_vertex p2 = trafo * its.vertices[tri(iedge)];
|
||||
assert(sign(p1) == s[iprev]);
|
||||
assert(sign(p2) == s[iedge]);
|
||||
assert(p1.z() * p2.z() < 0);
|
||||
// Edge crosses the z plane. Calculate intersection point with the plane.
|
||||
const float t = (world_min_z - p1.z()) / (p2.z() - p1.z());
|
||||
(is_inside(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z)) ? inside : outside) = true;
|
||||
}
|
||||
iprev = iedge;
|
||||
}
|
||||
if (inside && outside)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Much simpler and faster code, not clipping the object with the print bed.
|
||||
assert(! may_be_below_bed);
|
||||
num_above = its.vertices.size();
|
||||
for (const stl_vertex &v : its.vertices) {
|
||||
const stl_vertex pt = trafo * v;
|
||||
assert(pt.z() >= world_min_z);
|
||||
if (is_inside(pt))
|
||||
++ num_inside;
|
||||
}
|
||||
inside = num_inside > 0;
|
||||
outside = num_inside < num_above;
|
||||
}
|
||||
|
||||
return inside ? (outside ? BuildVolume::ObjectState::Colliding : BuildVolume::ObjectState::Inside) : BuildVolume::ObjectState::Outside;
|
||||
}
|
||||
|
||||
BuildVolume::ObjectState BuildVolume::object_state(const indexed_triangle_set& its, const Transform3f& trafo, bool may_be_below_bed, bool ignore_bottom) const
|
||||
{
|
||||
switch (m_type) {
|
||||
case Type::Rectangle:
|
||||
{
|
||||
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(SceneEpsilon);
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
BoundingBox3Base<Vec3f> build_volumef(build_volume.min.cast<float>(), build_volume.max.cast<float>());
|
||||
// The following test correctly interprets intersection of a non-convex object with a rectangular build volume.
|
||||
//return rectangle_test(its, trafo, to_2d(build_volume.min), to_2d(build_volume.max), build_volume.max.z());
|
||||
//FIXME This test does NOT correctly interprets intersection of a non-convex object with a rectangular build volume.
|
||||
return object_state_templ(its, trafo, may_be_below_bed, [build_volumef](const Vec3f &pt) { return build_volumef.contains(pt); });
|
||||
}
|
||||
case Type::Circle:
|
||||
{
|
||||
Geometry::Circlef circle { unscaled<float>(m_circle.center), unscaled<float>(m_circle.radius + SceneEpsilon) };
|
||||
return m_max_print_height == 0.0 ?
|
||||
object_state_templ(its, trafo, may_be_below_bed, [circle](const Vec3f &pt) { return circle.contains(to_2d(pt)); }) :
|
||||
object_state_templ(its, trafo, may_be_below_bed, [circle, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && circle.contains(to_2d(pt)); });
|
||||
}
|
||||
case Type::Convex:
|
||||
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
||||
case Type::Custom:
|
||||
return m_max_print_height == 0.0 ?
|
||||
object_state_templ(its, trafo, may_be_below_bed, [this](const Vec3f &pt) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); }) :
|
||||
object_state_templ(its, trafo, may_be_below_bed, [this, z = m_max_print_height + SceneEpsilon](const Vec3f &pt) { return pt.z() < z && Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_scene, to_2d(pt).cast<double>()); });
|
||||
case Type::Invalid:
|
||||
default:
|
||||
return ObjectState::Inside;
|
||||
}
|
||||
}
|
||||
|
||||
BuildVolume::ObjectState BuildVolume::volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom) const
|
||||
{
|
||||
assert(m_type == Type::Rectangle);
|
||||
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(SceneEpsilon);
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
return build_volume.max.z() <= - SceneEpsilon ? ObjectState::Below :
|
||||
build_volume.contains(volume_bbox) ? ObjectState::Inside :
|
||||
build_volume.intersects(volume_bbox) ? ObjectState::Colliding : ObjectState::Outside;
|
||||
}
|
||||
|
||||
bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const BoundingBoxf3& paths_bbox, bool ignore_bottom) const
|
||||
{
|
||||
auto move_valid = [](const GCodeProcessorResult::MoveVertex &move) {
|
||||
return move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.f && move.height != 0.f;
|
||||
};
|
||||
static constexpr const double epsilon = BedEpsilon;
|
||||
|
||||
switch (m_type) {
|
||||
case Type::Rectangle:
|
||||
{
|
||||
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(epsilon);
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
return build_volume.contains(paths_bbox);
|
||||
}
|
||||
case Type::Circle:
|
||||
{
|
||||
const Vec2f c = unscaled<float>(m_circle.center);
|
||||
const float r = unscaled<double>(m_circle.radius) + epsilon;
|
||||
const float r2 = sqr(r);
|
||||
return m_max_print_height == 0.0 ?
|
||||
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2](const GCodeProcessorResult::MoveVertex &move)
|
||||
{ return ! move_valid(move) || (to_2d(move.position) - c).squaredNorm() <= r2; }) :
|
||||
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex& move)
|
||||
{ return ! move_valid(move) || ((to_2d(move.position) - c).squaredNorm() <= r2 && move.position.z() <= z); });
|
||||
}
|
||||
case Type::Convex:
|
||||
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
||||
case Type::Custom:
|
||||
return m_max_print_height == 0.0 ?
|
||||
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this](const GCodeProcessorResult::MoveVertex &move)
|
||||
{ return ! move_valid(move) || Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast<double>()); }) :
|
||||
std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, this, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex &move)
|
||||
{ return ! move_valid(move) || (Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(move.position).cast<double>()) && move.position.z() <= z); });
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Fn>
|
||||
inline bool all_inside_vertices_normals_interleaved(const std::vector<float> &paths, Fn fn)
|
||||
{
|
||||
for (auto it = paths.begin(); it != paths.end(); ) {
|
||||
it += 3;
|
||||
if (! fn({ *it, *(it + 1), *(it + 2) }))
|
||||
return false;
|
||||
it += 3;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuildVolume::all_paths_inside_vertices_and_normals_interleaved(const std::vector<float>& paths, const Eigen::AlignedBox<float, 3>& paths_bbox, bool ignore_bottom) const
|
||||
{
|
||||
assert(paths.size() % 6 == 0);
|
||||
static constexpr const double epsilon = BedEpsilon;
|
||||
switch (m_type) {
|
||||
case Type::Rectangle:
|
||||
{
|
||||
BoundingBox3Base<Vec3d> build_volume = this->bounding_volume().inflated(epsilon);
|
||||
if (m_max_print_height == 0.0)
|
||||
build_volume.max.z() = std::numeric_limits<double>::max();
|
||||
if (ignore_bottom)
|
||||
build_volume.min.z() = -std::numeric_limits<double>::max();
|
||||
return build_volume.contains(paths_bbox.min().cast<double>()) && build_volume.contains(paths_bbox.max().cast<double>());
|
||||
}
|
||||
case Type::Circle:
|
||||
{
|
||||
const Vec2f c = unscaled<float>(m_circle.center);
|
||||
const float r = unscaled<double>(m_circle.radius) + float(epsilon);
|
||||
const float r2 = sqr(r);
|
||||
return m_max_print_height == 0.0 ?
|
||||
all_inside_vertices_normals_interleaved(paths, [c, r2](Vec3f p) { return (to_2d(p) - c).squaredNorm() <= r2; }) :
|
||||
all_inside_vertices_normals_interleaved(paths, [c, r2, z = m_max_print_height + epsilon](Vec3f p) { return (to_2d(p) - c).squaredNorm() <= r2 && p.z() <= z; });
|
||||
}
|
||||
case Type::Convex:
|
||||
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
||||
case Type::Custom:
|
||||
return m_max_print_height == 0.0 ?
|
||||
all_inside_vertices_normals_interleaved(paths, [this](Vec3f p) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(p).cast<double>()); }) :
|
||||
all_inside_vertices_normals_interleaved(paths, [this, z = m_max_print_height + epsilon](Vec3f p) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(p).cast<double>()) && p.z() <= z; });
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view BuildVolume::type_name(Type type)
|
||||
{
|
||||
using namespace std::literals;
|
||||
switch (type) {
|
||||
case Type::Invalid: return "Invalid"sv;
|
||||
case Type::Rectangle: return "Rectangle"sv;
|
||||
case Type::Circle: return "Circle"sv;
|
||||
case Type::Convex: return "Convex"sv;
|
||||
case Type::Custom: return "Custom"sv;
|
||||
}
|
||||
// make visual studio happy
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
indexed_triangle_set BuildVolume::bounding_mesh(bool scale) const
|
||||
{
|
||||
auto max_pt3 = m_bboxf.max;
|
||||
if (scale) {
|
||||
return its_make_cube(scale_(max_pt3.x()), scale_(max_pt3.y()), scale_(max_pt3.z()));
|
||||
}
|
||||
else {
|
||||
return its_make_cube(max_pt3.x(), max_pt3.y(), max_pt3.z());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
127
src/libslic3r/BuildVolume.hpp
Normal file
127
src/libslic3r/BuildVolume.hpp
Normal file
@@ -0,0 +1,127 @@
|
||||
#ifndef slic3r_BuildVolume_hpp_
|
||||
#define slic3r_BuildVolume_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "Geometry/Circle.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include <admesh/stl.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct GCodeProcessorResult;
|
||||
|
||||
// For collision detection of objects and G-code (extrusion paths) against the build volume.
|
||||
class BuildVolume
|
||||
{
|
||||
public:
|
||||
enum class Type : unsigned char
|
||||
{
|
||||
// Not set yet or undefined.
|
||||
Invalid,
|
||||
// Rectangular print bed. Most common, cheap to work with.
|
||||
Rectangle,
|
||||
// Circular print bed. Common on detals, cheap to work with.
|
||||
Circle,
|
||||
// Convex print bed. Complex to process.
|
||||
Convex,
|
||||
// Some non convex shape.
|
||||
Custom
|
||||
};
|
||||
|
||||
// Initialized to empty, all zeros, Invalid.
|
||||
BuildVolume() {}
|
||||
// Initialize from PrintConfig::printable_area and PrintConfig::printable_height
|
||||
BuildVolume(const std::vector<Vec2d> &printable_area, const double printable_height);
|
||||
|
||||
// Source data, unscaled coordinates.
|
||||
const std::vector<Vec2d>& printable_area() const { return m_bed_shape; }
|
||||
double printable_height() const { return m_max_print_height; }
|
||||
|
||||
// Derived data
|
||||
Type type() const { return m_type; }
|
||||
// Format the type for console output.
|
||||
static std::string_view type_name(Type type);
|
||||
std::string_view type_name() const { return type_name(m_type); }
|
||||
bool valid() const { return m_type != Type::Invalid; }
|
||||
// Same as printable_area(), but scaled coordinates.
|
||||
const Polygon& polygon() const { return m_polygon; }
|
||||
// Bounding box of polygon(), scaled.
|
||||
const BoundingBox& bounding_box() const { return m_bbox; }
|
||||
// Bounding volume of printable_area(), printable_height(), unscaled.
|
||||
const BoundingBoxf3& bounding_volume() const { return m_bboxf; }
|
||||
BoundingBoxf bounding_volume2d() const { return { to_2d(m_bboxf.min), to_2d(m_bboxf.max) }; }
|
||||
indexed_triangle_set bounding_mesh(bool scale=true) const;
|
||||
|
||||
// Center of the print bed, unscaled.
|
||||
Vec2d bed_center() const { return to_2d(m_bboxf.center()); }
|
||||
// Convex hull of polygon(), scaled.
|
||||
const Polygon& convex_hull() const { return m_convex_hull; }
|
||||
// Smallest enclosing circle of polygon(), scaled.
|
||||
const Geometry::Circled& circle() const { return m_circle; }
|
||||
|
||||
enum class ObjectState : unsigned char
|
||||
{
|
||||
// Inside the build volume, thus printable.
|
||||
Inside,
|
||||
// Colliding with the build volume boundary, thus not printable and error is shown.
|
||||
Colliding,
|
||||
// Outside of the build volume means the object is ignored: Not printed and no error is shown.
|
||||
Outside,
|
||||
// Completely below the print bed. The same as Outside, but an object with one printable part below the print bed
|
||||
// and at least one part above the print bed is still printable.
|
||||
Below,
|
||||
};
|
||||
|
||||
// 1) Tests called on the plater.
|
||||
// Using SceneEpsilon for all tests.
|
||||
static constexpr const double SceneEpsilon = EPSILON;
|
||||
// Called by Plater to update Inside / Colliding / Outside state of ModelObjects before slicing.
|
||||
// Called from Model::update_print_volume_state() -> ModelObject::update_instances_print_volume_state()
|
||||
// Using SceneEpsilon
|
||||
ObjectState object_state(const indexed_triangle_set &its, const Transform3f &trafo, bool may_be_below_bed, bool ignore_bottom = true) const;
|
||||
// Called by GLVolumeCollection::check_outside_state() after an object is manipulated with gizmos for example.
|
||||
// Called for a rectangular bed:
|
||||
ObjectState volume_state_bbox(const BoundingBoxf3& volume_bbox, bool ignore_bottom = true) const;
|
||||
|
||||
// 2) Test called on G-code paths.
|
||||
// Using BedEpsilon for all tests.
|
||||
static constexpr const double BedEpsilon = 3. * EPSILON;
|
||||
// Called on final G-code paths.
|
||||
//FIXME The test does not take the thickness of the extrudates into account!
|
||||
bool all_paths_inside(const GCodeProcessorResult& paths, const BoundingBoxf3& paths_bbox, bool ignore_bottom = true) const;
|
||||
// Called on initial G-code preview on OpenGL vertex buffer interleaved normals and vertices.
|
||||
bool all_paths_inside_vertices_and_normals_interleaved(const std::vector<float>& paths, const Eigen::AlignedBox<float, 3>& bbox, bool ignore_bottom = true) const;
|
||||
|
||||
private:
|
||||
// Source definition of the print bed geometry (PrintConfig::printable_area)
|
||||
std::vector<Vec2d> m_bed_shape;
|
||||
// Source definition of the print volume height (PrintConfig::printable_height)
|
||||
double m_max_print_height { 0.f };
|
||||
|
||||
// Derived values.
|
||||
Type m_type { Type::Invalid };
|
||||
// Geometry of the print bed, scaled copy of m_bed_shape.
|
||||
Polygon m_polygon;
|
||||
// Scaled snug bounding box around m_polygon.
|
||||
BoundingBox m_bbox;
|
||||
// 3D bounding box around m_shape, m_max_print_height.
|
||||
BoundingBoxf3 m_bboxf;
|
||||
// Area of m_polygon, scaled.
|
||||
double m_area { 0. };
|
||||
// Convex hull of m_polygon, scaled.
|
||||
Polygon m_convex_hull;
|
||||
// For collision detection against a convex build volume. Only filled in for m_type == Convex or Custom.
|
||||
// Variant with SceneEpsilon applied.
|
||||
std::pair<std::vector<Vec2d>, std::vector<Vec2d>> m_top_bottom_convex_hull_decomposition_scene;
|
||||
// Variant with BedEpsilon applied.
|
||||
std::pair<std::vector<Vec2d>, std::vector<Vec2d>> m_top_bottom_convex_hull_decomposition_bed;
|
||||
// Smallest enclosing circle of m_polygon, scaled.
|
||||
Geometry::Circled m_circle { Vec2d::Zero(), 0 };
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_BuildVolume_hpp_
|
||||
560
src/libslic3r/CMakeLists.txt
Normal file
560
src/libslic3r/CMakeLists.txt
Normal file
@@ -0,0 +1,560 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(libslic3r)
|
||||
|
||||
include(PrecompiledHeader)
|
||||
|
||||
string(TIMESTAMP COMPILE_TIME %Y%m%d-%H%M%S)
|
||||
set(SLIC3R_BUILD_TIME ${COMPILE_TIME})
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libslic3r_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h @ONLY)
|
||||
|
||||
if (MINGW)
|
||||
add_compile_options(-Wa,-mbig-obj)
|
||||
endif ()
|
||||
|
||||
set(OpenVDBUtils_SOURCES "")
|
||||
if (TARGET OpenVDB::openvdb)
|
||||
set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp)
|
||||
endif()
|
||||
|
||||
set(lisbslic3r_sources
|
||||
ArcFitter.cpp
|
||||
ArcFitter.hpp
|
||||
pchheader.cpp
|
||||
pchheader.hpp
|
||||
AABBTreeIndirect.hpp
|
||||
AABBTreeLines.hpp
|
||||
AABBMesh.hpp
|
||||
AABBMesh.cpp
|
||||
AnyPtr.hpp
|
||||
AStar.hpp
|
||||
BoundingBox.cpp
|
||||
BoundingBox.hpp
|
||||
BridgeDetector.cpp
|
||||
BridgeDetector.hpp
|
||||
FaceDetector.cpp
|
||||
FaceDetector.hpp
|
||||
Brim.cpp
|
||||
Brim.hpp
|
||||
BuildVolume.cpp
|
||||
BuildVolume.hpp
|
||||
Calib.cpp
|
||||
Calib.hpp
|
||||
Circle.cpp
|
||||
Circle.hpp
|
||||
clipper.cpp
|
||||
clipper.hpp
|
||||
ClipperUtils.cpp
|
||||
ClipperUtils.hpp
|
||||
Clipper2Utils.cpp
|
||||
Clipper2Utils.hpp
|
||||
Color.cpp
|
||||
Color.hpp
|
||||
Config.cpp
|
||||
Config.hpp
|
||||
CurveAnalyzer.cpp
|
||||
CurveAnalyzer.hpp
|
||||
CutUtils.cpp
|
||||
CutUtils.hpp
|
||||
EdgeGrid.cpp
|
||||
EdgeGrid.hpp
|
||||
ElephantFootCompensation.cpp
|
||||
ElephantFootCompensation.hpp
|
||||
enum_bitmask.hpp
|
||||
ExPolygon.cpp
|
||||
ExPolygon.hpp
|
||||
Extruder.cpp
|
||||
Extruder.hpp
|
||||
ExtrusionEntity.cpp
|
||||
ExtrusionEntity.hpp
|
||||
ExtrusionEntityCollection.cpp
|
||||
ExtrusionEntityCollection.hpp
|
||||
ExtrusionSimulator.cpp
|
||||
ExtrusionSimulator.hpp
|
||||
FileParserError.hpp
|
||||
Fill/Fill.cpp
|
||||
Fill/Fill.hpp
|
||||
Fill/Fill3DHoneycomb.cpp
|
||||
Fill/Fill3DHoneycomb.hpp
|
||||
Fill/FillAdaptive.cpp
|
||||
Fill/FillAdaptive.hpp
|
||||
Fill/FillBase.cpp
|
||||
Fill/FillBase.hpp
|
||||
Fill/FillConcentric.cpp
|
||||
Fill/FillConcentric.hpp
|
||||
Fill/FillConcentricInternal.cpp
|
||||
Fill/FillConcentricInternal.hpp
|
||||
Fill/FillCrossHatch.cpp
|
||||
Fill/FillCrossHatch.hpp
|
||||
Fill/FillHoneycomb.cpp
|
||||
Fill/FillHoneycomb.hpp
|
||||
Fill/FillGyroid.cpp
|
||||
Fill/FillGyroid.hpp
|
||||
Fill/FillPlanePath.cpp
|
||||
Fill/FillPlanePath.hpp
|
||||
Fill/FillLine.cpp
|
||||
Fill/FillLine.hpp
|
||||
Fill/FillLightning.cpp
|
||||
Fill/FillLightning.hpp
|
||||
Fill/Lightning/DistanceField.cpp
|
||||
Fill/Lightning/DistanceField.hpp
|
||||
Fill/Lightning/Generator.cpp
|
||||
Fill/Lightning/Generator.hpp
|
||||
Fill/Lightning/Layer.cpp
|
||||
Fill/Lightning/Layer.hpp
|
||||
Fill/Lightning/TreeNode.cpp
|
||||
Fill/Lightning/TreeNode.hpp
|
||||
Fill/FillRectilinear.cpp
|
||||
Fill/FillRectilinear.hpp
|
||||
Flow.cpp
|
||||
Flow.hpp
|
||||
FlushVolCalc.cpp
|
||||
FlushVolCalc.hpp
|
||||
format.hpp
|
||||
Format/3mf.cpp
|
||||
Format/3mf.hpp
|
||||
Format/qds_3mf.cpp
|
||||
Format/qds_3mf.hpp
|
||||
Format/AMF.cpp
|
||||
Format/AMF.hpp
|
||||
Format/OBJ.cpp
|
||||
Format/OBJ.hpp
|
||||
Format/objparser.cpp
|
||||
Format/objparser.hpp
|
||||
Format/STEP.cpp
|
||||
Format/STEP.hpp
|
||||
Format/STL.cpp
|
||||
Format/STL.hpp
|
||||
Format/SL1.hpp
|
||||
Format/SL1.cpp
|
||||
Format/svg.hpp
|
||||
Format/svg.cpp
|
||||
GCode/ThumbnailData.cpp
|
||||
GCode/ThumbnailData.hpp
|
||||
GCode/CoolingBuffer.cpp
|
||||
GCode/CoolingBuffer.hpp
|
||||
GCode/PostProcessor.cpp
|
||||
GCode/PostProcessor.hpp
|
||||
# GCode/PressureEqualizer.cpp
|
||||
# GCode/PressureEqualizer.hpp
|
||||
GCode/PrintExtents.cpp
|
||||
GCode/PrintExtents.hpp
|
||||
GCode/RetractWhenCrossingPerimeters.cpp
|
||||
GCode/RetractWhenCrossingPerimeters.hpp
|
||||
GCode/SpiralVase.cpp
|
||||
GCode/SpiralVase.hpp
|
||||
GCode/SeamPlacer.cpp
|
||||
GCode/SeamPlacer.hpp
|
||||
GCode/ToolOrdering.cpp
|
||||
GCode/ToolOrdering.hpp
|
||||
GCode/WipeTower.cpp
|
||||
GCode/WipeTower.hpp
|
||||
GCode/GCodeProcessor.cpp
|
||||
GCode/GCodeProcessor.hpp
|
||||
GCode/AvoidCrossingPerimeters.cpp
|
||||
GCode/AvoidCrossingPerimeters.hpp
|
||||
GCode/ConflictChecker.cpp
|
||||
GCode/ConflictChecker.hpp
|
||||
GCode.cpp
|
||||
GCode.hpp
|
||||
GCodeReader.cpp
|
||||
GCodeReader.hpp
|
||||
# GCodeSender.cpp
|
||||
# GCodeSender.hpp
|
||||
GCodeWriter.cpp
|
||||
GCodeWriter.hpp
|
||||
Geometry.cpp
|
||||
Geometry.hpp
|
||||
Geometry/Bicubic.hpp
|
||||
Geometry/Circle.cpp
|
||||
Geometry/Circle.hpp
|
||||
Geometry/ConvexHull.cpp
|
||||
Geometry/ConvexHull.hpp
|
||||
Geometry/Curves.hpp
|
||||
Geometry/MedialAxis.cpp
|
||||
Geometry/MedialAxis.hpp
|
||||
Geometry/Voronoi.cpp
|
||||
Geometry/Voronoi.hpp
|
||||
Geometry/VoronoiOffset.cpp
|
||||
Geometry/VoronoiOffset.hpp
|
||||
Geometry/VoronoiUtils.hpp
|
||||
Geometry/VoronoiUtils.cpp
|
||||
Geometry/VoronoiUtilsCgal.cpp
|
||||
Geometry/VoronoiUtilsCgal.hpp
|
||||
Geometry/VoronoiVisualUtils.hpp
|
||||
Int128.hpp
|
||||
InternalBridgeDetector.cpp
|
||||
InternalBridgeDetector.hpp
|
||||
JumpPointSearch.hpp
|
||||
JumpPointSearch.cpp
|
||||
KDTreeIndirect.hpp
|
||||
Layer.cpp
|
||||
Layer.hpp
|
||||
LayerRegion.cpp
|
||||
libslic3r.h
|
||||
Line.cpp
|
||||
Line.hpp
|
||||
BlacklistedLibraryCheck.cpp
|
||||
BlacklistedLibraryCheck.hpp
|
||||
LocalesUtils.cpp
|
||||
LocalesUtils.hpp
|
||||
Model.cpp
|
||||
Model.hpp
|
||||
ModelArrange.hpp
|
||||
ModelArrange.cpp
|
||||
MultiMaterialSegmentation.cpp
|
||||
MultiMaterialSegmentation.hpp
|
||||
Measure.hpp
|
||||
Measure.cpp
|
||||
MeasureUtils.hpp
|
||||
CustomGCode.cpp
|
||||
CustomGCode.hpp
|
||||
Arrange.hpp
|
||||
Arrange.cpp
|
||||
NormalUtils.cpp
|
||||
NormalUtils.hpp
|
||||
ObjColorUtils.hpp
|
||||
Orient.hpp
|
||||
Orient.cpp
|
||||
MultiPoint.cpp
|
||||
MultiPoint.hpp
|
||||
MutablePriorityQueue.hpp
|
||||
ObjectID.cpp
|
||||
ObjectID.hpp
|
||||
ParameterUtils.cpp
|
||||
ParameterUtils.hpp
|
||||
PerimeterGenerator.cpp
|
||||
PerimeterGenerator.hpp
|
||||
PlaceholderParser.cpp
|
||||
PlaceholderParser.hpp
|
||||
Platform.cpp
|
||||
Platform.hpp
|
||||
Point.cpp
|
||||
Point.hpp
|
||||
Polygon.cpp
|
||||
Polygon.hpp
|
||||
MutablePolygon.cpp
|
||||
MutablePolygon.hpp
|
||||
PolygonTrimmer.cpp
|
||||
PolygonTrimmer.hpp
|
||||
Polyline.cpp
|
||||
Polyline.hpp
|
||||
Preset.cpp
|
||||
Preset.hpp
|
||||
PresetBundle.cpp
|
||||
PresetBundle.hpp
|
||||
ProjectTask.cpp
|
||||
ProjectTask.hpp
|
||||
PrincipalComponents2D.hpp
|
||||
PrincipalComponents2D.cpp
|
||||
AppConfig.cpp
|
||||
AppConfig.hpp
|
||||
Print.cpp
|
||||
Print.hpp
|
||||
PrintApply.cpp
|
||||
PrintBase.cpp
|
||||
PrintBase.hpp
|
||||
PrintConfig.cpp
|
||||
PrintConfig.hpp
|
||||
PrintObject.cpp
|
||||
PrintObjectSlice.cpp
|
||||
PrintRegion.cpp
|
||||
PNGReadWrite.hpp
|
||||
PNGReadWrite.cpp
|
||||
QuadricEdgeCollapse.cpp
|
||||
QuadricEdgeCollapse.hpp
|
||||
Semver.cpp
|
||||
ShortEdgeCollapse.cpp
|
||||
ShortEdgeCollapse.hpp
|
||||
ShortestPath.cpp
|
||||
ShortestPath.hpp
|
||||
SLAPrint.cpp
|
||||
SLAPrintSteps.cpp
|
||||
SLAPrintSteps.hpp
|
||||
SLAPrint.hpp
|
||||
Slicing.cpp
|
||||
Slicing.hpp
|
||||
SlicesToTriangleMesh.hpp
|
||||
SlicesToTriangleMesh.cpp
|
||||
SlicingAdaptive.cpp
|
||||
SlicingAdaptive.hpp
|
||||
Support/SupportMaterial.cpp
|
||||
Support/SupportMaterial.hpp
|
||||
Support/TreeSupport.hpp
|
||||
Support/TreeSupport.cpp
|
||||
Support/TreeSupport3D.hpp
|
||||
Support/TreeSupport3D.cpp
|
||||
Support/TreeModelVolumes.hpp
|
||||
Support/TreeModelVolumes.cpp
|
||||
Support/TreeSupportCommon.hpp
|
||||
MinimumSpanningTree.hpp
|
||||
MinimumSpanningTree.cpp
|
||||
Surface.cpp
|
||||
Surface.hpp
|
||||
SurfaceCollection.cpp
|
||||
SurfaceCollection.hpp
|
||||
SurfaceMesh.hpp
|
||||
SVG.cpp
|
||||
SVG.hpp
|
||||
Technologies.hpp
|
||||
Tesselate.cpp
|
||||
Tesselate.hpp
|
||||
TriangleMesh.cpp
|
||||
TriangleMesh.hpp
|
||||
TriangleMeshSlicer.cpp
|
||||
TriangleMeshSlicer.hpp
|
||||
MeshSplitImpl.hpp
|
||||
TriangulateWall.hpp
|
||||
TriangulateWall.cpp
|
||||
utils.cpp
|
||||
Utils.hpp
|
||||
Time.cpp
|
||||
Time.hpp
|
||||
Thread.cpp
|
||||
Thread.hpp
|
||||
TriangleSelector.cpp
|
||||
TriangleSelector.hpp
|
||||
TriangleSetSampling.cpp
|
||||
TriangleSetSampling.hpp
|
||||
MTUtils.hpp
|
||||
VariableWidth.cpp
|
||||
VariableWidth.hpp
|
||||
Zipper.hpp
|
||||
Zipper.cpp
|
||||
MinAreaBoundingBox.hpp
|
||||
MinAreaBoundingBox.cpp
|
||||
miniz_extension.hpp
|
||||
miniz_extension.cpp
|
||||
MarchingSquares.hpp
|
||||
Execution/Execution.hpp
|
||||
Execution/ExecutionSeq.hpp
|
||||
Execution/ExecutionTBB.hpp
|
||||
Optimize/Optimizer.hpp
|
||||
Optimize/NLoptOptimizer.hpp
|
||||
Optimize/BruteforceOptimizer.hpp
|
||||
SLA/Pad.hpp
|
||||
SLA/Pad.cpp
|
||||
SLA/SupportTreeBuilder.hpp
|
||||
SLA/SupportTreeMesher.hpp
|
||||
SLA/SupportTreeMesher.cpp
|
||||
SLA/SupportTreeBuildsteps.hpp
|
||||
SLA/SupportTreeBuildsteps.cpp
|
||||
SLA/SupportTreeBuilder.cpp
|
||||
SLA/Concurrency.hpp
|
||||
SLA/SupportTree.hpp
|
||||
SLA/SupportTree.cpp
|
||||
# SLA/SupportTreeIGL.cpp
|
||||
SLA/Rotfinder.hpp
|
||||
SLA/Rotfinder.cpp
|
||||
SLA/BoostAdapter.hpp
|
||||
SLA/SpatIndex.hpp
|
||||
SLA/SpatIndex.cpp
|
||||
SLA/RasterBase.hpp
|
||||
SLA/RasterBase.cpp
|
||||
SLA/AGGRaster.hpp
|
||||
SLA/RasterToPolygons.hpp
|
||||
SLA/RasterToPolygons.cpp
|
||||
SLA/ConcaveHull.hpp
|
||||
SLA/ConcaveHull.cpp
|
||||
SLA/Hollowing.hpp
|
||||
SLA/Hollowing.cpp
|
||||
SLA/JobController.hpp
|
||||
SLA/SupportPoint.hpp
|
||||
SLA/SupportPointGenerator.hpp
|
||||
SLA/SupportPointGenerator.cpp
|
||||
SLA/IndexedMesh.hpp
|
||||
SLA/IndexedMesh.cpp
|
||||
SLA/Clustering.hpp
|
||||
SLA/Clustering.cpp
|
||||
SLA/ReprojectPointsOnMesh.hpp
|
||||
Arachne/BeadingStrategy/BeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/BeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/BeadingStrategyFactory.hpp
|
||||
Arachne/BeadingStrategy/BeadingStrategyFactory.cpp
|
||||
Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp
|
||||
Arachne/BeadingStrategy/WideningBeadingStrategy.hpp
|
||||
Arachne/BeadingStrategy/WideningBeadingStrategy.cpp
|
||||
Arachne/utils/ExtrusionJunction.hpp
|
||||
Arachne/utils/ExtrusionJunction.cpp
|
||||
Arachne/utils/ExtrusionLine.hpp
|
||||
Arachne/utils/ExtrusionLine.cpp
|
||||
Arachne/utils/HalfEdge.hpp
|
||||
Arachne/utils/HalfEdgeGraph.hpp
|
||||
Arachne/utils/HalfEdgeNode.hpp
|
||||
Arachne/utils/SparseGrid.hpp
|
||||
Arachne/utils/SparsePointGrid.hpp
|
||||
Arachne/utils/SparseLineGrid.hpp
|
||||
Arachne/utils/SquareGrid.hpp
|
||||
Arachne/utils/SquareGrid.cpp
|
||||
Arachne/utils/PolygonsPointIndex.hpp
|
||||
Arachne/utils/PolygonsSegmentIndex.hpp
|
||||
Arachne/utils/PolylineStitcher.hpp
|
||||
Arachne/utils/PolylineStitcher.cpp
|
||||
Arachne/SkeletalTrapezoidation.hpp
|
||||
Arachne/SkeletalTrapezoidation.cpp
|
||||
Arachne/SkeletalTrapezoidationEdge.hpp
|
||||
Arachne/SkeletalTrapezoidationGraph.hpp
|
||||
Arachne/SkeletalTrapezoidationGraph.cpp
|
||||
Arachne/SkeletalTrapezoidationJoint.hpp
|
||||
Arachne/WallToolPaths.hpp
|
||||
Arachne/WallToolPaths.cpp
|
||||
Shape/TextShape.hpp
|
||||
Shape/TextShape.cpp
|
||||
RegionExpansion.hpp
|
||||
RegionExpansion.cpp
|
||||
ClipperZUtils.hpp
|
||||
GCode/Thumbnails.cpp
|
||||
GCode/Thumbnails.hpp
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
list(APPEND lisbslic3r_sources
|
||||
MacUtils.mm
|
||||
Format/ModelIO.hpp
|
||||
Format/ModelIO.mm
|
||||
)
|
||||
endif ()
|
||||
|
||||
add_library(libslic3r STATIC ${lisbslic3r_sources}
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/libslic3r_version.h"
|
||||
${OpenVDBUtils_SOURCES})
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${lisbslic3r_sources})
|
||||
|
||||
if (SLIC3R_STATIC)
|
||||
set(CGAL_Boost_USE_STATIC_LIBS ON CACHE BOOL "" FORCE)
|
||||
endif ()
|
||||
set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE)
|
||||
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(SET CMP0011 NEW)
|
||||
find_package(CGAL REQUIRED)
|
||||
find_package(OpenCV REQUIRED core)
|
||||
cmake_policy(POP)
|
||||
|
||||
add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp TryCatchSignal.hpp
|
||||
TryCatchSignal.cpp)
|
||||
target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Reset compile options of libslic3r_cgal. Despite it being linked privately, CGAL options
|
||||
# (-frounding-math) still propagate to dependent libs which is not desired.
|
||||
get_target_property(_cgal_tgt CGAL::CGAL ALIASED_TARGET)
|
||||
if (NOT TARGET ${_cgal_tgt})
|
||||
set (_cgal_tgt CGAL::CGAL)
|
||||
endif ()
|
||||
get_target_property(_opts ${_cgal_tgt} INTERFACE_COMPILE_OPTIONS)
|
||||
if (_opts)
|
||||
set(_opts_bad "${_opts}")
|
||||
set(_opts_good "${_opts}")
|
||||
list(FILTER _opts_bad INCLUDE REGEX frounding-math)
|
||||
list(FILTER _opts_good EXCLUDE REGEX frounding-math)
|
||||
set_target_properties(${_cgal_tgt} PROPERTIES INTERFACE_COMPILE_OPTIONS "${_opts_good}")
|
||||
target_compile_options(libslic3r_cgal PRIVATE "${_opts_bad}")
|
||||
endif()
|
||||
|
||||
target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} libigl mcut)
|
||||
|
||||
if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround
|
||||
target_compile_definitions(libslic3r_cgal PRIVATE CGAL_DO_NOT_USE_MPZF)
|
||||
endif ()
|
||||
|
||||
encoding_check(libslic3r)
|
||||
|
||||
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
|
||||
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_include_directories(libslic3r PUBLIC ${EXPAT_INCLUDE_DIRS})
|
||||
|
||||
# Find the OCCT and related libraries
|
||||
set(OpenCASCADE_DIR "${CMAKE_PREFIX_PATH}/lib/cmake/occt")
|
||||
find_package(OpenCASCADE REQUIRED)
|
||||
target_include_directories(libslic3r PUBLIC ${OpenCASCADE_INCLUDE_DIR})
|
||||
|
||||
set(OCCT_LIBS
|
||||
TKXDESTEP
|
||||
TKSTEP
|
||||
TKSTEP209
|
||||
TKSTEPAttr
|
||||
TKSTEPBase
|
||||
TKXCAF
|
||||
TKXSBase
|
||||
TKVCAF
|
||||
TKCAF
|
||||
TKLCAF
|
||||
TKCDF
|
||||
TKV3d
|
||||
TKService
|
||||
TKMesh
|
||||
TKBO
|
||||
TKPrim
|
||||
TKHLR
|
||||
TKShHealing
|
||||
TKTopAlgo
|
||||
TKGeomAlgo
|
||||
TKBRep
|
||||
TKGeomBase
|
||||
TKG3d
|
||||
TKG2d
|
||||
TKMath
|
||||
TKernel
|
||||
)
|
||||
|
||||
|
||||
target_link_libraries(libslic3r
|
||||
libnest2d
|
||||
admesh
|
||||
cereal
|
||||
libigl
|
||||
miniz
|
||||
boost_libs
|
||||
clipper
|
||||
nowide
|
||||
${EXPAT_LIBRARIES}
|
||||
glu-libtess
|
||||
qhull
|
||||
semver
|
||||
TBB::tbb
|
||||
TBB::tbbmalloc
|
||||
libslic3r_cgal
|
||||
${CMAKE_DL_LIBS}
|
||||
PNG::PNG
|
||||
ZLIB::ZLIB
|
||||
${OCCT_LIBS}
|
||||
Clipper2
|
||||
mcut
|
||||
opencv_world
|
||||
)
|
||||
|
||||
if(NOT WIN32)
|
||||
target_link_libraries(libslic3r freetype)
|
||||
if (NOT APPLE)
|
||||
target_link_libraries(libslic3r fontconfig)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
find_library(FOUNDATION Foundation REQUIRED)
|
||||
find_library(MODELIO ModelIO REQUIRED)
|
||||
target_link_libraries(libslic3r ${FOUNDATION} ${MODELIO})
|
||||
endif ()
|
||||
|
||||
if (TARGET OpenVDB::openvdb)
|
||||
target_link_libraries(libslic3r OpenVDB::openvdb)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(libslic3r Psapi.lib)
|
||||
endif()
|
||||
|
||||
if(SLIC3R_PROFILE)
|
||||
target_link_libraries(libslic3r Shiny)
|
||||
endif()
|
||||
|
||||
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
||||
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
|
||||
endif ()
|
||||
87
src/libslic3r/CSGMesh/CSGMesh.hpp
Normal file
87
src/libslic3r/CSGMesh/CSGMesh.hpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#ifndef CSGMESH_HPP
|
||||
#define CSGMESH_HPP
|
||||
|
||||
#include <libslic3r/AnyPtr.hpp>
|
||||
#include <admesh/stl.h>
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// A CSGPartT should be an object that can provide at least a mesh + trafo and an
|
||||
// associated csg operation. A collection of CSGPartT objects can then
|
||||
// be interpreted as one model and used in various contexts. It can be assembled
|
||||
// with CGAL or OpenVDB, rendered with OpenCSG or provided to a ray-tracer to
|
||||
// deal with various parts of it according to the supported CSG types...
|
||||
//
|
||||
// A few simple templated interface functions are provided here and a default
|
||||
// CSGPart class that implements the necessary means to be usable as a
|
||||
// CSGPartT object.
|
||||
|
||||
// Supported CSG operation types
|
||||
enum class CSGType { Union, Difference, Intersection };
|
||||
|
||||
// A CSG part can instruct the processing to push the sub-result until a new
|
||||
// csg part with a pop instruction appears. This can be used to implement
|
||||
// parentheses in a CSG expression represented by the collection of csg parts.
|
||||
// A CSG part can not contain another CSG collection, only a mesh, this is why
|
||||
// its easier to do this stacking instead of recursion in the data definition.
|
||||
// CSGStackOp::Continue means no stack operation required.
|
||||
// When a CSG part contains a Push instruction, it is expected that the CSG
|
||||
// operation it contains refers to the whole collection spanning to the nearest
|
||||
// part with a Pop instruction.
|
||||
// e.g.:
|
||||
// {
|
||||
// CUBE1: { mesh: cube, op: Union, stack op: Continue },
|
||||
// CUBE2: { mesh: cube, op: Difference, stack op: Push},
|
||||
// CUBE3: { mesh: cube, op: Union, stack op: Pop}
|
||||
// }
|
||||
// is a collection of csg parts representing the expression CUBE1 - (CUBE2 + CUBE3)
|
||||
enum class CSGStackOp { Push, Continue, Pop };
|
||||
|
||||
// Get the CSG operation of the part. Can be overriden for any type
|
||||
template<class CSGPartT> CSGType get_operation(const CSGPartT &part)
|
||||
{
|
||||
return part.operation;
|
||||
}
|
||||
|
||||
// Get the stack operation required by the CSG part.
|
||||
template<class CSGPartT> CSGStackOp get_stack_operation(const CSGPartT &part)
|
||||
{
|
||||
return part.stack_operation;
|
||||
}
|
||||
|
||||
// Get the mesh for the part. Can be overriden for any type
|
||||
template<class CSGPartT>
|
||||
const indexed_triangle_set *get_mesh(const CSGPartT &part)
|
||||
{
|
||||
return part.its_ptr.get();
|
||||
}
|
||||
|
||||
// Get the transformation associated with the mesh inside a CSGPartT object.
|
||||
// Can be overriden for any type.
|
||||
template<class CSGPartT>
|
||||
Transform3f get_transform(const CSGPartT &part)
|
||||
{
|
||||
return part.trafo;
|
||||
}
|
||||
|
||||
// Default implementation
|
||||
struct CSGPart {
|
||||
AnyPtr<const indexed_triangle_set> its_ptr;
|
||||
Transform3f trafo;
|
||||
CSGType operation;
|
||||
CSGStackOp stack_operation;
|
||||
std::string name;
|
||||
|
||||
CSGPart(AnyPtr<const indexed_triangle_set> ptr = {},
|
||||
CSGType op = CSGType::Union,
|
||||
const Transform3f &tr = Transform3f::Identity())
|
||||
: its_ptr{std::move(ptr)}
|
||||
, operation{op}
|
||||
, stack_operation{CSGStackOp::Continue}
|
||||
, trafo{tr}
|
||||
{}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // CSGMESH_HPP
|
||||
80
src/libslic3r/CSGMesh/CSGMeshCopy.hpp
Normal file
80
src/libslic3r/CSGMesh/CSGMeshCopy.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#ifndef CSGMESHCOPY_HPP
|
||||
#define CSGMESHCOPY_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// Copy a csg range but for the meshes, only copy the pointers. If the copy
|
||||
// is made from a CSGPart compatible object, and the pointer is a shared one,
|
||||
// it will be copied with reference counting.
|
||||
template<class It, class OutIt>
|
||||
void copy_csgrange_shallow(const Range<It> &csgrange, OutIt out)
|
||||
{
|
||||
for (const auto &part : csgrange) {
|
||||
CSGPart cpy{{},
|
||||
get_operation(part),
|
||||
get_transform(part)};
|
||||
|
||||
cpy.stack_operation = get_stack_operation(part);
|
||||
|
||||
if constexpr (std::is_convertible_v<decltype(part), const CSGPart&>) {
|
||||
if (auto shptr = part.its_ptr.get_shared_cpy()) {
|
||||
cpy.its_ptr = shptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cpy.its_ptr)
|
||||
cpy.its_ptr = AnyPtr<const indexed_triangle_set>{get_mesh(part)};
|
||||
|
||||
*out = std::move(cpy);
|
||||
++out;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the csg range, allocating new meshes
|
||||
template<class It, class OutIt>
|
||||
void copy_csgrange_deep(const Range<It> &csgrange, OutIt out)
|
||||
{
|
||||
for (const auto &part : csgrange) {
|
||||
|
||||
CSGPart cpy{{}, get_operation(part), get_transform(part)};
|
||||
|
||||
if (auto meshptr = get_mesh(part)) {
|
||||
cpy.its_ptr = std::make_unique<const indexed_triangle_set>(*meshptr);
|
||||
}
|
||||
|
||||
cpy.stack_operation = get_stack_operation(part);
|
||||
|
||||
*out = std::move(cpy);
|
||||
++out;
|
||||
}
|
||||
}
|
||||
|
||||
template<class ItA, class ItB>
|
||||
bool is_same(const Range<ItA> &A, const Range<ItB> &B)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
size_t s = A.size();
|
||||
|
||||
if (B.size() != s)
|
||||
ret = false;
|
||||
|
||||
size_t i = 0;
|
||||
auto itA = A.begin();
|
||||
auto itB = B.begin();
|
||||
for (; ret && i < s; ++itA, ++itB, ++i) {
|
||||
ret = ret &&
|
||||
get_mesh(*itA) == get_mesh(*itB) &&
|
||||
get_operation(*itA) == get_operation(*itB) &&
|
||||
get_stack_operation(*itA) == get_stack_operation(*itB) &&
|
||||
get_transform(*itA).isApprox(get_transform(*itB));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // CSGCOPY_HPP
|
||||
92
src/libslic3r/CSGMesh/ModelToCSGMesh.hpp
Normal file
92
src/libslic3r/CSGMesh/ModelToCSGMesh.hpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#ifndef MODELTOCSGMESH_HPP
|
||||
#define MODELTOCSGMESH_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
#include "libslic3r/MeshSplitImpl.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// Flags to select which parts to export from Model into a csg part collection.
|
||||
// These flags can be chained with the | operator
|
||||
enum ModelParts {
|
||||
mpartsPositive = 1, // Include positive parts
|
||||
mpartsNegative = 2, // Include negative parts
|
||||
mpartsDrillHoles = 4, // Include drill holes
|
||||
mpartsDoSplits = 8, // Split each splitable mesh and export as a union of csg parts
|
||||
};
|
||||
|
||||
template<class OutIt>
|
||||
bool model_to_csgmesh(const ModelObject &mo,
|
||||
const Transform3d &trafo, // Applies to all exported parts
|
||||
OutIt out, // Output iterator
|
||||
// values of ModelParts OR-ed
|
||||
int parts_to_include = mpartsPositive
|
||||
)
|
||||
{
|
||||
bool do_positives = parts_to_include & mpartsPositive;
|
||||
bool do_negatives = parts_to_include & mpartsNegative;
|
||||
bool do_drillholes = parts_to_include & mpartsDrillHoles;
|
||||
bool do_splits = parts_to_include & mpartsDoSplits;
|
||||
bool has_splitable_volume = false;
|
||||
|
||||
for (const ModelVolume *vol : mo.volumes) {
|
||||
if (vol && vol->mesh_ptr() &&
|
||||
((do_positives && vol->is_model_part()) ||
|
||||
(do_negatives && vol->is_negative_volume()))) {
|
||||
|
||||
if (do_splits && its_is_splittable(vol->mesh().its)) {
|
||||
CSGPart part_begin{{}, vol->is_model_part() ? CSGType::Union : CSGType::Difference};
|
||||
part_begin.stack_operation = CSGStackOp::Push;
|
||||
*out = std::move(part_begin);
|
||||
++out;
|
||||
|
||||
its_split(vol->mesh().its, SplitOutputFn{[&out, &vol, &trafo](indexed_triangle_set &&its) {
|
||||
if (its.empty())
|
||||
return;
|
||||
|
||||
CSGPart part{std::make_unique<indexed_triangle_set>(std::move(its)),
|
||||
CSGType::Union,
|
||||
(trafo * vol->get_matrix()).cast<float>()};
|
||||
|
||||
*out = std::move(part);
|
||||
++out;
|
||||
}});
|
||||
|
||||
CSGPart part_end{{}};
|
||||
part_end.stack_operation = CSGStackOp::Pop;
|
||||
*out = std::move(part_end);
|
||||
++out;
|
||||
has_splitable_volume = true;
|
||||
} else {
|
||||
CSGPart part{&(vol->mesh().its),
|
||||
vol->is_model_part() ? CSGType::Union : CSGType::Difference,
|
||||
(trafo * vol->get_matrix()).cast<float>()};
|
||||
part.name = vol->name;
|
||||
*out = std::move(part);
|
||||
++out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if (do_drillholes) {
|
||||
// sla::DrainHoles drainholes = sla::transformed_drainhole_points(mo, trafo);
|
||||
|
||||
// for (const sla::DrainHole &dhole : drainholes) {
|
||||
// CSGPart part{std::make_unique<const indexed_triangle_set>(
|
||||
// dhole.to_mesh()),
|
||||
// CSGType::Difference};
|
||||
|
||||
// *out = std::move(part);
|
||||
// ++out;
|
||||
// }
|
||||
//}
|
||||
|
||||
return has_splitable_volume;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // MODELTOCSGMESH_HPP
|
||||
382
src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp
Normal file
382
src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp
Normal file
@@ -0,0 +1,382 @@
|
||||
#ifndef PERFORMCSGMESHBOOLEANS_HPP
|
||||
#define PERFORMCSGMESHBOOLEANS_HPP
|
||||
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
||||
//#include "libslic3r/Execution/ExecutionSeq.hpp"
|
||||
#include "libslic3r/MeshBoolean.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
enum class BooleanFailReason { OK, MeshEmpty, NotBoundAVolume, SelfIntersect, NoIntersection};
|
||||
|
||||
// This method can be overriden when a specific CSGPart type supports caching
|
||||
// of the voxel grid
|
||||
template<class CSGPartT>
|
||||
MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart)
|
||||
{
|
||||
const indexed_triangle_set *its = csg::get_mesh(csgpart);
|
||||
indexed_triangle_set dummy;
|
||||
|
||||
if (!its)
|
||||
its = &dummy;
|
||||
|
||||
MeshBoolean::cgal::CGALMeshPtr ret;
|
||||
|
||||
indexed_triangle_set m = *its;
|
||||
its_transform(m, get_transform(csgpart), true);
|
||||
|
||||
try {
|
||||
ret = MeshBoolean::cgal::triangle_mesh_to_cgal(m);
|
||||
} catch (...) {
|
||||
// errors are ignored, simply return null
|
||||
ret = nullptr;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// This method can be overriden when a specific CSGPart type supports caching
|
||||
// of the voxel grid
|
||||
template<class CSGPartT>
|
||||
MeshBoolean::mcut::McutMeshPtr get_mcutmesh(const CSGPartT& csgpart)
|
||||
{
|
||||
const indexed_triangle_set* its = csg::get_mesh(csgpart);
|
||||
indexed_triangle_set dummy;
|
||||
|
||||
if (!its)
|
||||
its = &dummy;
|
||||
|
||||
MeshBoolean::mcut::McutMeshPtr ret;
|
||||
|
||||
indexed_triangle_set m = *its;
|
||||
its_transform(m, get_transform(csgpart), true);
|
||||
|
||||
try {
|
||||
ret = MeshBoolean::mcut::triangle_mesh_to_mcut(m);
|
||||
}
|
||||
catch (...) {
|
||||
// errors are ignored, simply return null
|
||||
ret = nullptr;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace detail_cgal {
|
||||
|
||||
using MeshBoolean::cgal::CGALMeshPtr;
|
||||
|
||||
inline void perform_csg(CSGType op, CGALMeshPtr &dst, CGALMeshPtr &src)
|
||||
{
|
||||
if (!dst && op == CSGType::Union && src) {
|
||||
dst = std::move(src);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dst || !src)
|
||||
return;
|
||||
|
||||
switch (op) {
|
||||
case CSGType::Union:
|
||||
MeshBoolean::cgal::plus(*dst, *src);
|
||||
break;
|
||||
case CSGType::Difference:
|
||||
MeshBoolean::cgal::minus(*dst, *src);
|
||||
break;
|
||||
case CSGType::Intersection:
|
||||
MeshBoolean::cgal::intersect(*dst, *src);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Ex, class It>
|
||||
std::vector<CGALMeshPtr> get_cgalptrs(Ex policy, const Range<It> &csgrange)
|
||||
{
|
||||
std::vector<CGALMeshPtr> ret(csgrange.size());
|
||||
execution::for_each(policy, size_t(0), csgrange.size(),
|
||||
[&csgrange, &ret](size_t i) {
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
auto &csgpart = *it;
|
||||
ret[i] = get_cgalmesh(csgpart);
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
namespace detail_mcut {
|
||||
|
||||
using MeshBoolean::mcut::McutMeshPtr;
|
||||
|
||||
inline void perform_csg(CSGType op, McutMeshPtr& dst, McutMeshPtr& src)
|
||||
{
|
||||
if (!dst && op == CSGType::Union && src) {
|
||||
dst = std::move(src);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dst || !src)
|
||||
return;
|
||||
|
||||
switch (op) {
|
||||
case CSGType::Union:
|
||||
MeshBoolean::mcut::do_boolean(*dst, *src,"UNION");
|
||||
break;
|
||||
case CSGType::Difference:
|
||||
MeshBoolean::mcut::do_boolean(*dst, *src,"A_NOT_B");
|
||||
break;
|
||||
case CSGType::Intersection:
|
||||
MeshBoolean::mcut::do_boolean(*dst, *src,"INTERSECTION");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Ex, class It>
|
||||
std::vector<McutMeshPtr> get_mcutptrs(Ex policy, const Range<It>& csgrange)
|
||||
{
|
||||
std::vector<McutMeshPtr> ret(csgrange.size());
|
||||
execution::for_each(policy, size_t(0), csgrange.size(),
|
||||
[&csgrange, &ret](size_t i) {
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
auto& csgpart = *it;
|
||||
ret[i] = get_mcutmesh(csgpart);
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace mcut_detail
|
||||
|
||||
// Process the sequence of CSG parts with CGAL.
|
||||
template<class It>
|
||||
void perform_csgmesh_booleans_cgal(MeshBoolean::cgal::CGALMeshPtr &cgalm,
|
||||
const Range<It> &csgrange)
|
||||
{
|
||||
using MeshBoolean::cgal::CGALMesh;
|
||||
using MeshBoolean::cgal::CGALMeshPtr;
|
||||
using namespace detail_cgal;
|
||||
|
||||
struct Frame {
|
||||
CSGType op; CGALMeshPtr cgalptr;
|
||||
explicit Frame(CSGType csgop = CSGType::Union)
|
||||
: op{ csgop }
|
||||
, cgalptr{ MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{}) }
|
||||
{}
|
||||
};
|
||||
|
||||
std::stack opstack{ std::vector<Frame>{} };
|
||||
|
||||
opstack.push(Frame{});
|
||||
|
||||
std::vector<CGALMeshPtr> cgalmeshes = get_cgalptrs(ex_tbb, csgrange);
|
||||
|
||||
size_t csgidx = 0;
|
||||
for (auto& csgpart : csgrange) {
|
||||
|
||||
auto op = get_operation(csgpart);
|
||||
CGALMeshPtr& cgalptr = cgalmeshes[csgidx++];
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
|
||||
opstack.push(Frame{ op });
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame* top = &opstack.top();
|
||||
|
||||
perform_csg(get_operation(csgpart), top->cgalptr, cgalptr);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
|
||||
CGALMeshPtr src = std::move(top->cgalptr);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
CGALMeshPtr& dst = opstack.top().cgalptr;
|
||||
perform_csg(popop, dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
cgalm = std::move(opstack.top().cgalptr);
|
||||
}
|
||||
|
||||
// Process the sequence of CSG parts with mcut.
|
||||
template<class It>
|
||||
void perform_csgmesh_booleans_mcut(MeshBoolean::mcut::McutMeshPtr& mcutm,
|
||||
const Range<It>& csgrange)
|
||||
{
|
||||
using MeshBoolean::mcut::McutMesh;
|
||||
using MeshBoolean::mcut::McutMeshPtr;
|
||||
using namespace detail_mcut;
|
||||
|
||||
struct Frame {
|
||||
CSGType op; McutMeshPtr mcutptr;
|
||||
explicit Frame(CSGType csgop = CSGType::Union)
|
||||
: op{ csgop }
|
||||
, mcutptr{ MeshBoolean::mcut::triangle_mesh_to_mcut(indexed_triangle_set{}) }
|
||||
{}
|
||||
};
|
||||
|
||||
std::stack opstack{ std::vector<Frame>{} };
|
||||
|
||||
opstack.push(Frame{});
|
||||
|
||||
std::vector<McutMeshPtr> McutMeshes = get_mcutptrs(ex_tbb, csgrange);
|
||||
|
||||
size_t csgidx = 0;
|
||||
for (auto& csgpart : csgrange) {
|
||||
|
||||
auto op = get_operation(csgpart);
|
||||
McutMeshPtr& mcutptr = McutMeshes[csgidx++];
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
|
||||
opstack.push(Frame{ op });
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame* top = &opstack.top();
|
||||
|
||||
perform_csg(get_operation(csgpart), top->mcutptr, mcutptr);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
|
||||
McutMeshPtr src = std::move(top->mcutptr);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
McutMeshPtr& dst = opstack.top().mcutptr;
|
||||
perform_csg(popop, dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
mcutm = std::move(opstack.top().mcutptr);
|
||||
|
||||
}
|
||||
|
||||
|
||||
template<class It, class Visitor>
|
||||
std::tuple<BooleanFailReason,std::string> check_csgmesh_booleans(const Range<It> &csgrange, Visitor &&vfn)
|
||||
{
|
||||
using namespace detail_cgal;
|
||||
BooleanFailReason fail_reason = BooleanFailReason::OK;
|
||||
std::string fail_part_name;
|
||||
std::vector<CGALMeshPtr> cgalmeshes(csgrange.size());
|
||||
auto check_part = [&csgrange, &cgalmeshes,&fail_reason,&fail_part_name](size_t i)
|
||||
{
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
auto &csgpart = *it;
|
||||
auto m = get_cgalmesh(csgpart);
|
||||
|
||||
// mesh can be nullptr if this is a stack push or pull
|
||||
if (!get_mesh(csgpart) && get_stack_operation(csgpart) != CSGStackOp::Continue) {
|
||||
cgalmeshes[i] = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!m || MeshBoolean::cgal::empty(*m)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh " << i << "/" << csgrange.size() << " is empty, cannot do boolean!";
|
||||
fail_reason= BooleanFailReason::MeshEmpty;
|
||||
fail_part_name = csgpart.name;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MeshBoolean::cgal::does_bound_a_volume(*m)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh "<<i<<"/"<<csgrange.size()<<" does_bound_a_volume is false, cannot do boolean!";
|
||||
fail_reason= BooleanFailReason::NotBoundAVolume;
|
||||
fail_part_name = csgpart.name;
|
||||
return;
|
||||
}
|
||||
|
||||
if (MeshBoolean::cgal::does_self_intersect(*m)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "check_csgmesh_booleans fails! mesh " << i << "/" << csgrange.size() << " does_self_intersect is true, cannot do boolean!";
|
||||
fail_reason= BooleanFailReason::SelfIntersect;
|
||||
fail_part_name = csgpart.name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (...) { return; }
|
||||
|
||||
cgalmeshes[i] = std::move(m);
|
||||
};
|
||||
execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part);
|
||||
|
||||
//It ret = csgrange.end();
|
||||
//for (size_t i = 0; i < csgrange.size(); ++i) {
|
||||
// if (!cgalmeshes[i]) {
|
||||
// auto it = csgrange.begin();
|
||||
// std::advance(it, i);
|
||||
// vfn(it);
|
||||
|
||||
// if (ret == csgrange.end())
|
||||
// ret = it;
|
||||
// }
|
||||
//}
|
||||
|
||||
return { fail_reason,fail_part_name };
|
||||
}
|
||||
|
||||
template<class It>
|
||||
std::tuple<BooleanFailReason, std::string> check_csgmesh_booleans(const Range<It> &csgrange, bool use_mcut=false)
|
||||
{
|
||||
if(!use_mcut)
|
||||
return check_csgmesh_booleans(csgrange, [](auto &) {});
|
||||
else {
|
||||
using namespace detail_mcut;
|
||||
BooleanFailReason fail_reason = BooleanFailReason::OK;
|
||||
std::string fail_part_name;
|
||||
|
||||
std::vector<McutMeshPtr> McutMeshes(csgrange.size());
|
||||
auto check_part = [&csgrange, &McutMeshes,&fail_reason,&fail_part_name](size_t i) {
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
auto& csgpart = *it;
|
||||
auto m = get_mcutmesh(csgpart);
|
||||
|
||||
// mesh can be nullptr if this is a stack push or pull
|
||||
if (!get_mesh(csgpart) && get_stack_operation(csgpart) != CSGStackOp::Continue) {
|
||||
McutMeshes[i] = MeshBoolean::mcut::triangle_mesh_to_mcut(indexed_triangle_set{});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!m || MeshBoolean::mcut::empty(*m)) {
|
||||
fail_reason=BooleanFailReason::MeshEmpty;
|
||||
fail_part_name = csgpart.name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (...) { return; }
|
||||
|
||||
McutMeshes[i] = std::move(m);
|
||||
};
|
||||
execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part);
|
||||
return { fail_reason,fail_part_name };
|
||||
}
|
||||
}
|
||||
|
||||
template<class It>
|
||||
MeshBoolean::cgal::CGALMeshPtr perform_csgmesh_booleans(const Range<It> &csgparts)
|
||||
{
|
||||
auto ret = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{});
|
||||
if (ret)
|
||||
perform_csgmesh_booleans_cgal(ret, csgparts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class It>
|
||||
MeshBoolean::mcut::McutMeshPtr perform_csgmesh_booleans_mcut(const Range<It>& csgparts)
|
||||
{
|
||||
auto ret = MeshBoolean::mcut::triangle_mesh_to_mcut(indexed_triangle_set{});
|
||||
if (ret)
|
||||
perform_csgmesh_booleans_mcut(ret, csgparts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace csg
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // PERFORMCSGMESHBOOLEANS_HPP
|
||||
131
src/libslic3r/CSGMesh/SliceCSGMesh.hpp
Normal file
131
src/libslic3r/CSGMesh/SliceCSGMesh.hpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#ifndef SLICECSGMESH_HPP
|
||||
#define SLICECSGMESH_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include "libslic3r/TriangleMeshSlicer.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline void merge_slices(csg::CSGType op, size_t i,
|
||||
std::vector<ExPolygons> &target,
|
||||
std::vector<ExPolygons> &source)
|
||||
{
|
||||
switch(op) {
|
||||
case CSGType::Union:
|
||||
for (ExPolygon &expoly : source[i])
|
||||
target[i].emplace_back(std::move(expoly));
|
||||
break;
|
||||
case CSGType::Difference:
|
||||
target[i] = diff_ex(target[i], source[i]);
|
||||
break;
|
||||
case CSGType::Intersection:
|
||||
target[i] = intersection_ex(target[i], source[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void collect_nonempty_indices(csg::CSGType op,
|
||||
const std::vector<float> &slicegrid,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
std::vector<size_t> &indices)
|
||||
{
|
||||
indices.clear();
|
||||
for (size_t i = 0; i < slicegrid.size(); ++i) {
|
||||
if (op == CSGType::Intersection || !slices[i].empty())
|
||||
indices.emplace_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template<class ItCSG>
|
||||
std::vector<ExPolygons> slice_csgmesh_ex(
|
||||
const Range<ItCSG> &csgrange,
|
||||
const std::vector<float> &slicegrid,
|
||||
const MeshSlicingParamsEx ¶ms,
|
||||
const std::function<void()> &throw_on_cancel = [] {})
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
struct Frame { CSGType op; std::vector<ExPolygons> slices; };
|
||||
|
||||
std::stack opstack{std::vector<Frame>{}};
|
||||
|
||||
MeshSlicingParamsEx params_cpy = params;
|
||||
auto trafo = params.trafo;
|
||||
auto nonempty_indices = reserve_vector<size_t>(slicegrid.size());
|
||||
|
||||
opstack.push({CSGType::Union, std::vector<ExPolygons>(slicegrid.size())});
|
||||
|
||||
for (const auto &csgpart : csgrange) {
|
||||
const indexed_triangle_set *its = csg::get_mesh(csgpart);
|
||||
|
||||
auto op = get_operation(csgpart);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
|
||||
opstack.push({op, std::vector<ExPolygons>(slicegrid.size())});
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame *top = &opstack.top();
|
||||
|
||||
if (its) {
|
||||
params_cpy.trafo = trafo * csg::get_transform(csgpart).template cast<double>();
|
||||
std::vector<ExPolygons> slices = slice_mesh_ex(*its,
|
||||
slicegrid, params_cpy,
|
||||
throw_on_cancel);
|
||||
|
||||
assert(slices.size() == slicegrid.size());
|
||||
|
||||
collect_nonempty_indices(op, slicegrid, slices, nonempty_indices);
|
||||
|
||||
execution::for_each(
|
||||
ex_tbb, nonempty_indices.begin(), nonempty_indices.end(),
|
||||
[op, &slices, &top](size_t i) {
|
||||
merge_slices(op, i, top->slices, slices);
|
||||
}, execution::max_concurrency(ex_tbb));
|
||||
}
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
|
||||
std::vector<ExPolygons> popslices = std::move(top->slices);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
std::vector<ExPolygons> &prev_slices = opstack.top().slices;
|
||||
|
||||
collect_nonempty_indices(popop, slicegrid, popslices, nonempty_indices);
|
||||
|
||||
execution::for_each(
|
||||
ex_tbb, nonempty_indices.begin(), nonempty_indices.end(),
|
||||
[&popslices, &prev_slices, popop](size_t i) {
|
||||
merge_slices(popop, i, prev_slices, popslices);
|
||||
}, execution::max_concurrency(ex_tbb));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> ret = std::move(opstack.top().slices);
|
||||
|
||||
// TODO: verify if this part can be omitted or not.
|
||||
execution::for_each(ex_tbb, ret.begin(), ret.end(), [](ExPolygons &slice) {
|
||||
auto it = std::remove_if(slice.begin(), slice.end(), [](const ExPolygon &p){
|
||||
return p.area() < double(SCALED_EPSILON) * double(SCALED_EPSILON);
|
||||
});
|
||||
|
||||
// Hopefully, ExPolygons are moved, not copied to new positions
|
||||
// and that is cheap for expolygons
|
||||
slice.erase(it, slice.end());
|
||||
slice = union_ex(slice);
|
||||
}, execution::max_concurrency(ex_tbb));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // SLICECSGMESH_HPP
|
||||
95
src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp
Normal file
95
src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#ifndef TRIANGLEMESHADAPTER_HPP
|
||||
#define TRIANGLEMESHADAPTER_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// Provide default overloads for indexed_triangle_set to be usable as a plain
|
||||
// CSGPart with an implicit union operation
|
||||
|
||||
inline CSGType get_operation(const indexed_triangle_set &part)
|
||||
{
|
||||
return CSGType::Union;
|
||||
}
|
||||
|
||||
inline CSGStackOp get_stack_operation(const indexed_triangle_set &part)
|
||||
{
|
||||
return CSGStackOp::Continue;
|
||||
}
|
||||
|
||||
inline const indexed_triangle_set * get_mesh(const indexed_triangle_set &part)
|
||||
{
|
||||
return ∂
|
||||
}
|
||||
|
||||
inline Transform3f get_transform(const indexed_triangle_set &part)
|
||||
{
|
||||
return Transform3f::Identity();
|
||||
}
|
||||
|
||||
inline CSGType get_operation(const indexed_triangle_set *const part)
|
||||
{
|
||||
return CSGType::Union;
|
||||
}
|
||||
|
||||
inline CSGStackOp get_stack_operation(const indexed_triangle_set *const part)
|
||||
{
|
||||
return CSGStackOp::Continue;
|
||||
}
|
||||
|
||||
inline const indexed_triangle_set * get_mesh(const indexed_triangle_set *const part)
|
||||
{
|
||||
return part;
|
||||
}
|
||||
|
||||
inline Transform3f get_transform(const indexed_triangle_set *const part)
|
||||
{
|
||||
return Transform3f::Identity();
|
||||
}
|
||||
|
||||
inline CSGType get_operation(const TriangleMesh &part)
|
||||
{
|
||||
return CSGType::Union;
|
||||
}
|
||||
|
||||
inline CSGStackOp get_stack_operation(const TriangleMesh &part)
|
||||
{
|
||||
return CSGStackOp::Continue;
|
||||
}
|
||||
|
||||
inline const indexed_triangle_set * get_mesh(const TriangleMesh &part)
|
||||
{
|
||||
return &part.its;
|
||||
}
|
||||
|
||||
inline Transform3f get_transform(const TriangleMesh &part)
|
||||
{
|
||||
return Transform3f::Identity();
|
||||
}
|
||||
|
||||
inline CSGType get_operation(const TriangleMesh * const part)
|
||||
{
|
||||
return CSGType::Union;
|
||||
}
|
||||
|
||||
inline CSGStackOp get_stack_operation(const TriangleMesh * const part)
|
||||
{
|
||||
return CSGStackOp::Continue;
|
||||
}
|
||||
|
||||
inline const indexed_triangle_set * get_mesh(const TriangleMesh * const part)
|
||||
{
|
||||
return &part->its;
|
||||
}
|
||||
|
||||
inline Transform3f get_transform(const TriangleMesh * const part)
|
||||
{
|
||||
return Transform3f::Identity();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // TRIANGLEMESHADAPTER_HPP
|
||||
116
src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp
Normal file
116
src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp
Normal file
@@ -0,0 +1,116 @@
|
||||
#ifndef VOXELIZECSGMESH_HPP
|
||||
#define VOXELIZECSGMESH_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <stack>
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
#include "libslic3r/OpenVDBUtils.hpp"
|
||||
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
using VoxelizeParams = MeshToGridParams;
|
||||
|
||||
// This method can be overriden when a specific CSGPart type supports caching
|
||||
// of the voxel grid
|
||||
template<class CSGPartT>
|
||||
VoxelGridPtr get_voxelgrid(const CSGPartT &csgpart, VoxelizeParams params)
|
||||
{
|
||||
const indexed_triangle_set *its = csg::get_mesh(csgpart);
|
||||
VoxelGridPtr ret;
|
||||
|
||||
params.trafo(params.trafo() * csg::get_transform(csgpart));
|
||||
|
||||
if (its)
|
||||
ret = mesh_to_grid(*its, params);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline void perform_csg(CSGType op, VoxelGridPtr &dst, VoxelGridPtr &src)
|
||||
{
|
||||
if (!dst || !src)
|
||||
return;
|
||||
|
||||
switch (op) {
|
||||
case CSGType::Union:
|
||||
if (is_grid_empty(*dst) && !is_grid_empty(*src))
|
||||
dst = clone(*src);
|
||||
else
|
||||
grid_union(*dst, *src);
|
||||
|
||||
break;
|
||||
case CSGType::Difference:
|
||||
grid_difference(*dst, *src);
|
||||
break;
|
||||
case CSGType::Intersection:
|
||||
grid_intersection(*dst, *src);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template<class It>
|
||||
VoxelGridPtr voxelize_csgmesh(const Range<It> &csgrange,
|
||||
const VoxelizeParams ¶ms = {})
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
VoxelGridPtr ret;
|
||||
|
||||
std::vector<VoxelGridPtr> grids (csgrange.size());
|
||||
|
||||
execution::for_each(ex_tbb, size_t(0), csgrange.size(), [&](size_t csgidx) {
|
||||
if (params.statusfn() && params.statusfn()(-1))
|
||||
return;
|
||||
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, csgidx);
|
||||
auto &csgpart = *it;
|
||||
grids[csgidx] = get_voxelgrid(csgpart, params);
|
||||
}, execution::max_concurrency(ex_tbb));
|
||||
|
||||
size_t csgidx = 0;
|
||||
struct Frame { CSGType op = CSGType::Union; VoxelGridPtr grid; };
|
||||
std::stack opstack{std::vector<Frame>{}};
|
||||
|
||||
opstack.push({CSGType::Union, mesh_to_grid({}, params)});
|
||||
|
||||
for (auto &csgpart : csgrange) {
|
||||
if (params.statusfn() && params.statusfn()(-1))
|
||||
break;
|
||||
|
||||
auto &partgrid = grids[csgidx++];
|
||||
|
||||
auto op = get_operation(csgpart);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
|
||||
opstack.push({op, mesh_to_grid({}, params)});
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame *top = &opstack.top();
|
||||
|
||||
perform_csg(get_operation(csgpart), top->grid, partgrid);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
|
||||
VoxelGridPtr popgrid = std::move(top->grid);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
VoxelGridPtr &grid = opstack.top().grid;
|
||||
perform_csg(popop, grid, popgrid);
|
||||
}
|
||||
}
|
||||
|
||||
ret = std::move(opstack.top().grid);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // VOXELIZECSGMESH_HPP
|
||||
779
src/libslic3r/Calib.cpp
Normal file
779
src/libslic3r/Calib.cpp
Normal file
@@ -0,0 +1,779 @@
|
||||
#include "Calib.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include <cmath>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
float CalibPressureAdvance::find_optimal_PA_speed(const DynamicPrintConfig &config, double line_width, double layer_height, int filament_idx)
|
||||
{
|
||||
const double general_suggested_min_speed = 100.0;
|
||||
double filament_max_volumetric_speed = config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->get_at(0);
|
||||
Flow pattern_line = Flow(line_width, layer_height, config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(0));
|
||||
auto pa_speed = std::min(std::max(general_suggested_min_speed, config.option<ConfigOptionFloat>("outer_wall_speed")->value),
|
||||
filament_max_volumetric_speed / pattern_line.mm3_per_mm());
|
||||
|
||||
return std::floor(pa_speed);
|
||||
}
|
||||
|
||||
std::string CalibPressureAdvance::move_to(Vec2d pt, GCodeWriter &writer, std::string comment)
|
||||
{
|
||||
std::stringstream gcode;
|
||||
|
||||
gcode << writer.retract();
|
||||
gcode << writer.travel_to_xy(pt, comment);
|
||||
gcode << writer.unretract();
|
||||
|
||||
m_last_pos = Vec3d(pt.x(), pt.y(), 0);
|
||||
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
double CalibPressureAdvance::e_per_mm(double line_width, double layer_height, float nozzle_diameter, float filament_diameter, float print_flow_ratio) const
|
||||
{
|
||||
const Flow line_flow = Flow(line_width, layer_height, nozzle_diameter);
|
||||
const double filament_area = M_PI * std::pow(filament_diameter / 2, 2);
|
||||
|
||||
return line_flow.mm3_per_mm() / filament_area * print_flow_ratio;
|
||||
}
|
||||
|
||||
std::string CalibPressureAdvance::convert_number_to_string(double num) const
|
||||
{
|
||||
auto sNumber = std::to_string(num);
|
||||
sNumber.erase(sNumber.find_last_not_of('0') + 1, std::string::npos);
|
||||
sNumber.erase(sNumber.find_last_not_of('.') + 1, std::string::npos);
|
||||
|
||||
return sNumber;
|
||||
}
|
||||
|
||||
std::string CalibPressureAdvance::draw_digit(
|
||||
double startx, double starty, char c, CalibPressureAdvance::DrawDigitMode mode, double line_width, double e_per_mm, GCodeWriter &writer)
|
||||
{
|
||||
const double len = m_digit_segment_len;
|
||||
const double gap = line_width / 2.0;
|
||||
|
||||
const auto dE = e_per_mm * len;
|
||||
const auto two_dE = dE * 2;
|
||||
|
||||
Vec2d p0, p1, p2, p3, p4, p5;
|
||||
Vec2d p0_5, p4_5;
|
||||
Vec2d gap_p0_toward_p3, gap_p2_toward_p3;
|
||||
Vec2d dot_direction;
|
||||
|
||||
if (mode == CalibPressureAdvance::DrawDigitMode::Bottom_To_Top) {
|
||||
// 1-------2-------5
|
||||
// | | |
|
||||
// | | |
|
||||
// 0-------3-------4
|
||||
p0 = Vec2d(startx, starty);
|
||||
p0_5 = Vec2d(startx, starty + len / 2);
|
||||
p1 = Vec2d(startx, starty + len);
|
||||
p2 = Vec2d(startx + len, starty + len);
|
||||
p3 = Vec2d(startx + len, starty);
|
||||
p4 = Vec2d(startx + len * 2, starty);
|
||||
p4_5 = Vec2d(startx + len * 2, starty + len / 2);
|
||||
p5 = Vec2d(startx + len * 2, starty + len);
|
||||
|
||||
gap_p0_toward_p3 = p0 + Vec2d(gap, 0);
|
||||
gap_p2_toward_p3 = p2 + Vec2d(0, gap);
|
||||
|
||||
dot_direction = Vec2d(-len / 2, 0);
|
||||
} else {
|
||||
// 0-------1
|
||||
// | |
|
||||
// 3-------2
|
||||
// | |
|
||||
// 4-------5
|
||||
p0 = Vec2d(startx, starty);
|
||||
p0_5 = Vec2d(startx + len / 2, starty);
|
||||
p1 = Vec2d(startx + len, starty);
|
||||
p2 = Vec2d(startx + len, starty - len);
|
||||
p3 = Vec2d(startx, starty - len);
|
||||
p4 = Vec2d(startx, starty - len * 2);
|
||||
p4_5 = Vec2d(startx + len / 2, starty - len * 2);
|
||||
p5 = Vec2d(startx + len, starty - len * 2);
|
||||
|
||||
gap_p0_toward_p3 = p0 - Vec2d(0, gap);
|
||||
gap_p2_toward_p3 = p2 - Vec2d(gap, 0);
|
||||
|
||||
dot_direction = Vec2d(0, len / 2);
|
||||
}
|
||||
|
||||
std::stringstream gcode;
|
||||
|
||||
switch (c) {
|
||||
case '0':
|
||||
gcode << move_to(p0, writer, "Glyph: 0");
|
||||
gcode << writer.extrude_to_xy(p1, dE);
|
||||
gcode << writer.extrude_to_xy(p5, two_dE);
|
||||
gcode << writer.extrude_to_xy(p4, dE);
|
||||
gcode << writer.extrude_to_xy(gap_p0_toward_p3, two_dE);
|
||||
break;
|
||||
case '1':
|
||||
gcode << move_to(p0_5, writer, "Glyph: 1");
|
||||
gcode << writer.extrude_to_xy(p4_5, two_dE);
|
||||
break;
|
||||
case '2':
|
||||
gcode << move_to(p0, writer, "Glyph: 2");
|
||||
gcode << writer.extrude_to_xy(p1, dE);
|
||||
gcode << writer.extrude_to_xy(p2, dE);
|
||||
gcode << writer.extrude_to_xy(p3, dE);
|
||||
gcode << writer.extrude_to_xy(p4, dE);
|
||||
gcode << writer.extrude_to_xy(p5, dE);
|
||||
break;
|
||||
case '3':
|
||||
gcode << move_to(p0, writer, "Glyph: 3");
|
||||
gcode << writer.extrude_to_xy(p1, dE);
|
||||
gcode << writer.extrude_to_xy(p5, two_dE);
|
||||
gcode << writer.extrude_to_xy(p4, dE);
|
||||
gcode << move_to(gap_p2_toward_p3, writer);
|
||||
gcode << writer.extrude_to_xy(p3, dE);
|
||||
break;
|
||||
case '4':
|
||||
gcode << move_to(p0, writer, "Glyph: 4");
|
||||
gcode << writer.extrude_to_xy(p3, dE);
|
||||
gcode << writer.extrude_to_xy(p2, dE);
|
||||
gcode << move_to(p1, writer);
|
||||
gcode << writer.extrude_to_xy(p5, two_dE);
|
||||
break;
|
||||
case '5':
|
||||
gcode << move_to(p1, writer, "Glyph: 5");
|
||||
gcode << writer.extrude_to_xy(p0, dE);
|
||||
gcode << writer.extrude_to_xy(p3, dE);
|
||||
gcode << writer.extrude_to_xy(p2, dE);
|
||||
gcode << writer.extrude_to_xy(p5, dE);
|
||||
gcode << writer.extrude_to_xy(p4, dE);
|
||||
break;
|
||||
case '6':
|
||||
gcode << move_to(p1, writer, "Glyph: 6");
|
||||
gcode << writer.extrude_to_xy(p0, dE);
|
||||
gcode << writer.extrude_to_xy(p4, two_dE);
|
||||
gcode << writer.extrude_to_xy(p5, dE);
|
||||
gcode << writer.extrude_to_xy(p2, dE);
|
||||
gcode << writer.extrude_to_xy(p3, dE);
|
||||
break;
|
||||
case '7':
|
||||
gcode << move_to(p0, writer, "Glyph: 7");
|
||||
gcode << writer.extrude_to_xy(p1, dE);
|
||||
gcode << writer.extrude_to_xy(p5, two_dE);
|
||||
break;
|
||||
case '8':
|
||||
gcode << move_to(p2, writer, "Glyph: 8");
|
||||
gcode << writer.extrude_to_xy(p3, dE);
|
||||
gcode << writer.extrude_to_xy(p4, dE);
|
||||
gcode << writer.extrude_to_xy(p5, dE);
|
||||
gcode << writer.extrude_to_xy(p1, two_dE);
|
||||
gcode << writer.extrude_to_xy(p0, dE);
|
||||
gcode << writer.extrude_to_xy(p3, dE);
|
||||
break;
|
||||
case '9':
|
||||
gcode << move_to(p5, writer, "Glyph: 9");
|
||||
gcode << writer.extrude_to_xy(p1, two_dE);
|
||||
gcode << writer.extrude_to_xy(p0, dE);
|
||||
gcode << writer.extrude_to_xy(p3, dE);
|
||||
gcode << writer.extrude_to_xy(p2, dE);
|
||||
break;
|
||||
case '.':
|
||||
gcode << move_to(p4_5, writer, "Glyph: .");
|
||||
gcode << writer.extrude_to_xy(p4_5 + dot_direction, dE);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string CalibPressureAdvance::draw_number(
|
||||
double startx, double starty, double value, CalibPressureAdvance::DrawDigitMode mode, double line_width, double e_per_mm, double speed, GCodeWriter &writer)
|
||||
{
|
||||
auto sNumber = convert_number_to_string(value);
|
||||
std::stringstream gcode;
|
||||
gcode << writer.set_speed(speed);
|
||||
|
||||
for (std::string::size_type i = 0; i < sNumber.length(); ++i) {
|
||||
if (i > m_max_number_len) { break; }
|
||||
switch (mode) {
|
||||
case DrawDigitMode::Bottom_To_Top: gcode << draw_digit(startx, starty + i * number_spacing(), sNumber[i], mode, line_width, e_per_mm, writer); break;
|
||||
default: gcode << draw_digit(startx + i * number_spacing(), starty, sNumber[i], mode, line_width, e_per_mm, writer);
|
||||
}
|
||||
}
|
||||
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
double CalibPressureAdvance::get_distance(Vec2d from, Vec2d to) const { return std::hypot((to.x() - from.x()), (to.y() - from.y())); }
|
||||
|
||||
std::string CalibPressureAdvance::draw_line(GCodeWriter &writer, Vec2d to_pt, double line_width, double layer_height, double speed, const std::string &comment)
|
||||
{
|
||||
const double e_per_mm = CalibPressureAdvance::e_per_mm(line_width, layer_height, m_config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(0),
|
||||
m_config.option<ConfigOptionFloats>("filament_diameter")->get_at(0),
|
||||
m_config.option<ConfigOptionFloats>("filament_flow_ratio")->get_at(0));
|
||||
|
||||
const double length = get_distance(Vec2d(m_last_pos.x(), m_last_pos.y()), to_pt);
|
||||
auto dE = e_per_mm * length;
|
||||
|
||||
std::stringstream gcode;
|
||||
|
||||
gcode << writer.set_speed(speed);
|
||||
gcode << writer.extrude_to_xy(to_pt, dE, comment);
|
||||
|
||||
m_last_pos = Vec3d(to_pt.x(), to_pt.y(), 0);
|
||||
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string CalibPressureAdvance::draw_box(GCodeWriter &writer, double min_x, double min_y, double size_x, double size_y, DrawBoxOptArgs opt_args)
|
||||
{
|
||||
std::stringstream gcode;
|
||||
|
||||
double x = min_x;
|
||||
double y = min_y;
|
||||
const double max_x = min_x + size_x;
|
||||
const double max_y = min_y + size_y;
|
||||
|
||||
const double spacing = opt_args.line_width - opt_args.height * (1 - M_PI / 4);
|
||||
|
||||
// if number of perims exceeds size of box, reduce it to max
|
||||
const int max_perimeters = std::min(
|
||||
// this is the equivalent of number of perims for concentric fill
|
||||
std::floor(size_x * std::sin(to_radians(45))) / (spacing / std::sin(to_radians(45))),
|
||||
std::floor(size_y * std::sin(to_radians(45))) / (spacing / std::sin(to_radians(45))));
|
||||
|
||||
opt_args.num_perimeters = std::min(opt_args.num_perimeters, max_perimeters);
|
||||
|
||||
gcode << move_to(Vec2d(min_x, min_y), writer, "Move to box start");
|
||||
|
||||
// DrawLineOptArgs line_opt_args(*this);
|
||||
auto line_arg_height = opt_args.height;
|
||||
auto line_arg_line_width = opt_args.line_width;
|
||||
auto line_arg_speed = opt_args.speed;
|
||||
std::string comment = "";
|
||||
|
||||
for (int i = 0; i < opt_args.num_perimeters; ++i) {
|
||||
if (i != 0) { // after first perimeter, step inwards to start next perimeter
|
||||
x += spacing;
|
||||
y += spacing;
|
||||
gcode << move_to(Vec2d(x, y), writer, "Step inwards to print next perimeter");
|
||||
}
|
||||
|
||||
y += size_y - i * spacing * 2;
|
||||
comment = "Draw perimeter (up)";
|
||||
gcode << draw_line(writer, Vec2d(x, y), line_arg_line_width, line_arg_height, line_arg_speed, comment);
|
||||
|
||||
x += size_x - i * spacing * 2;
|
||||
comment = "Draw perimeter (right)";
|
||||
gcode << draw_line(writer, Vec2d(x, y), line_arg_line_width, line_arg_height, line_arg_speed, comment);
|
||||
|
||||
y -= size_y - i * spacing * 2;
|
||||
comment = "Draw perimeter (down)";
|
||||
gcode << draw_line(writer, Vec2d(x, y), line_arg_line_width, line_arg_height, line_arg_speed, comment);
|
||||
|
||||
x -= size_x - i * spacing * 2;
|
||||
comment = "Draw perimeter (left)";
|
||||
gcode << draw_line(writer, Vec2d(x, y), line_arg_line_width, line_arg_height, line_arg_speed, comment);
|
||||
}
|
||||
|
||||
if (!opt_args.is_filled) { return gcode.str(); }
|
||||
|
||||
// create box infill
|
||||
const double spacing_45 = spacing / std::sin(to_radians(45));
|
||||
|
||||
const double bound_modifier = (spacing * (opt_args.num_perimeters - 1)) + (opt_args.line_width * (1 - m_encroachment));
|
||||
const double x_min_bound = min_x + bound_modifier;
|
||||
const double x_max_bound = max_x - bound_modifier;
|
||||
const double y_min_bound = min_y + bound_modifier;
|
||||
const double y_max_bound = max_y - bound_modifier;
|
||||
const int x_count = std::floor((x_max_bound - x_min_bound) / spacing_45);
|
||||
const int y_count = std::floor((y_max_bound - y_min_bound) / spacing_45);
|
||||
|
||||
double x_remainder = std::fmod((x_max_bound - x_min_bound), spacing_45);
|
||||
double y_remainder = std::fmod((y_max_bound - y_min_bound), spacing_45);
|
||||
|
||||
x = x_min_bound;
|
||||
y = y_min_bound;
|
||||
|
||||
gcode << move_to(Vec2d(x, y), writer, "Move to fill start");
|
||||
|
||||
for (int i = 0; i < x_count + y_count + (x_remainder + y_remainder >= spacing_45 ? 1 : 0);
|
||||
++i) { // this isn't the most robust way, but less expensive than finding line intersections
|
||||
if (i < std::min(x_count, y_count)) {
|
||||
if (i % 2 == 0) {
|
||||
x += spacing_45;
|
||||
y = y_min_bound;
|
||||
gcode << move_to(Vec2d(x, y), writer, "Fill: Step right");
|
||||
|
||||
y += x - x_min_bound;
|
||||
x = x_min_bound;
|
||||
comment = "Fill: Print up/left";
|
||||
gcode << draw_line(writer, Vec2d(x, y), line_arg_line_width, line_arg_height, line_arg_speed, comment);
|
||||
} else {
|
||||
y += spacing_45;
|
||||
x = x_min_bound;
|
||||
gcode << move_to(Vec2d(x, y), writer, "Fill: Step up");
|
||||
|
||||
x += y - y_min_bound;
|
||||
y = y_min_bound;
|
||||
comment = "Fill: Print down/right";
|
||||
gcode << draw_line(writer, Vec2d(x, y), line_arg_line_width, line_arg_height, line_arg_speed, comment);
|
||||
}
|
||||
} else if (i < std::max(x_count, y_count)) {
|
||||
if (x_count > y_count) {
|
||||
// box is wider than tall
|
||||
if (i % 2 == 0) {
|
||||
x += spacing_45;
|
||||
y = y_min_bound;
|
||||
gcode << move_to(Vec2d(x, y), writer, "Fill: Step right");
|
||||
|
||||
x -= y_max_bound - y_min_bound;
|
||||
y = y_max_bound;
|
||||
comment = "Fill: Print up/left";
|
||||
gcode << draw_line(writer, Vec2d(x, y), line_arg_line_width, line_arg_height, line_arg_speed, comment);
|
||||
} else {
|
||||
if (i == y_count) {
|
||||
x += spacing_45 - y_remainder;
|
||||
y_remainder = 0;
|
||||
} else {
|
||||
x += spacing_45;
|
||||
}
|
||||
y = y_max_bound;
|
||||
gcode << move_to(Vec2d(x, y), writer, "Fill: Step right");
|
||||
|
||||
x += y_max_bound - y_min_bound;
|
||||
y = y_min_bound;
|
||||
comment = "Fill: Print down/right";
|
||||
gcode << draw_line(writer, Vec2d(x, y), line_arg_line_width, line_arg_height, line_arg_speed, comment);
|
||||
}
|
||||
} else {
|
||||
// box is taller than wide
|
||||
if (i % 2 == 0) {
|
||||
x = x_max_bound;
|
||||
if (i == x_count) {
|
||||
y += spacing_45 - x_remainder;
|
||||
x_remainder = 0;
|
||||
} else {
|
||||
y += spacing_45;
|
||||
}
|
||||
gcode << move_to(Vec2d(x, y), writer, "Fill: Step up");
|
||||
|
||||
x = x_min_bound;
|
||||
y += x_max_bound - x_min_bound;
|
||||
comment = "Fill: Print up/left";
|
||||
gcode << draw_line(writer, Vec2d(x, y), line_arg_line_width, line_arg_height, line_arg_speed, comment);
|
||||
} else {
|
||||
x = x_min_bound;
|
||||
y += spacing_45;
|
||||
gcode << move_to(Vec2d(x, y), writer, "Fill: Step up");
|
||||
|
||||
x = x_max_bound;
|
||||
y -= x_max_bound - x_min_bound;
|
||||
comment = "Fill: Print down/right";
|
||||
gcode << draw_line(writer, Vec2d(x, y), line_arg_line_width, line_arg_height, line_arg_speed, comment);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (i % 2 == 0) {
|
||||
x = x_max_bound;
|
||||
if (i == x_count) {
|
||||
y += spacing_45 - x_remainder;
|
||||
} else {
|
||||
y += spacing_45;
|
||||
}
|
||||
gcode << move_to(Vec2d(x, y), writer, "Fill: Step up");
|
||||
|
||||
x -= y_max_bound - y;
|
||||
y = y_max_bound;
|
||||
comment = "Fill: Print up/left";
|
||||
gcode << draw_line(writer, Vec2d(x, y), line_arg_line_width, line_arg_height, line_arg_speed, comment);
|
||||
} else {
|
||||
if (i == y_count) {
|
||||
x += spacing_45 - y_remainder;
|
||||
} else {
|
||||
x += spacing_45;
|
||||
}
|
||||
y = y_max_bound;
|
||||
gcode << move_to(Vec2d(x, y), writer, "Fill: Step right");
|
||||
|
||||
y -= x_max_bound - x;
|
||||
x = x_max_bound;
|
||||
comment = "Fill: Print down/right";
|
||||
gcode << draw_line(writer, Vec2d(x, y), line_arg_line_width, line_arg_height, line_arg_speed, comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gcode.str();
|
||||
}
|
||||
CalibPressureAdvanceLine::CalibPressureAdvanceLine(GCode *gcodegen)
|
||||
: CalibPressureAdvance(gcodegen->config()), mp_gcodegen(gcodegen), m_nozzle_diameter(gcodegen->config().nozzle_diameter.get_at(0))
|
||||
{
|
||||
m_line_width = m_nozzle_diameter < 0.51 ? m_nozzle_diameter * 1.5 : m_nozzle_diameter * 1.05;
|
||||
m_height_layer = gcodegen->config().initial_layer_print_height;
|
||||
m_number_line_width = m_thin_line_width = m_nozzle_diameter;
|
||||
};
|
||||
|
||||
std::string CalibPressureAdvanceLine::generate_test(double start_pa /*= 0*/, double step_pa /*= 0.002*/, int count /*= 10*/)
|
||||
{
|
||||
BoundingBoxf bed_ext = get_extents(mp_gcodegen->config().printable_area.values);
|
||||
if (is_delta()) { CalibPressureAdvanceLine::delta_scale_bed_ext(bed_ext); }
|
||||
|
||||
auto bed_sizes = mp_gcodegen->config().printable_area.values;
|
||||
const auto &w = bed_ext.size().x();
|
||||
const auto &h = bed_ext.size().y();
|
||||
count = std::min(count, int((h - 10) / m_space_y));
|
||||
|
||||
m_length_long = 40 + std::min(w - 120.0, 0.0);
|
||||
|
||||
auto startx = bed_ext.min.x() + (w - m_length_short * 2 - m_length_long - 20) / 2;
|
||||
auto starty = bed_ext.min.y() + (h - count * m_space_y) / 2;
|
||||
|
||||
return print_pa_lines(startx, starty, start_pa, step_pa, count);
|
||||
}
|
||||
|
||||
bool CalibPressureAdvanceLine::is_delta() const { return mp_gcodegen->config().printable_area.values.size() > 4; }
|
||||
|
||||
std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double start_y, double start_pa, double step_pa, int num)
|
||||
{
|
||||
auto & writer = mp_gcodegen->writer();
|
||||
const auto &config = mp_gcodegen->config();
|
||||
|
||||
const auto filament_diameter = config.filament_diameter.get_at(0);
|
||||
const auto print_flow_ratio = config.print_flow_ratio;
|
||||
|
||||
const double e_per_mm = CalibPressureAdvance::e_per_mm(m_line_width, m_height_layer, m_nozzle_diameter, filament_diameter, print_flow_ratio);
|
||||
const double thin_e_per_mm = CalibPressureAdvance::e_per_mm(m_thin_line_width, m_height_layer, m_nozzle_diameter, filament_diameter, print_flow_ratio);
|
||||
const double number_e_per_mm = CalibPressureAdvance::e_per_mm(m_number_line_width, m_height_layer, m_nozzle_diameter, filament_diameter, print_flow_ratio);
|
||||
|
||||
const double fast = CalibPressureAdvance::speed_adjust(m_fast_speed);
|
||||
const double slow = CalibPressureAdvance::speed_adjust(m_slow_speed);
|
||||
std::stringstream gcode;
|
||||
gcode << mp_gcodegen->writer().travel_to_z(m_height_layer);
|
||||
double y_pos = start_y;
|
||||
|
||||
// prime line
|
||||
auto prime_x = start_x;
|
||||
gcode << move_to(Vec2d(prime_x, y_pos + (num) *m_space_y), writer);
|
||||
gcode << writer.set_speed(slow);
|
||||
gcode << writer.extrude_to_xy(Vec2d(prime_x, y_pos), e_per_mm * m_space_y * num * 1.2);
|
||||
|
||||
for (int i = 0; i < num; ++i) {
|
||||
gcode << writer.set_pressure_advance(start_pa + i * step_pa);
|
||||
gcode << move_to(Vec2d(start_x, y_pos + i * m_space_y), writer);
|
||||
gcode << writer.set_speed(slow);
|
||||
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short, y_pos + i * m_space_y), e_per_mm * m_length_short);
|
||||
gcode << writer.set_speed(fast);
|
||||
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long, y_pos + i * m_space_y), e_per_mm * m_length_long);
|
||||
gcode << writer.set_speed(slow);
|
||||
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long + m_length_short, y_pos + i * m_space_y), e_per_mm * m_length_short);
|
||||
}
|
||||
gcode << writer.set_pressure_advance(0.0);
|
||||
|
||||
if (m_draw_numbers) {
|
||||
// Orca: skip drawing indicator lines
|
||||
// gcode << writer.set_speed(fast);
|
||||
// gcode << move_to(Vec2d(start_x + m_length_short, y_pos + (num - 1) * m_space_y + 2), writer);
|
||||
// gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short, y_pos + (num - 1) * m_space_y + 7), thin_e_per_mm * 7);
|
||||
// gcode << move_to(Vec2d(start_x + m_length_short + m_length_long, y_pos + (num - 1) * m_space_y + 7), writer);
|
||||
// gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long, y_pos + (num - 1) * m_space_y + 2), thin_e_per_mm * 7);
|
||||
|
||||
const auto box_start_x = start_x + m_length_short + m_length_long + m_length_short;
|
||||
DrawBoxOptArgs default_box_opt_args(2, m_height_layer, m_line_width, fast);
|
||||
default_box_opt_args.is_filled = true;
|
||||
gcode << draw_box(writer, box_start_x, start_y - m_space_y, number_spacing() * 8, (num + 1) * m_space_y, default_box_opt_args);
|
||||
gcode << writer.travel_to_z(m_height_layer * 2);
|
||||
for (int i = 0; i < num; i += 2) {
|
||||
gcode << draw_number(box_start_x + 3 + m_line_width, y_pos + i * m_space_y + m_space_y / 2, start_pa + i * step_pa, m_draw_digit_mode, m_number_line_width,
|
||||
number_e_per_mm, 3600, writer);
|
||||
}
|
||||
}
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
void CalibPressureAdvanceLine::delta_modify_start(double &startx, double &starty, int count)
|
||||
{
|
||||
startx = -startx;
|
||||
starty = -(count * m_space_y) / 2;
|
||||
}
|
||||
|
||||
CalibPressureAdvancePattern::CalibPressureAdvancePattern(const Calib_Params ¶ms, const DynamicPrintConfig &config, bool is_qdt_machine, Model &model, const Vec3d &origin)
|
||||
: m_params(params), CalibPressureAdvance(config)
|
||||
{
|
||||
this->m_draw_digit_mode = DrawDigitMode::Bottom_To_Top;
|
||||
|
||||
refresh_setup(config, is_qdt_machine, model, origin);
|
||||
};
|
||||
|
||||
void CalibPressureAdvancePattern::generate_custom_gcodes(const DynamicPrintConfig &config, bool is_qdt_machine, Model &model, const Vec3d &origin)
|
||||
{
|
||||
std::stringstream gcode;
|
||||
gcode << "; start pressure advance pattern for layer\n";
|
||||
|
||||
refresh_setup(config, is_qdt_machine, model, origin);
|
||||
|
||||
gcode << move_to(Vec2d(m_starting_point.x(), m_starting_point.y()), m_writer, "Move to start XY position");
|
||||
gcode << m_writer.travel_to_z(height_first_layer(), "Move to start Z position");
|
||||
gcode << m_writer.set_pressure_advance(m_params.start);
|
||||
|
||||
const DrawBoxOptArgs default_box_opt_args(wall_count(), height_first_layer(), line_width_first_layer(), speed_adjust(speed_first_layer()));
|
||||
|
||||
// create anchoring frame
|
||||
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y(), print_size_x(), frame_size_y(), default_box_opt_args);
|
||||
|
||||
// create tab for numbers
|
||||
DrawBoxOptArgs draw_box_opt_args = default_box_opt_args;
|
||||
draw_box_opt_args.is_filled = true;
|
||||
draw_box_opt_args.num_perimeters = wall_count();
|
||||
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y() + frame_size_y() + line_spacing_first_layer(), glyph_tab_max_x() - m_starting_point.x(),
|
||||
max_numbering_height() + line_spacing_first_layer() + m_glyph_padding_vertical * 2, draw_box_opt_args);
|
||||
|
||||
std::vector<CustomGCode::Item> gcode_items;
|
||||
const int num_patterns = get_num_patterns(); // "cache" for use in loops
|
||||
|
||||
// draw pressure advance pattern
|
||||
for (int i = 0; i < m_num_layers; ++i) {
|
||||
const double layer_height = height_first_layer() + (i * height_layer());
|
||||
const double zhop_height = layer_height + height_layer();
|
||||
|
||||
if (i > 0) {
|
||||
gcode << "; end pressure advance pattern for layer\n";
|
||||
CustomGCode::Item item;
|
||||
item.print_z = height_first_layer() + (i - 1) * height_layer();
|
||||
item.type = CustomGCode::Type::Custom;
|
||||
item.extra = gcode.str();
|
||||
gcode_items.push_back(item);
|
||||
|
||||
gcode = std::stringstream(); // reset for next layer contents
|
||||
gcode << "; start pressure advance pattern for layer\n";
|
||||
|
||||
gcode << m_writer.travel_to_z(layer_height, "Move to layer height");
|
||||
gcode << m_writer.reset_e();
|
||||
}
|
||||
|
||||
// line numbering
|
||||
if (i == 1) {
|
||||
gcode << m_writer.set_pressure_advance(m_params.start);
|
||||
|
||||
double number_e_per_mm = e_per_mm(line_width(), height_layer(), m_config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(0),
|
||||
m_config.option<ConfigOptionFloats>("filament_diameter")->get_at(0),
|
||||
m_config.option<ConfigOptionFloats>("filament_flow_ratio")->get_at(0));
|
||||
|
||||
// glyph on every other line
|
||||
for (int j = 0; j < num_patterns; j += 2) {
|
||||
gcode << draw_number(glyph_start_x(j), m_starting_point.y() + frame_size_y() + m_glyph_padding_vertical + line_width(), m_params.start + (j * m_params.step),
|
||||
m_draw_digit_mode, line_width(), number_e_per_mm, speed_first_layer(), m_writer);
|
||||
}
|
||||
}
|
||||
|
||||
double to_x = m_starting_point.x() + pattern_shift();
|
||||
double to_y = m_starting_point.y();
|
||||
double side_length = m_wall_side_length;
|
||||
|
||||
// shrink first layer to fit inside frame
|
||||
if (i == 0) {
|
||||
double shrink = (line_spacing_first_layer() * (wall_count() - 1) + (line_width_first_layer() * (1 - m_encroachment))) / std::sin(to_radians(m_corner_angle) / 2);
|
||||
side_length = m_wall_side_length - shrink;
|
||||
to_x += shrink * std::sin(to_radians(90) - to_radians(m_corner_angle) / 2);
|
||||
to_y += line_spacing_first_layer() * (wall_count() - 1) + (line_width_first_layer() * (1 - m_encroachment));
|
||||
}
|
||||
|
||||
double initial_x = to_x;
|
||||
double initial_y = to_y;
|
||||
|
||||
gcode << m_writer.travel_to_z(zhop_height, "z-hop before move");
|
||||
gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move to pattern start");
|
||||
gcode << m_writer.travel_to_z(layer_height, "undo z-hop");
|
||||
|
||||
for (int j = 0; j < num_patterns; ++j) {
|
||||
// increment pressure advance
|
||||
gcode << m_writer.set_pressure_advance(m_params.start + (j * m_params.step));
|
||||
|
||||
for (int k = 0; k < wall_count(); ++k) {
|
||||
to_x += std::cos(to_radians(m_corner_angle) / 2) * side_length;
|
||||
to_y += std::sin(to_radians(m_corner_angle) / 2) * side_length;
|
||||
|
||||
auto draw_line_arg_height = i == 0 ? height_first_layer() : height_layer();
|
||||
auto draw_line_arg_line_width = line_width(); // don't use line_width_first_layer so results are consistent across all layers
|
||||
auto draw_line_arg_speed = i == 0 ? speed_adjust(speed_first_layer()) : speed_adjust(speed_perimeter());
|
||||
auto draw_line_arg_comment = "Print pattern wall";
|
||||
gcode << draw_line(m_writer, Vec2d(to_x, to_y), draw_line_arg_line_width, draw_line_arg_height, draw_line_arg_speed, draw_line_arg_comment);
|
||||
|
||||
to_x -= std::cos(to_radians(m_corner_angle) / 2) * side_length;
|
||||
to_y += std::sin(to_radians(m_corner_angle) / 2) * side_length;
|
||||
|
||||
gcode << draw_line(m_writer, Vec2d(to_x, to_y), draw_line_arg_line_width, draw_line_arg_height, draw_line_arg_speed, draw_line_arg_comment);
|
||||
|
||||
to_y = initial_y;
|
||||
if (k != wall_count() - 1) {
|
||||
// perimeters not done yet. move to next perimeter
|
||||
to_x += line_spacing_angle();
|
||||
gcode << m_writer.travel_to_z(zhop_height, "z-hop before move");
|
||||
gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move to start next pattern wall");
|
||||
gcode << m_writer.travel_to_z(layer_height, "undo z-hop");
|
||||
} else if (j != num_patterns - 1) {
|
||||
// patterns not done yet. move to next pattern
|
||||
to_x += m_pattern_spacing + line_width();
|
||||
gcode << m_writer.travel_to_z(zhop_height, "z-hop before move");
|
||||
gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move to next pattern");
|
||||
gcode << m_writer.travel_to_z(layer_height, "undo z-hop");
|
||||
} else if (i != m_num_layers - 1) {
|
||||
// layers not done yet. move back to start
|
||||
to_x = initial_x;
|
||||
gcode << m_writer.travel_to_z(zhop_height, "z-hop before move");
|
||||
gcode << move_to(Vec2d(to_x, to_y), m_writer, "Move back to start position");
|
||||
gcode << m_writer.travel_to_z(layer_height, "undo z-hop");
|
||||
gcode << m_writer.reset_e(); // reset extruder before printing placeholder cube to avoid
|
||||
} else {
|
||||
// everything done
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gcode << m_writer.set_pressure_advance(m_params.start);
|
||||
gcode << "; end pressure advance pattern for layer\n";
|
||||
|
||||
CustomGCode::Item item;
|
||||
item.print_z = max_layer_z();
|
||||
item.type = CustomGCode::Type::Custom;
|
||||
item.extra = gcode.str();
|
||||
gcode_items.push_back(item);
|
||||
|
||||
CustomGCode::Info info;
|
||||
info.mode = CustomGCode::Mode::SingleExtruder;
|
||||
info.gcodes = gcode_items;
|
||||
|
||||
model.plates_custom_gcodes[model.curr_plate_index] = info;
|
||||
}
|
||||
|
||||
//w28
|
||||
std::stringstream CalibPressureAdvancePattern::generate_custom_nums_gcodes(const DynamicPrintConfig& config, bool is_qdt_machine, Model& model, const Vec3d& origin, const double nums,double x,double y)
|
||||
{
|
||||
std::stringstream gcode;
|
||||
gcode << "\nG1 Z" << height_first_layer() + height_layer()<<" F600" << "\n";
|
||||
refresh_setup(config, is_qdt_machine, model, origin);
|
||||
double number_e_per_mm = e_per_mm(line_width(), height_layer(), m_config.option<ConfigOptionFloats>("nozzle_diameter")->get_at(0),
|
||||
m_config.option<ConfigOptionFloats>("filament_diameter")->get_at(0),
|
||||
m_config.option<ConfigOptionFloats>("filament_flow_ratio")->get_at(0));
|
||||
gcode << draw_number(x, y + line_width(), nums,
|
||||
DrawDigitMode::Left_To_Right, line_width(), number_e_per_mm, speed_first_layer(), m_writer);
|
||||
return gcode;
|
||||
}
|
||||
|
||||
void CalibPressureAdvancePattern::set_start_offset(const Vec3d &offset)
|
||||
{
|
||||
m_starting_point = offset;
|
||||
m_is_start_point_fixed = true;
|
||||
}
|
||||
|
||||
Vec3d CalibPressureAdvancePattern::get_start_offset() { return m_starting_point; }
|
||||
|
||||
void CalibPressureAdvancePattern::refresh_setup(const DynamicPrintConfig &config, bool is_qdt_machine, const Model &model, const Vec3d &origin)
|
||||
{
|
||||
m_config = config;
|
||||
m_config.apply(model.objects.front()->config.get(), true);
|
||||
m_config.apply(model.objects.front()->volumes.front()->config.get(), true);
|
||||
|
||||
_refresh_starting_point(model);
|
||||
_refresh_writer(is_qdt_machine, model, origin);
|
||||
}
|
||||
|
||||
void CalibPressureAdvancePattern::_refresh_starting_point(const Model &model)
|
||||
{
|
||||
if (m_is_start_point_fixed) return;
|
||||
|
||||
ModelObject * obj = model.objects.front();
|
||||
BoundingBoxf3 bbox = obj->instance_bounding_box(*obj->instances.front(), false);
|
||||
|
||||
m_starting_point = Vec3d(bbox.min.x(), bbox.max.y(), 0);
|
||||
m_starting_point.y() += m_handle_spacing;
|
||||
}
|
||||
|
||||
void CalibPressureAdvancePattern::_refresh_writer(bool is_qdt_machine, const Model &model, const Vec3d &origin)
|
||||
{
|
||||
PrintConfig print_config;
|
||||
print_config.apply(m_config, true);
|
||||
|
||||
m_writer.apply_print_config(print_config);
|
||||
m_writer.set_xy_offset(origin(0), origin(1));
|
||||
//m_writer.set_is_qdt_machine(is_qdt_machine);
|
||||
|
||||
const unsigned int extruder_id = model.objects.front()->volumes.front()->extruder_id();
|
||||
m_writer.set_extruders({extruder_id});
|
||||
m_writer.set_extruder(extruder_id);
|
||||
}
|
||||
|
||||
double CalibPressureAdvancePattern::object_size_x() const
|
||||
{
|
||||
return get_num_patterns() * ((wall_count() - 1) * line_spacing_angle()) + (get_num_patterns() - 1) * (m_pattern_spacing + line_width()) +
|
||||
std::cos(to_radians(m_corner_angle) / 2) * m_wall_side_length + line_spacing_first_layer() * wall_count();
|
||||
}
|
||||
|
||||
double CalibPressureAdvancePattern::object_size_y() const
|
||||
{
|
||||
return 2 * (std::sin(to_radians(m_corner_angle) / 2) * m_wall_side_length) + max_numbering_height() + m_glyph_padding_vertical * 2 + line_width_first_layer();
|
||||
}
|
||||
|
||||
double CalibPressureAdvancePattern::glyph_start_x(int pattern_i) const
|
||||
{
|
||||
// note that pattern_i is zero-based!
|
||||
// align glyph's start with first perimeter of specified pattern
|
||||
double x =
|
||||
// starting offset
|
||||
m_starting_point.x() + pattern_shift() +
|
||||
|
||||
// width of pattern extrusions
|
||||
pattern_i * (wall_count() - 1) * line_spacing_angle() + // center to center distance of extrusions
|
||||
pattern_i * line_width() + // endcaps. center to end on either side = 1 line width
|
||||
|
||||
// space between each pattern
|
||||
pattern_i * m_pattern_spacing;
|
||||
|
||||
// align to middle of pattern walls
|
||||
x += wall_count() * line_spacing_angle() / 2;
|
||||
|
||||
// shift so glyph is centered on pattern
|
||||
// m_digit_segment_len = half of X length of glyph
|
||||
x -= (glyph_length_x() / 2);
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
double CalibPressureAdvancePattern::glyph_length_x() const
|
||||
{
|
||||
// half of line_width sticks out on each side
|
||||
return line_width() + (2 * m_digit_segment_len);
|
||||
}
|
||||
|
||||
double CalibPressureAdvancePattern::glyph_tab_max_x() const
|
||||
{
|
||||
// only every other glyph is shown, starting with 1
|
||||
int num = get_num_patterns();
|
||||
int max_num = (num % 2 == 0) ? num - 1 : num;
|
||||
|
||||
// padding at end should be same as padding at start
|
||||
double padding = glyph_start_x(0) - m_starting_point.x();
|
||||
|
||||
return glyph_start_x(max_num - 1) + // glyph_start_x is zero-based
|
||||
(glyph_length_x() - line_width() / 2) + padding;
|
||||
}
|
||||
|
||||
double CalibPressureAdvancePattern::max_numbering_height() const
|
||||
{
|
||||
std::string::size_type most_characters = 0;
|
||||
const int num_patterns = get_num_patterns();
|
||||
|
||||
// note: only every other number is printed
|
||||
for (std::string::size_type i = 0; i < num_patterns; i += 2) {
|
||||
std::string sNumber = convert_number_to_string(m_params.start + (i * m_params.step));
|
||||
|
||||
if (sNumber.length() > most_characters) { most_characters = sNumber.length(); }
|
||||
}
|
||||
|
||||
most_characters = std::min(most_characters, m_max_number_len);
|
||||
|
||||
return (most_characters * m_digit_segment_len) + ((most_characters - 1) * m_digit_gap_len);
|
||||
}
|
||||
|
||||
double CalibPressureAdvancePattern::pattern_shift() const { return (wall_count() - 1) * line_spacing_first_layer() + line_width_first_layer() + m_glyph_padding_horizontal; }
|
||||
} // namespace Slic3r
|
||||
|
||||
313
src/libslic3r/Calib.hpp
Normal file
313
src/libslic3r/Calib.hpp
Normal file
@@ -0,0 +1,313 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "GCodeWriter.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCode;
|
||||
class Model;
|
||||
|
||||
enum class CalibMode : int {
|
||||
Calib_None = 0,
|
||||
Calib_PA_Line,
|
||||
Calib_PA_Pattern,
|
||||
Calib_PA_Tower,
|
||||
Calib_Flow_Rate,
|
||||
Calib_Temp_Tower,
|
||||
Calib_Vol_speed_Tower,
|
||||
Calib_VFA_Tower,
|
||||
Calib_Retraction_tower,
|
||||
//w29
|
||||
Calib_Flow_Rate_Coarse,
|
||||
Calib_Flow_Rate_Fine
|
||||
};
|
||||
|
||||
enum class CalibState {
|
||||
Start = 0,
|
||||
Preset,
|
||||
Calibration,
|
||||
CoarseSave,
|
||||
FineCalibration,
|
||||
Save,
|
||||
Finish
|
||||
};
|
||||
|
||||
struct Calib_Params
|
||||
{
|
||||
Calib_Params() : mode(CalibMode::Calib_None){}
|
||||
double start, end, step;
|
||||
bool print_numbers = false;
|
||||
CalibMode mode;
|
||||
};
|
||||
|
||||
enum FlowRatioCalibrationType {
|
||||
COMPLETE_CALIBRATION = 0,
|
||||
FINE_CALIBRATION,
|
||||
};
|
||||
|
||||
class X1CCalibInfos
|
||||
{
|
||||
public:
|
||||
struct X1CCalibInfo
|
||||
{
|
||||
int tray_id;
|
||||
int bed_temp;
|
||||
int nozzle_temp;
|
||||
float nozzle_diameter;
|
||||
std::string filament_id;
|
||||
std::string setting_id;
|
||||
float max_volumetric_speed;
|
||||
float flow_rate = 0.98f; // for flow ratio
|
||||
};
|
||||
|
||||
std::vector<X1CCalibInfo> calib_datas;
|
||||
};
|
||||
|
||||
class CaliPresetInfo
|
||||
{
|
||||
public:
|
||||
int tray_id;
|
||||
float nozzle_diameter;
|
||||
std::string filament_id;
|
||||
std::string setting_id;
|
||||
std::string name;
|
||||
|
||||
CaliPresetInfo &operator=(const CaliPresetInfo &other)
|
||||
{
|
||||
this->tray_id = other.tray_id;
|
||||
this->nozzle_diameter = other.nozzle_diameter;
|
||||
this->filament_id = other.filament_id;
|
||||
this->setting_id = other.setting_id;
|
||||
this->name = other.name;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct PrinterCaliInfo
|
||||
{
|
||||
std::string dev_id;
|
||||
bool cali_finished = true;
|
||||
float cache_flow_ratio;
|
||||
std::vector<CaliPresetInfo> selected_presets;
|
||||
FlowRatioCalibrationType cache_flow_rate_calibration_type = FlowRatioCalibrationType::COMPLETE_CALIBRATION;
|
||||
};
|
||||
|
||||
class PACalibResult
|
||||
{
|
||||
public:
|
||||
enum CalibResult {
|
||||
CALI_RESULT_SUCCESS = 0,
|
||||
CALI_RESULT_PROBLEM = 1,
|
||||
CALI_RESULT_FAILED = 2,
|
||||
};
|
||||
int tray_id;
|
||||
int cali_idx = -1;
|
||||
float nozzle_diameter;
|
||||
std::string filament_id;
|
||||
std::string setting_id;
|
||||
std::string name;
|
||||
float k_value = 0.0;
|
||||
float n_coef = 0.0;
|
||||
int confidence = -1; // 0: success 1: uncertain 2: failed
|
||||
};
|
||||
|
||||
struct PACalibIndexInfo
|
||||
{
|
||||
int tray_id;
|
||||
int cali_idx;
|
||||
float nozzle_diameter;
|
||||
std::string filament_id;
|
||||
};
|
||||
|
||||
class FlowRatioCalibResult
|
||||
{
|
||||
public:
|
||||
int tray_id;
|
||||
float nozzle_diameter;
|
||||
std::string filament_id;
|
||||
std::string setting_id;
|
||||
float flow_ratio;
|
||||
int confidence; // 0: success 1: uncertain 2: failed
|
||||
};
|
||||
|
||||
struct DrawBoxOptArgs
|
||||
{
|
||||
DrawBoxOptArgs(int num_perimeters, double height, double line_width, double speed) : num_perimeters{num_perimeters}, height{height}, line_width{line_width}, speed{speed} {};
|
||||
DrawBoxOptArgs() = default;
|
||||
|
||||
bool is_filled{false};
|
||||
int num_perimeters;
|
||||
double height;
|
||||
double line_width;
|
||||
double speed;
|
||||
};
|
||||
class CalibPressureAdvance
|
||||
{
|
||||
public:
|
||||
static float find_optimal_PA_speed(const DynamicPrintConfig &config, double line_width, double layer_height, int filament_idx = 0);
|
||||
|
||||
protected:
|
||||
CalibPressureAdvance() = default;
|
||||
CalibPressureAdvance(const DynamicPrintConfig &config) : m_config(config){};
|
||||
CalibPressureAdvance(const FullPrintConfig &config) { m_config.apply(config); };
|
||||
~CalibPressureAdvance() = default;
|
||||
|
||||
enum class DrawDigitMode { Left_To_Right, Bottom_To_Top };
|
||||
|
||||
void delta_scale_bed_ext(BoundingBoxf &bed_ext) const { bed_ext.scale(1.0f / 1.41421f); }
|
||||
|
||||
std::string move_to(Vec2d pt, GCodeWriter &writer, std::string comment = std::string());
|
||||
double e_per_mm(double line_width, double layer_height, float nozzle_diameter, float filament_diameter, float print_flow_ratio) const;
|
||||
double speed_adjust(int speed) const { return speed * 60; };
|
||||
|
||||
std::string convert_number_to_string(double num) const;
|
||||
double number_spacing() const { return m_digit_segment_len + m_digit_gap_len; };
|
||||
std::string draw_digit(double startx, double starty, char c, CalibPressureAdvance::DrawDigitMode mode, double line_width, double e_per_mm, GCodeWriter &writer);
|
||||
std::string draw_number(
|
||||
double startx, double starty, double value, CalibPressureAdvance::DrawDigitMode mode, double line_width, double e_per_mm, double speed, GCodeWriter &writer);
|
||||
|
||||
std::string draw_line(GCodeWriter &writer, Vec2d to_pt, double line_width, double layer_height, double speed, const std::string &comment = std::string());
|
||||
std::string draw_box(GCodeWriter &writer, double min_x, double min_y, double size_x, double size_y, DrawBoxOptArgs opt_args);
|
||||
|
||||
double to_radians(double degrees) const { return degrees * M_PI / 180; };
|
||||
double get_distance(Vec2d from, Vec2d to) const;
|
||||
|
||||
Vec3d m_last_pos;
|
||||
DynamicPrintConfig m_config;
|
||||
|
||||
const double m_encroachment{1. / 3.};
|
||||
DrawDigitMode m_draw_digit_mode{DrawDigitMode::Left_To_Right};
|
||||
const double m_digit_segment_len{2};
|
||||
const double m_digit_gap_len{1};
|
||||
const std::string::size_type m_max_number_len{5};
|
||||
};
|
||||
|
||||
class CalibPressureAdvanceLine : public CalibPressureAdvance
|
||||
{
|
||||
public:
|
||||
CalibPressureAdvanceLine(GCode *gcodegen);
|
||||
~CalibPressureAdvanceLine(){};
|
||||
|
||||
std::string generate_test(double start_pa = 0, double step_pa = 0.002, int count = 50);
|
||||
|
||||
void set_speed(double fast = 100.0, double slow = 20.0)
|
||||
{
|
||||
m_slow_speed = slow;
|
||||
m_fast_speed = fast;
|
||||
}
|
||||
|
||||
const double &line_width() { return m_line_width; };
|
||||
const double &height_layer() { return m_height_layer; };
|
||||
bool is_delta() const;
|
||||
bool & draw_numbers() { return m_draw_numbers; }
|
||||
|
||||
private:
|
||||
std::string print_pa_lines(double start_x, double start_y, double start_pa, double step_pa, int num);
|
||||
|
||||
void delta_modify_start(double &startx, double &starty, int count);
|
||||
|
||||
GCode *mp_gcodegen;
|
||||
|
||||
double m_nozzle_diameter;
|
||||
double m_slow_speed, m_fast_speed;
|
||||
|
||||
double m_height_layer{0.2};
|
||||
double m_line_width{0.6};
|
||||
double m_thin_line_width{0.44};
|
||||
double m_number_line_width{0.48};
|
||||
const double m_space_y{3.5};
|
||||
|
||||
double m_length_short{20.0}, m_length_long{40.0};
|
||||
bool m_draw_numbers{true};
|
||||
};
|
||||
|
||||
struct SuggestedConfigCalibPAPattern
|
||||
{
|
||||
const std::vector<std::pair<std::string, double>> float_pairs{{"initial_layer_print_height", 0.25}, {"layer_height", 0.2}, {"initial_layer_speed", 30}};
|
||||
|
||||
const std::vector<std::pair<std::string, double>> nozzle_ratio_pairs{{"line_width", 112.5}, {"initial_layer_line_width", 140}};
|
||||
|
||||
const std::vector<std::pair<std::string, int>> int_pairs{{"skirt_loops", 0}, {"wall_loops", 3}};
|
||||
|
||||
const std::pair<std::string, BrimType> brim_pair{"brim_type", BrimType::btNoBrim};
|
||||
};
|
||||
|
||||
class CalibPressureAdvancePattern : public CalibPressureAdvance
|
||||
{
|
||||
friend struct DrawBoxOptArgs;
|
||||
|
||||
public:
|
||||
CalibPressureAdvancePattern(const Calib_Params ¶ms, const DynamicPrintConfig &config, bool is_qdt_machine, Model &model, const Vec3d &origin);
|
||||
|
||||
double handle_xy_size() const { return m_handle_xy_size; };
|
||||
double handle_spacing() const { return m_handle_spacing; };
|
||||
double print_size_x() const { return object_size_x() + pattern_shift(); };
|
||||
double print_size_y() const { return object_size_y(); };
|
||||
double max_layer_z() const { return height_first_layer() + ((m_num_layers - 1) * height_layer()); };
|
||||
|
||||
void generate_custom_gcodes(const DynamicPrintConfig &config, bool is_qdt_machine, Model &model, const Vec3d &origin);
|
||||
//w29
|
||||
std::stringstream generate_custom_nums_gcodes(const DynamicPrintConfig& config, bool is_qdt_machine, Model& model, const Vec3d& origin, const double nums,double x,double y);
|
||||
|
||||
void set_start_offset(const Vec3d &offset);
|
||||
Vec3d get_start_offset();
|
||||
|
||||
protected:
|
||||
double speed_first_layer() const { return m_config.option<ConfigOptionFloat>("initial_layer_speed")->value; };
|
||||
double speed_perimeter() const { return m_config.option<ConfigOptionFloat>("outer_wall_speed")->value; };
|
||||
double line_width_first_layer() const { return m_config.get_abs_value("initial_layer_line_width"); };
|
||||
double line_width() const { return m_config.get_abs_value("line_width"); };
|
||||
int wall_count() const { return m_config.option<ConfigOptionInt>("wall_loops")->value; };
|
||||
|
||||
private:
|
||||
void refresh_setup(const DynamicPrintConfig &config, bool is_qdt_machine, const Model &model, const Vec3d &origin);
|
||||
void _refresh_starting_point(const Model &model);
|
||||
void _refresh_writer(bool is_qdt_machine, const Model &model, const Vec3d &origin);
|
||||
|
||||
double height_first_layer() const { return m_config.option<ConfigOptionFloat>("initial_layer_print_height")->value; };
|
||||
double height_layer() const { return m_config.option<ConfigOptionFloat>("layer_height")->value; };
|
||||
const int get_num_patterns() const { return std::ceil((m_params.end - m_params.start) / m_params.step + 1); }
|
||||
|
||||
/*
|
||||
from slic3r documentation: spacing = extrusion_width - layer_height * (1 - PI/4)
|
||||
"spacing" = center-to-center distance of adjacent extrusions, which partially overlap
|
||||
https://manual.slic3r.org/advanced/flow-math
|
||||
https://ellis3dp.com/Print-Tuning-Guide/articles/misconceptions.html#two-04mm-perimeters--08mm
|
||||
*/
|
||||
double line_spacing() const { return line_width() - height_layer() * (1 - M_PI / 4); };
|
||||
double line_spacing_first_layer() const { return line_width_first_layer() - height_first_layer() * (1 - M_PI / 4); };
|
||||
double line_spacing_angle() const { return line_spacing() / std::sin(to_radians(m_corner_angle) / 2); };
|
||||
|
||||
double object_size_x() const;
|
||||
double object_size_y() const;
|
||||
double frame_size_y() const { return std::sin(to_radians(double(m_corner_angle) / 2)) * m_wall_side_length * 2; };
|
||||
|
||||
double glyph_start_x(int pattern_i = 0) const;
|
||||
double glyph_length_x() const;
|
||||
double glyph_tab_max_x() const;
|
||||
double max_numbering_height() const;
|
||||
|
||||
double pattern_shift() const;
|
||||
|
||||
const Calib_Params &m_params;
|
||||
|
||||
GCodeWriter m_writer;
|
||||
Vec3d m_starting_point;
|
||||
bool m_is_start_point_fixed = false;
|
||||
|
||||
const double m_handle_xy_size{5};
|
||||
const double m_handle_spacing{2};
|
||||
const int m_num_layers{4};
|
||||
|
||||
const double m_wall_side_length{30.0};
|
||||
const int m_corner_angle{90};
|
||||
const int m_pattern_spacing{2};
|
||||
|
||||
const double m_glyph_padding_horizontal{1};
|
||||
const double m_glyph_padding_vertical{1};
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
101
src/libslic3r/Channel.hpp
Normal file
101
src/libslic3r/Channel.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#ifndef slic3r_Channel_hpp_
|
||||
#define slic3r_Channel_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
template<class T> class Channel
|
||||
{
|
||||
public:
|
||||
using UniqueLock = std::unique_lock<std::mutex>;
|
||||
|
||||
template<class Ptr> class Unlocker
|
||||
{
|
||||
public:
|
||||
Unlocker(UniqueLock lock) : m_lock(std::move(lock)) {}
|
||||
Unlocker(const Unlocker &other) noexcept : m_lock(std::move(other.m_lock)) {} // XXX: done beacuse of MSVC 2013 not supporting init of deleter by move
|
||||
Unlocker(Unlocker &&other) noexcept : m_lock(std::move(other.m_lock)) {}
|
||||
Unlocker& operator=(const Unlocker &other) = delete;
|
||||
Unlocker& operator=(Unlocker &&other) { m_lock = std::move(other.m_lock); }
|
||||
|
||||
void operator()(Ptr*) { m_lock.unlock(); }
|
||||
private:
|
||||
mutable UniqueLock m_lock; // XXX: mutable: see above
|
||||
};
|
||||
|
||||
using Queue = std::deque<T>;
|
||||
using LockedConstPtr = std::unique_ptr<const Queue, Unlocker<const Queue>>;
|
||||
using LockedPtr = std::unique_ptr<Queue, Unlocker<Queue>>;
|
||||
|
||||
Channel() {}
|
||||
~Channel() {}
|
||||
|
||||
void push(const T& item, bool silent = false)
|
||||
{
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
m_queue.push_back(item);
|
||||
}
|
||||
if (! silent) { m_condition.notify_one(); }
|
||||
}
|
||||
|
||||
void push(T &&item, bool silent = false)
|
||||
{
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
m_queue.push_back(std::forward<T>(item));
|
||||
}
|
||||
if (! silent) { m_condition.notify_one(); }
|
||||
}
|
||||
|
||||
T pop()
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
m_condition.wait(lock, [this]() { return !m_queue.empty(); });
|
||||
auto item = std::move(m_queue.front());
|
||||
m_queue.pop_front();
|
||||
return item;
|
||||
}
|
||||
|
||||
boost::optional<T> try_pop()
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
if (m_queue.empty()) {
|
||||
return boost::none;
|
||||
} else {
|
||||
auto item = std::move(m_queue.front());
|
||||
m_queue.pop();
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
// Unlocked observer/hint. Thread unsafe! Keep in mind you need to re-verify the result after locking.
|
||||
size_t size_hint() const noexcept { return m_queue.size(); }
|
||||
|
||||
LockedConstPtr lock_read() const
|
||||
{
|
||||
return LockedConstPtr(&m_queue, Unlocker<const Queue>(UniqueLock(m_mutex)));
|
||||
}
|
||||
|
||||
LockedPtr lock_rw()
|
||||
{
|
||||
return LockedPtr(&m_queue, Unlocker<Queue>(UniqueLock(m_mutex)));
|
||||
}
|
||||
private:
|
||||
Queue m_queue;
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_Channel_hpp_
|
||||
516
src/libslic3r/Circle.cpp
Normal file
516
src/libslic3r/Circle.cpp
Normal file
@@ -0,0 +1,516 @@
|
||||
#include "Circle.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include "Geometry.hpp"
|
||||
|
||||
|
||||
//QDS: Refer to ArcWelderLib for the arc fitting functions
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//QDS: threshold used to judge collineation
|
||||
static const double Parallel_area_threshold = 0.0001;
|
||||
|
||||
bool Circle::try_create_circle(const Point& p1, const Point& p2, const Point& p3, const double max_radius, Circle& new_circle)
|
||||
{
|
||||
double x1 = p1.x();
|
||||
double y1 = p1.y();
|
||||
double x2 = p2.x();
|
||||
double y2 = p2.y();
|
||||
double x3 = p3.x();
|
||||
double y3 = p3.y();
|
||||
|
||||
//QDS: use area of triangle to judge whether three points are almostly on one line
|
||||
//Because the point is scale_ once, so area should scale_ twice.
|
||||
if (fabs((y1 - y2) * (x1 - x3) - (y1 - y3) * (x1 - x2)) <= scale_(scale_(Parallel_area_threshold)))
|
||||
return false;
|
||||
|
||||
double a = x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2;
|
||||
//QDS: take out to figure out how we handle very small values
|
||||
if (fabs(a) < SCALED_EPSILON)
|
||||
return false;
|
||||
|
||||
double b = (x1 * x1 + y1 * y1) * (y3 - y2)
|
||||
+ (x2 * x2 + y2 * y2) * (y1 - y3)
|
||||
+ (x3 * x3 + y3 * y3) * (y2 - y1);
|
||||
|
||||
double c = (x1 * x1 + y1 * y1) * (x2 - x3)
|
||||
+ (x2 * x2 + y2 * y2) * (x3 - x1)
|
||||
+ (x3 * x3 + y3 * y3) * (x1 - x2);
|
||||
|
||||
double center_x = -b / (2.0 * a);
|
||||
double center_y = -c / (2.0 * a);
|
||||
|
||||
double delta_x = center_x - x1;
|
||||
double delta_y = center_y - y1;
|
||||
double radius = sqrt(delta_x * delta_x + delta_y * delta_y);
|
||||
if (radius > max_radius)
|
||||
return false;
|
||||
|
||||
new_circle.center = Point(center_x, center_y);
|
||||
new_circle.radius = radius;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Circle::try_create_circle(const Points& points, const double max_radius, const double tolerance, Circle& new_circle)
|
||||
{
|
||||
size_t count = points.size();
|
||||
size_t middle_index = count / 2;
|
||||
// QDS: the middle point will almost always produce the best arcs with high possibility.
|
||||
if (count == 3) {
|
||||
return (Circle::try_create_circle(points[0], points[middle_index], points[count - 1], max_radius, new_circle)
|
||||
&& !new_circle.is_over_deviation(points, tolerance));
|
||||
} else {
|
||||
Point middle_point = (count % 2 == 0) ? (points[middle_index] + points[middle_index - 1]) / 2 :
|
||||
(points[middle_index - 1] + points[middle_index + 1]) / 2;
|
||||
if (Circle::try_create_circle(points[0], middle_point, points[count - 1], max_radius, new_circle)
|
||||
&& !new_circle.is_over_deviation(points, tolerance))
|
||||
return true;
|
||||
}
|
||||
|
||||
// QDS: Find the circle with the least deviation, if one exists.
|
||||
Circle test_circle;
|
||||
double least_deviation;
|
||||
bool found_circle = false;
|
||||
double current_deviation;
|
||||
for (int index = 1; index < count - 1; index++)
|
||||
{
|
||||
if (index == middle_index)
|
||||
// QDS: We already checked this one, and it failed. don't need to do again
|
||||
continue;
|
||||
|
||||
if (Circle::try_create_circle(points[0], points[index], points[count - 1], max_radius, test_circle) && test_circle.get_deviation_sum_squared(points, tolerance, current_deviation))
|
||||
{
|
||||
if (!found_circle || current_deviation < least_deviation)
|
||||
{
|
||||
found_circle = true;
|
||||
least_deviation = current_deviation;
|
||||
new_circle = test_circle;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found_circle;
|
||||
}
|
||||
|
||||
double Circle::get_polar_radians(const Point& p1) const
|
||||
{
|
||||
double polar_radians = atan2(p1.y() - center.y(), p1.x() - center.x());
|
||||
if (polar_radians < 0)
|
||||
polar_radians = (2.0 * PI) + polar_radians;
|
||||
return polar_radians;
|
||||
}
|
||||
|
||||
bool Circle::is_over_deviation(const Points& points, const double tolerance)
|
||||
{
|
||||
Point closest_point;
|
||||
Point temp;
|
||||
double distance_from_center;
|
||||
// QDS: skip the first and last points since they has fit perfectly.
|
||||
for (size_t index = 0; index < points.size() - 1; index++)
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
//QDS: check fitting tolerance
|
||||
temp = points[index] - center;
|
||||
distance_from_center = sqrt((double)temp.x() * (double)temp.x() + (double)temp.y() * (double)temp.y());
|
||||
if (std::fabs(distance_from_center - radius) > tolerance)
|
||||
return true;
|
||||
}
|
||||
|
||||
//QDS: Check the point perpendicular from the segment to the circle's center
|
||||
if (get_closest_perpendicular_point(points[index], points[(size_t)index + 1], center, closest_point)) {
|
||||
temp = closest_point - center;
|
||||
distance_from_center = sqrt((double)temp.x() * (double)temp.x() + (double)temp.y() * (double)temp.y());
|
||||
if (std::fabs(distance_from_center - radius) > tolerance)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Circle::get_closest_perpendicular_point(const Point& p1, const Point& p2, const Point& c, Point& out)
|
||||
{
|
||||
double x1 = p1.x();
|
||||
double y1 = p1.y();
|
||||
double x2 = p2.x();
|
||||
double y2 = p2.y();
|
||||
double x_dif = x2 - x1;
|
||||
double y_dif = y2 - y1;
|
||||
//QDS: [(Cx - Ax)(Bx - Ax) + (Cy - Ay)(By - Ay)] / [(Bx - Ax) ^ 2 + (By - Ay) ^ 2]
|
||||
double num = (c[0] - x1) * x_dif + (c[1] - y1) * y_dif;
|
||||
double denom = (x_dif * x_dif) + (y_dif * y_dif);
|
||||
double t = num / denom;
|
||||
|
||||
//QDS: Considering this a failure if t == 0 or t==1 within tolerance. In that case we hit the endpoint, which is OK.
|
||||
if (Circle::less_than_or_equal(t, 0) || Circle::greater_than_or_equal(t, 1))
|
||||
return false;
|
||||
|
||||
out[0] = x1 + t * (x2 - x1);
|
||||
out[1] = y1 + t * (y2 - y1);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Circle::get_deviation_sum_squared(const Points& points, const double tolerance, double& total_deviation)
|
||||
{
|
||||
total_deviation = 0;
|
||||
Point temp;
|
||||
double distance_from_center, deviation;
|
||||
// QDS: skip the first and last points since they are on the circle
|
||||
for (int index = 1; index < points.size() - 1; index++)
|
||||
{
|
||||
//QDS: make sure the length from the center of our circle to the test point is
|
||||
// at or below our max distance.
|
||||
temp = points[index] - center;
|
||||
distance_from_center = sqrt((double)temp.x() * (double)temp.x() + (double)temp.y() * (double)temp.y());
|
||||
deviation = std::fabs(distance_from_center - radius);
|
||||
total_deviation += deviation * deviation;
|
||||
if (deviation > tolerance)
|
||||
return false;
|
||||
|
||||
}
|
||||
Point closest_point;
|
||||
//QDS: check the point perpendicular from the segment to the circle's center
|
||||
for (int index = 0; index < points.size() - 1; index++)
|
||||
{
|
||||
if (get_closest_perpendicular_point(points[index], points[(size_t)index + 1], center, closest_point)) {
|
||||
temp = closest_point - center;
|
||||
distance_from_center = sqrt((double)temp.x() * (double)temp.x() + (double)temp.y() * (double)temp.y());
|
||||
deviation = std::fabs(distance_from_center - radius);
|
||||
total_deviation += deviation * deviation;
|
||||
if (deviation > tolerance)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//QDS: only support calculate on X-Y plane, Z is useless
|
||||
Vec3f Circle::calc_tangential_vector(const Vec3f& pos, const Vec3f& center_pos, const bool is_ccw)
|
||||
{
|
||||
Vec3f dir = center_pos - pos;
|
||||
dir(2,0) = 0;
|
||||
dir.normalize();
|
||||
Vec3f res;
|
||||
if (is_ccw)
|
||||
res = { dir(1, 0), -dir(0, 0), 0.0f };
|
||||
else
|
||||
res = { -dir(1, 0), dir(0, 0), 0.0f };
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ArcSegment::reverse()
|
||||
{
|
||||
if (!is_valid())
|
||||
return false;
|
||||
std::swap(start_point, end_point);
|
||||
direction = (direction == ArcDirection::Arc_Dir_CCW) ? ArcDirection::Arc_Dir_CW : ArcDirection::Arc_Dir_CCW;
|
||||
angle_radians *= -1.0;
|
||||
std::swap(polar_start_theta, polar_end_theta);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArcSegment::clip_start(const Point &point)
|
||||
{
|
||||
if (!is_valid() || point == center || !is_point_inside(point))
|
||||
return false;
|
||||
start_point = get_closest_point(point);
|
||||
update_angle_and_length();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArcSegment::clip_end(const Point &point)
|
||||
{
|
||||
if (!is_valid() || point == center || !is_point_inside(point))
|
||||
return false;
|
||||
end_point = get_closest_point(point);
|
||||
update_angle_and_length();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArcSegment::split_at(const Point &point, ArcSegment& p1, ArcSegment& p2)
|
||||
{
|
||||
if (!is_valid() || point == center || !is_point_inside(point))
|
||||
return false;
|
||||
Point segment_point = get_closest_point(point);
|
||||
p1 = ArcSegment(center, radius, this->start_point, segment_point, this->direction);
|
||||
p2 = ArcSegment(center, radius, segment_point, this->end_point, this->direction);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArcSegment::is_point_inside(const Point& point) const
|
||||
{
|
||||
double polar_theta = get_polar_radians(point);
|
||||
double radian_delta = polar_theta - polar_start_theta;
|
||||
if (radian_delta > 0 && direction == ArcDirection::Arc_Dir_CW)
|
||||
radian_delta = radian_delta - 2 * M_PI;
|
||||
else if (radian_delta < 0 && direction == ArcDirection::Arc_Dir_CCW)
|
||||
radian_delta = radian_delta + 2 * M_PI;
|
||||
|
||||
return (direction == ArcDirection::Arc_Dir_CCW ?
|
||||
radian_delta > 0.0 && radian_delta < angle_radians :
|
||||
radian_delta < 0.0 && radian_delta > angle_radians);
|
||||
}
|
||||
|
||||
void ArcSegment::update_angle_and_length()
|
||||
{
|
||||
polar_start_theta = get_polar_radians(start_point);
|
||||
polar_end_theta = get_polar_radians(end_point);
|
||||
angle_radians = polar_end_theta - polar_start_theta;
|
||||
if (angle_radians < 0 && direction == ArcDirection::Arc_Dir_CCW)
|
||||
angle_radians = angle_radians + 2 * M_PI;
|
||||
else if (angle_radians > 0 && direction == ArcDirection::Arc_Dir_CW)
|
||||
angle_radians = angle_radians - 2 * M_PI;
|
||||
length = fabs(angle_radians) * radius;
|
||||
is_arc = true;
|
||||
}
|
||||
|
||||
bool ArcSegment::try_create_arc(
|
||||
const Points& points,
|
||||
ArcSegment& target_arc,
|
||||
double approximate_length,
|
||||
double max_radius,
|
||||
double tolerance,
|
||||
double path_tolerance_percent)
|
||||
{
|
||||
Circle test_circle = (Circle)target_arc;
|
||||
if (!Circle::try_create_circle(points, max_radius, tolerance, test_circle))
|
||||
return false;
|
||||
|
||||
int mid_point_index = ((points.size() - 2) / 2) + 1;
|
||||
ArcSegment test_arc;
|
||||
if (!ArcSegment::try_create_arc(test_circle, points[0], points[mid_point_index], points[points.size() - 1], test_arc, approximate_length, path_tolerance_percent))
|
||||
return false;
|
||||
|
||||
if (ArcSegment::are_points_within_slice(test_arc, points))
|
||||
{
|
||||
target_arc = test_arc;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ArcSegment::try_create_arc(
|
||||
const Circle& c,
|
||||
const Point& start_point,
|
||||
const Point& mid_point,
|
||||
const Point& end_point,
|
||||
ArcSegment& target_arc,
|
||||
double approximate_length,
|
||||
double path_tolerance_percent)
|
||||
{
|
||||
double polar_start_theta = c.get_polar_radians(start_point);
|
||||
double polar_mid_theta = c.get_polar_radians(mid_point);
|
||||
double polar_end_theta = c.get_polar_radians(end_point);
|
||||
|
||||
double angle_radians = 0;
|
||||
ArcDirection direction = ArcDirection::Arc_Dir_unknow;
|
||||
//QDS: calculate the direction of the arc
|
||||
if (polar_end_theta > polar_start_theta) {
|
||||
if (polar_start_theta < polar_mid_theta && polar_mid_theta < polar_end_theta) {
|
||||
direction = ArcDirection::Arc_Dir_CCW;
|
||||
angle_radians = polar_end_theta - polar_start_theta;
|
||||
} else if ((0.0 <= polar_mid_theta && polar_mid_theta < polar_start_theta) ||
|
||||
(polar_end_theta < polar_mid_theta && polar_mid_theta < (2.0 * PI))) {
|
||||
direction = ArcDirection::Arc_Dir_CW;
|
||||
angle_radians = polar_start_theta + ((2.0 * PI) - polar_end_theta);
|
||||
}
|
||||
} else if (polar_start_theta > polar_end_theta) {
|
||||
if ((polar_start_theta < polar_mid_theta && polar_mid_theta < (2.0 * PI)) ||
|
||||
(0.0 < polar_mid_theta && polar_mid_theta < polar_end_theta)) {
|
||||
direction = ArcDirection::Arc_Dir_CCW;
|
||||
angle_radians = polar_end_theta + ((2.0 * PI) - polar_start_theta);
|
||||
} else if (polar_end_theta < polar_mid_theta && polar_mid_theta < polar_start_theta) {
|
||||
direction = ArcDirection::Arc_Dir_CW;
|
||||
angle_radians = polar_start_theta - polar_end_theta;
|
||||
}
|
||||
}
|
||||
|
||||
// QDS: this doesn't always work.. in rare situations, the angle may be backward
|
||||
if (direction == ArcDirection::Arc_Dir_unknow || std::fabs(angle_radians) < EPSILON)
|
||||
return false;
|
||||
|
||||
// QDS: Check the length against the original length.
|
||||
// This can trigger simply due to the differing path lengths
|
||||
// but also could indicate that the vector calculation above
|
||||
// got wrong direction
|
||||
double arc_length = c.radius * angle_radians;
|
||||
double difference = (arc_length - approximate_length) / approximate_length;
|
||||
if (std::fabs(difference) >= path_tolerance_percent)
|
||||
{
|
||||
// QDS: So it's possible that vector calculation above got wrong direction.
|
||||
// This can happen if there is a crazy arrangement of points
|
||||
// extremely close to eachother. They have to be close enough to
|
||||
// break our other checks. However, we may be able to salvage this.
|
||||
// see if an arc moving in the opposite direction had the correct length.
|
||||
|
||||
//QDS: Find the rest of the angle across the circle
|
||||
double test_radians = std::fabs(angle_radians - 2 * PI);
|
||||
// Calculate the length of that arc
|
||||
double test_arc_length = c.radius * test_radians;
|
||||
difference = (test_arc_length - approximate_length) / approximate_length;
|
||||
if (std::fabs(difference) >= path_tolerance_percent)
|
||||
return false;
|
||||
//QDS: Set the new length and flip the direction (but not the angle)!
|
||||
arc_length = test_arc_length;
|
||||
direction = direction == ArcDirection::Arc_Dir_CCW ? ArcDirection::Arc_Dir_CW : ArcDirection::Arc_Dir_CCW;
|
||||
}
|
||||
|
||||
if (direction == ArcDirection::Arc_Dir_CW)
|
||||
angle_radians *= -1.0;
|
||||
|
||||
target_arc.is_arc = true;
|
||||
target_arc.direction = direction;
|
||||
target_arc.center = c.center;
|
||||
target_arc.radius = c.radius;
|
||||
target_arc.start_point = start_point;
|
||||
target_arc.end_point = end_point;
|
||||
target_arc.length = arc_length;
|
||||
target_arc.angle_radians = angle_radians;
|
||||
target_arc.polar_start_theta = polar_start_theta;
|
||||
target_arc.polar_end_theta = polar_end_theta;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArcSegment::are_points_within_slice(const ArcSegment& test_arc, const Points& points)
|
||||
{
|
||||
//QDS: Check all the points and see if they fit inside of the angles
|
||||
double previous_polar = test_arc.polar_start_theta;
|
||||
bool will_cross_zero = false;
|
||||
bool crossed_zero = false;
|
||||
const int point_count = points.size();
|
||||
|
||||
Vec2d start_norm(((double)test_arc.start_point.x() - (double)test_arc.center.x()) / test_arc.radius,
|
||||
((double)test_arc.start_point.y() - (double)test_arc.center.y()) / test_arc.radius);
|
||||
Vec2d end_norm(((double)test_arc.end_point.x() - (double)test_arc.center.x()) / test_arc.radius,
|
||||
((double)test_arc.end_point.y() - (double)test_arc.center.y()) / test_arc.radius);
|
||||
|
||||
if (test_arc.direction == ArcDirection::Arc_Dir_CCW)
|
||||
will_cross_zero = test_arc.polar_start_theta > test_arc.polar_end_theta;
|
||||
else
|
||||
will_cross_zero = test_arc.polar_start_theta < test_arc.polar_end_theta;
|
||||
|
||||
//QDS: check if point 1 to point 2 cross zero
|
||||
double polar_test;
|
||||
for (int index = point_count - 2; index < point_count; index++)
|
||||
{
|
||||
if (index < point_count - 1)
|
||||
polar_test = test_arc.get_polar_radians(points[index]);
|
||||
else
|
||||
polar_test = test_arc.polar_end_theta;
|
||||
|
||||
//QDS: First ensure the test point is within the arc
|
||||
if (test_arc.direction == ArcDirection::Arc_Dir_CCW)
|
||||
{
|
||||
//QDS: Only check to see if we are within the arc if this isn't the endpoint
|
||||
if (index < point_count - 1) {
|
||||
if (will_cross_zero) {
|
||||
if (!(polar_test > test_arc.polar_start_theta || polar_test < test_arc.polar_end_theta))
|
||||
return false;
|
||||
} else if (!(test_arc.polar_start_theta < polar_test && polar_test < test_arc.polar_end_theta))
|
||||
return false;
|
||||
}
|
||||
//QDS: check the angles are increasing
|
||||
if (previous_polar > polar_test) {
|
||||
if (!will_cross_zero)
|
||||
return false;
|
||||
|
||||
//QDS: Allow the angle to cross zero once
|
||||
if (crossed_zero)
|
||||
return false;
|
||||
crossed_zero = true;
|
||||
}
|
||||
} else {
|
||||
if (index < point_count - 1) {
|
||||
if (will_cross_zero) {
|
||||
if (!(polar_test < test_arc.polar_start_theta || polar_test > test_arc.polar_end_theta))
|
||||
return false;
|
||||
} else if (!(test_arc.polar_start_theta > polar_test && polar_test > test_arc.polar_end_theta))
|
||||
return false;
|
||||
}
|
||||
//QDS: Now make sure the angles are decreasing
|
||||
if (previous_polar < polar_test)
|
||||
{
|
||||
if (!will_cross_zero)
|
||||
return false;
|
||||
//QDS: Allow the angle to cross zero once
|
||||
if (crossed_zero)
|
||||
return false;
|
||||
crossed_zero = true;
|
||||
}
|
||||
}
|
||||
|
||||
// QDS: check if the segment intersects either of the vector from the center of the circle to the endpoints of the arc
|
||||
Line segmemt(points[index - 1], points[index]);
|
||||
if ((index != 1 && ray_intersects_segment(test_arc.center, start_norm, segmemt)) ||
|
||||
(index != point_count - 1 && ray_intersects_segment(test_arc.center, end_norm, segmemt)))
|
||||
return false;
|
||||
previous_polar = polar_test;
|
||||
}
|
||||
//QDS: Ensure that all arcs that cross zero
|
||||
if (will_cross_zero != crossed_zero)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// QDS: this function is used to detect whether a ray cross the segment
|
||||
bool ArcSegment::ray_intersects_segment(const Point &rayOrigin, const Vec2d &rayDirection, const Line& segment)
|
||||
{
|
||||
Vec2d v1 = Vec2d(rayOrigin.x() - segment.a.x(), rayOrigin.y() - segment.a.y());
|
||||
Vec2d v2 = Vec2d(segment.b.x() - segment.a.x(), segment.b.y() - segment.a.y());
|
||||
Vec2d v3 = Vec2d(-rayDirection(1), rayDirection(0));
|
||||
|
||||
double dot = v2(0) * v3(0) + v2(1) * v3(1);
|
||||
if (std::fabs(dot) < SCALED_EPSILON)
|
||||
return false;
|
||||
|
||||
double t1 = (v2(0) * v1(1) - v2(1) * v1(0)) / dot;
|
||||
double t2 = (v1(0) * v3(0) + v1(1) * v3(1)) / dot;
|
||||
|
||||
if (t1 >= 0.0 && (t2 >= 0.0 && t2 <= 1.0))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// QDS: new function to calculate arc radian in X-Y plane
|
||||
float ArcSegment::calc_arc_radian(Vec3f start_pos, Vec3f end_pos, Vec3f center_pos, bool is_ccw)
|
||||
{
|
||||
Vec3f delta1 = center_pos - start_pos;
|
||||
Vec3f delta2 = center_pos - end_pos;
|
||||
// only consider arc in x-y plane, so clean z distance
|
||||
delta1(2,0) = 0;
|
||||
delta2(2,0) = 0;
|
||||
|
||||
float radian;
|
||||
if ((delta1 - delta2).norm() < 1e-6) {
|
||||
// start_pos is same with end_pos, we think it's a full circle
|
||||
radian = 2 * M_PI;
|
||||
} else {
|
||||
double dot = delta1.dot(delta2);
|
||||
double cross = (double)delta1(0, 0) * (double)delta2(1, 0) - (double)delta1(1, 0) * (double)delta2(0, 0);
|
||||
radian = atan2(cross, dot);
|
||||
if (is_ccw)
|
||||
radian = (radian < 0) ? 2 * M_PI + radian : radian;
|
||||
else
|
||||
radian = (radian < 0) ? abs(radian) : 2 * M_PI - radian;
|
||||
}
|
||||
return radian;
|
||||
}
|
||||
|
||||
float ArcSegment::calc_arc_radius(Vec3f start_pos, Vec3f center_pos)
|
||||
{
|
||||
Vec3f delta1 = center_pos - start_pos;
|
||||
delta1(2,0) = 0;
|
||||
return delta1.norm();
|
||||
}
|
||||
|
||||
// QDS: new function to calculate arc length in X-Y plane
|
||||
float ArcSegment::calc_arc_length(Vec3f start_pos, Vec3f end_pos, Vec3f center_pos, bool is_ccw)
|
||||
{
|
||||
return calc_arc_radius(start_pos, center_pos) * calc_arc_radian(start_pos, end_pos, center_pos, is_ccw);
|
||||
}
|
||||
|
||||
}
|
||||
135
src/libslic3r/Circle.hpp
Normal file
135
src/libslic3r/Circle.hpp
Normal file
@@ -0,0 +1,135 @@
|
||||
#ifndef slic3r_Circle_hpp_
|
||||
#define slic3r_Circle_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "Line.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
constexpr double ZERO_TOLERANCE = 0.000005;
|
||||
|
||||
class Circle {
|
||||
public:
|
||||
Circle() {
|
||||
center = Point(0,0);
|
||||
radius = 0;
|
||||
}
|
||||
Circle(Point &p, double r) {
|
||||
center = p;
|
||||
radius = r;
|
||||
}
|
||||
Point center;
|
||||
double radius;
|
||||
|
||||
Point get_closest_point(const Point& input) {
|
||||
Vec2d v = (input - center).cast<double>().normalized();
|
||||
return (center + (v * radius).cast<coord_t>());
|
||||
}
|
||||
|
||||
static bool try_create_circle(const Point &p1, const Point &p2, const Point &p3, const double max_radius, Circle& new_circle);
|
||||
static bool try_create_circle(const Points& points, const double max_radius, const double tolerance, Circle& new_circle);
|
||||
double get_polar_radians(const Point& p1) const;
|
||||
bool is_over_deviation(const Points& points, const double tolerance);
|
||||
bool get_deviation_sum_squared(const Points& points, const double tolerance, double& sum_deviation);
|
||||
|
||||
//QDS: only support calculate on X-Y plane, Z is useless
|
||||
static Vec3f calc_tangential_vector(const Vec3f& pos, const Vec3f& center_pos, const bool is_ccw);
|
||||
static bool get_closest_perpendicular_point(const Point& p1, const Point& p2, const Point& c, Point& out);
|
||||
static bool is_equal(double x, double y, double tolerance = ZERO_TOLERANCE) {
|
||||
double abs_difference = std::fabs(x - y);
|
||||
return abs_difference < tolerance;
|
||||
};
|
||||
static bool greater_than(double x, double y, double tolerance = ZERO_TOLERANCE) {
|
||||
return x > y && !Circle::is_equal(x, y, tolerance);
|
||||
};
|
||||
static bool greater_than_or_equal(double x, double y, double tolerance = ZERO_TOLERANCE) {
|
||||
return x > y || Circle::is_equal(x, y, tolerance);
|
||||
};
|
||||
static bool less_than(double x, double y, double tolerance = ZERO_TOLERANCE) {
|
||||
return x < y && !Circle::is_equal(x, y, tolerance);
|
||||
};
|
||||
static bool less_than_or_equal(double x, double y, double tolerance = ZERO_TOLERANCE){
|
||||
return x < y || Circle::is_equal(x, y, tolerance);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
enum class ArcDirection : unsigned char {
|
||||
Arc_Dir_unknow,
|
||||
Arc_Dir_CCW,
|
||||
Arc_Dir_CW,
|
||||
Count
|
||||
};
|
||||
|
||||
#define DEFAULT_SCALED_MAX_RADIUS scale_(2000) // 2000mm
|
||||
#define DEFAULT_SCALED_RESOLUTION scale_(0.05) // 0.05mm
|
||||
#define DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE 0.05 // 5 percent
|
||||
|
||||
class ArcSegment: public Circle {
|
||||
public:
|
||||
ArcSegment(): Circle() {}
|
||||
ArcSegment(Point center, double radius, Point start, Point end, ArcDirection dir) :
|
||||
Circle(center, radius),
|
||||
start_point(start),
|
||||
end_point(end),
|
||||
direction(dir) {
|
||||
if (radius == 0.0 ||
|
||||
start_point == center ||
|
||||
end_point == center ||
|
||||
start_point == end_point) {
|
||||
is_arc = false;
|
||||
return;
|
||||
}
|
||||
update_angle_and_length();
|
||||
is_arc = true;
|
||||
}
|
||||
|
||||
bool is_arc = false;
|
||||
double length = 0;
|
||||
double angle_radians = 0;
|
||||
double polar_start_theta = 0;
|
||||
double polar_end_theta = 0;
|
||||
Point start_point { Point(0,0) };
|
||||
Point end_point{ Point(0,0) };
|
||||
ArcDirection direction = ArcDirection::Arc_Dir_unknow;
|
||||
|
||||
bool is_valid() const { return is_arc; }
|
||||
bool clip_start(const Point& point);
|
||||
bool clip_end(const Point& point);
|
||||
bool reverse();
|
||||
bool split_at(const Point& point, ArcSegment& p1, ArcSegment& p2);
|
||||
bool is_point_inside(const Point& point) const;
|
||||
|
||||
private:
|
||||
void update_angle_and_length();
|
||||
|
||||
public:
|
||||
static bool try_create_arc(
|
||||
const Points &points,
|
||||
ArcSegment& target_arc,
|
||||
double approximate_length,
|
||||
double max_radius = DEFAULT_SCALED_MAX_RADIUS,
|
||||
double tolerance = DEFAULT_SCALED_RESOLUTION,
|
||||
double path_tolerance_percent = DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE);
|
||||
|
||||
static bool are_points_within_slice(const ArcSegment& test_arc, const Points &points);
|
||||
// QDS: this function is used to detect whether a ray cross the segment
|
||||
static bool ray_intersects_segment(const Point& rayOrigin, const Vec2d& rayDirection, const Line& segment);
|
||||
// QDS: these three functions are used to calculate related arguments of arc in unscale_field.
|
||||
static float calc_arc_radian(Vec3f start_pos, Vec3f end_pos, Vec3f center_pos, bool is_ccw);
|
||||
static float calc_arc_radius(Vec3f start_pos, Vec3f center_pos);
|
||||
static float calc_arc_length(Vec3f start_pos, Vec3f end_pos, Vec3f center_pos, bool is_ccw);
|
||||
private:
|
||||
static bool try_create_arc(
|
||||
const Circle& c,
|
||||
const Point& start_point,
|
||||
const Point& mid_point,
|
||||
const Point& end_point,
|
||||
ArcSegment& target_arc,
|
||||
double approximate_length,
|
||||
double path_tolerance_percent = DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
60
src/libslic3r/Clipper2Utils.cpp
Normal file
60
src/libslic3r/Clipper2Utils.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "Clipper2Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//QDS: FIXME
|
||||
Slic3r::Polylines Paths64_to_polylines(const Clipper2Lib::Paths64& in)
|
||||
{
|
||||
Slic3r::Polylines out;
|
||||
out.reserve(in.size());
|
||||
for (const Clipper2Lib::Path64& path64 : in) {
|
||||
Slic3r::Points points;
|
||||
points.reserve(path64.size());
|
||||
for (const Clipper2Lib::Point64& point64 : path64)
|
||||
points.emplace_back(std::move(Slic3r::Point(point64.x, point64.y)));
|
||||
out.emplace_back(std::move(Slic3r::Polyline(points)));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
//QDS: FIXME
|
||||
template <typename T>
|
||||
Clipper2Lib::Paths64 Slic3rPoints_to_Paths64(const std::vector<T>& in)
|
||||
{
|
||||
Clipper2Lib::Paths64 out;
|
||||
out.reserve(in.size());
|
||||
for (const T item: in) {
|
||||
Clipper2Lib::Path64 path;
|
||||
path.reserve(item.size());
|
||||
for (const Slic3r::Point& point : item.points)
|
||||
path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y())));
|
||||
out.emplace_back(std::move(path));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Polylines _clipper2_pl_open(Clipper2Lib::ClipType clipType, const Slic3r::Polylines& subject, const Slic3r::Polygons& clip)
|
||||
{
|
||||
Clipper2Lib::Clipper64 c;
|
||||
c.AddOpenSubject(Slic3rPoints_to_Paths64(subject));
|
||||
c.AddClip(Slic3rPoints_to_Paths64(clip));
|
||||
|
||||
Clipper2Lib::ClipType ct = clipType;
|
||||
Clipper2Lib::FillRule fr = Clipper2Lib::FillRule::NonZero;
|
||||
Clipper2Lib::Paths64 solution, solution_open;
|
||||
c.Execute(ct, fr, solution, solution_open);
|
||||
|
||||
Slic3r::Polylines out;
|
||||
out.reserve(solution.size() + solution_open.size());
|
||||
polylines_append(out, std::move(Paths64_to_polylines(solution)));
|
||||
polylines_append(out, std::move(Paths64_to_polylines(solution_open)));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Slic3r::Polylines intersection_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip)
|
||||
{ return _clipper2_pl_open(Clipper2Lib::ClipType::Intersection, subject, clip); }
|
||||
Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip)
|
||||
{ return _clipper2_pl_open(Clipper2Lib::ClipType::Difference, subject, clip); }
|
||||
|
||||
}
|
||||
17
src/libslic3r/Clipper2Utils.hpp
Normal file
17
src/libslic3r/Clipper2Utils.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef slic3r_Clipper2Utils_hpp_
|
||||
#define slic3r_Clipper2Utils_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "clipper2/clipper.h"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
Slic3r::Polylines intersection_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip);
|
||||
Slic3r::Polylines diff_pl_2(const Slic3r::Polylines& subject, const Slic3r::Polygons& clip);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
1410
src/libslic3r/ClipperUtils.cpp
Normal file
1410
src/libslic3r/ClipperUtils.cpp
Normal file
File diff suppressed because it is too large
Load Diff
666
src/libslic3r/ClipperUtils.hpp
Normal file
666
src/libslic3r/ClipperUtils.hpp
Normal file
@@ -0,0 +1,666 @@
|
||||
#ifndef slic3r_ClipperUtils_hpp_
|
||||
#define slic3r_ClipperUtils_hpp_
|
||||
|
||||
//#define SLIC3R_USE_CLIPPER2
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Surface.hpp"
|
||||
|
||||
#ifdef SLIC3R_USE_CLIPPER2
|
||||
|
||||
#include <clipper2.clipper.h>
|
||||
|
||||
#else /* SLIC3R_USE_CLIPPER2 */
|
||||
|
||||
#include "clipper.hpp"
|
||||
// import these wherever we're included
|
||||
using Slic3r::ClipperLib::jtMiter;
|
||||
using Slic3r::ClipperLib::jtRound;
|
||||
using Slic3r::ClipperLib::jtSquare;
|
||||
|
||||
#endif /* SLIC3R_USE_CLIPPER2 */
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class BoundingBox;
|
||||
|
||||
static constexpr const float ClipperSafetyOffset = 10.f;
|
||||
|
||||
static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter;
|
||||
//FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2.
|
||||
// Mitter Limit 3 is useful for perimeter generator, where sharp corners are extruded without needing a gap fill.
|
||||
// However such a high limit causes issues with large positive or negative offsets, where a sharp corner
|
||||
// is extended excessively.
|
||||
static constexpr const double DefaultMiterLimit = 3.;
|
||||
|
||||
static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Slic3r::ClipperLib::jtSquare;
|
||||
// Miter limit is ignored for jtSquare.
|
||||
static constexpr const double DefaultLineMiterLimit = 0.;
|
||||
|
||||
// Decimation factor applied on input contour when doing offset, multiplied by the offset distance.
|
||||
static constexpr const double ClipperOffsetShortestEdgeFactor = 0.005;
|
||||
|
||||
enum class ApplySafetyOffset {
|
||||
No,
|
||||
Yes
|
||||
};
|
||||
|
||||
namespace ClipperUtils {
|
||||
class PathsProviderIteratorBase {
|
||||
public:
|
||||
using value_type = Points;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = const Points*;
|
||||
using reference = const Points&;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
};
|
||||
|
||||
class EmptyPathsProvider {
|
||||
public:
|
||||
struct iterator : public PathsProviderIteratorBase {
|
||||
public:
|
||||
const Points& operator*() { assert(false); return s_empty_points; }
|
||||
// all iterators point to end.
|
||||
constexpr bool operator==(const iterator &rhs) const { return true; }
|
||||
constexpr bool operator!=(const iterator &rhs) const { return false; }
|
||||
const Points& operator++(int) { assert(false); return s_empty_points; }
|
||||
const iterator& operator++() { assert(false); return *this; }
|
||||
};
|
||||
|
||||
constexpr EmptyPathsProvider() {}
|
||||
static constexpr iterator cend() throw() { return iterator{}; }
|
||||
static constexpr iterator end() throw() { return cend(); }
|
||||
static constexpr iterator cbegin() throw() { return cend(); }
|
||||
static constexpr iterator begin() throw() { return cend(); }
|
||||
static constexpr size_t size() throw() { return 0; }
|
||||
|
||||
static Points s_empty_points;
|
||||
};
|
||||
|
||||
class SinglePathProvider {
|
||||
public:
|
||||
SinglePathProvider(const Points &points) : m_points(points) {}
|
||||
|
||||
struct iterator : public PathsProviderIteratorBase {
|
||||
public:
|
||||
explicit iterator(const Points &points) : m_ptr(&points) {}
|
||||
const Points& operator*() const { return *m_ptr; }
|
||||
bool operator==(const iterator &rhs) const { return m_ptr == rhs.m_ptr; }
|
||||
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
|
||||
const Points& operator++(int) { auto out = m_ptr; m_ptr = &s_end; return *out; }
|
||||
iterator& operator++() { m_ptr = &s_end; return *this; }
|
||||
private:
|
||||
const Points *m_ptr;
|
||||
};
|
||||
|
||||
iterator cbegin() const { return iterator(m_points); }
|
||||
iterator begin() const { return this->cbegin(); }
|
||||
iterator cend() const { return iterator(s_end); }
|
||||
iterator end() const { return this->cend(); }
|
||||
size_t size() const { return 1; }
|
||||
|
||||
private:
|
||||
const Points &m_points;
|
||||
static Points s_end;
|
||||
};
|
||||
|
||||
template<typename PathType>
|
||||
class PathsProvider {
|
||||
public:
|
||||
PathsProvider(const std::vector<PathType> &paths) : m_paths(paths) {}
|
||||
|
||||
struct iterator : public PathsProviderIteratorBase {
|
||||
public:
|
||||
explicit iterator(typename std::vector<PathType>::const_iterator it) : m_it(it) {}
|
||||
const Points& operator*() const { return *m_it; }
|
||||
bool operator==(const iterator &rhs) const { return m_it == rhs.m_it; }
|
||||
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
|
||||
const Points& operator++(int) { return *(m_it ++); }
|
||||
iterator& operator++() { ++ m_it; return *this; }
|
||||
private:
|
||||
typename std::vector<PathType>::const_iterator m_it;
|
||||
};
|
||||
|
||||
iterator cbegin() const { return iterator(m_paths.begin()); }
|
||||
iterator begin() const { return this->cbegin(); }
|
||||
iterator cend() const { return iterator(m_paths.end()); }
|
||||
iterator end() const { return this->cend(); }
|
||||
size_t size() const { return m_paths.size(); }
|
||||
|
||||
private:
|
||||
const std::vector<PathType> &m_paths;
|
||||
};
|
||||
|
||||
template<typename MultiPointType>
|
||||
class MultiPointsProvider {
|
||||
public:
|
||||
MultiPointsProvider(const std::vector<MultiPointType> &multipoints) : m_multipoints(multipoints) {}
|
||||
|
||||
struct iterator : public PathsProviderIteratorBase {
|
||||
public:
|
||||
explicit iterator(typename std::vector<MultiPointType>::const_iterator it) : m_it(it) {}
|
||||
const Points& operator*() const { return m_it->points; }
|
||||
bool operator==(const iterator &rhs) const { return m_it == rhs.m_it; }
|
||||
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
|
||||
const Points& operator++(int) { return (m_it ++)->points; }
|
||||
iterator& operator++() { ++ m_it; return *this; }
|
||||
private:
|
||||
typename std::vector<MultiPointType>::const_iterator m_it;
|
||||
};
|
||||
|
||||
iterator cbegin() const { return iterator(m_multipoints.begin()); }
|
||||
iterator begin() const { return this->cbegin(); }
|
||||
iterator cend() const { return iterator(m_multipoints.end()); }
|
||||
iterator end() const { return this->cend(); }
|
||||
size_t size() const { return m_multipoints.size(); }
|
||||
|
||||
private:
|
||||
const std::vector<MultiPointType> &m_multipoints;
|
||||
};
|
||||
|
||||
using PolygonsProvider = MultiPointsProvider<Polygon>;
|
||||
using PolylinesProvider = MultiPointsProvider<Polyline>;
|
||||
|
||||
struct ExPolygonProvider {
|
||||
ExPolygonProvider(const ExPolygon &expoly) : m_expoly(expoly) {}
|
||||
|
||||
struct iterator : public PathsProviderIteratorBase {
|
||||
public:
|
||||
explicit iterator(const ExPolygon &expoly, int idx) : m_expoly(expoly), m_idx(idx) {}
|
||||
const Points& operator*() const { return (m_idx == 0) ? m_expoly.contour.points : m_expoly.holes[m_idx - 1].points; }
|
||||
bool operator==(const iterator &rhs) const { assert(m_expoly == rhs.m_expoly); return m_idx == rhs.m_idx; }
|
||||
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
|
||||
const Points& operator++(int) { const Points &out = **this; ++ m_idx; return out; }
|
||||
iterator& operator++() { ++ m_idx; return *this; }
|
||||
private:
|
||||
const ExPolygon &m_expoly;
|
||||
int m_idx;
|
||||
};
|
||||
|
||||
iterator cbegin() const { return iterator(m_expoly, 0); }
|
||||
iterator begin() const { return this->cbegin(); }
|
||||
iterator cend() const { return iterator(m_expoly, m_expoly.holes.size() + 1); }
|
||||
iterator end() const { return this->cend(); }
|
||||
size_t size() const { return m_expoly.holes.size() + 1; }
|
||||
|
||||
private:
|
||||
const ExPolygon &m_expoly;
|
||||
};
|
||||
|
||||
struct ExPolygonsProvider {
|
||||
ExPolygonsProvider(const ExPolygons &expolygons) : m_expolygons(expolygons) {
|
||||
m_size = 0;
|
||||
for (const ExPolygon &expoly : expolygons)
|
||||
m_size += expoly.holes.size() + 1;
|
||||
}
|
||||
|
||||
struct iterator : public PathsProviderIteratorBase {
|
||||
public:
|
||||
explicit iterator(ExPolygons::const_iterator it) : m_it_expolygon(it), m_idx_contour(0) {}
|
||||
const Points& operator*() const { return (m_idx_contour == 0) ? m_it_expolygon->contour.points : m_it_expolygon->holes[m_idx_contour - 1].points; }
|
||||
bool operator==(const iterator &rhs) const { return m_it_expolygon == rhs.m_it_expolygon && m_idx_contour == rhs.m_idx_contour; }
|
||||
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
|
||||
iterator& operator++() {
|
||||
if (++ m_idx_contour == m_it_expolygon->holes.size() + 1) {
|
||||
++ m_it_expolygon;
|
||||
m_idx_contour = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
const Points& operator++(int) {
|
||||
const Points &out = **this;
|
||||
++ (*this);
|
||||
return out;
|
||||
}
|
||||
private:
|
||||
ExPolygons::const_iterator m_it_expolygon;
|
||||
size_t m_idx_contour;
|
||||
};
|
||||
|
||||
iterator cbegin() const { return iterator(m_expolygons.cbegin()); }
|
||||
iterator begin() const { return this->cbegin(); }
|
||||
iterator cend() const { return iterator(m_expolygons.cend()); }
|
||||
iterator end() const { return this->cend(); }
|
||||
size_t size() const { return m_size; }
|
||||
|
||||
private:
|
||||
const ExPolygons &m_expolygons;
|
||||
size_t m_size;
|
||||
};
|
||||
|
||||
struct SurfacesProvider {
|
||||
SurfacesProvider(const Surfaces &surfaces) : m_surfaces(surfaces) {
|
||||
m_size = 0;
|
||||
for (const Surface &surface : surfaces)
|
||||
m_size += surface.expolygon.holes.size() + 1;
|
||||
}
|
||||
|
||||
struct iterator : public PathsProviderIteratorBase {
|
||||
public:
|
||||
explicit iterator(Surfaces::const_iterator it) : m_it_surface(it), m_idx_contour(0) {}
|
||||
const Points& operator*() const { return (m_idx_contour == 0) ? m_it_surface->expolygon.contour.points : m_it_surface->expolygon.holes[m_idx_contour - 1].points; }
|
||||
bool operator==(const iterator &rhs) const { return m_it_surface == rhs.m_it_surface && m_idx_contour == rhs.m_idx_contour; }
|
||||
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
|
||||
iterator& operator++() {
|
||||
if (++ m_idx_contour == m_it_surface->expolygon.holes.size() + 1) {
|
||||
++ m_it_surface;
|
||||
m_idx_contour = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
const Points& operator++(int) {
|
||||
const Points &out = **this;
|
||||
++ (*this);
|
||||
return out;
|
||||
}
|
||||
private:
|
||||
Surfaces::const_iterator m_it_surface;
|
||||
size_t m_idx_contour;
|
||||
};
|
||||
|
||||
iterator cbegin() const { return iterator(m_surfaces.cbegin()); }
|
||||
iterator begin() const { return this->cbegin(); }
|
||||
iterator cend() const { return iterator(m_surfaces.cend()); }
|
||||
iterator end() const { return this->cend(); }
|
||||
size_t size() const { return m_size; }
|
||||
|
||||
private:
|
||||
const Surfaces &m_surfaces;
|
||||
size_t m_size;
|
||||
};
|
||||
|
||||
struct SurfacesPtrProvider {
|
||||
SurfacesPtrProvider(const SurfacesPtr &surfaces) : m_surfaces(surfaces) {
|
||||
m_size = 0;
|
||||
for (const Surface *surface : surfaces)
|
||||
m_size += surface->expolygon.holes.size() + 1;
|
||||
}
|
||||
|
||||
struct iterator : public PathsProviderIteratorBase {
|
||||
public:
|
||||
explicit iterator(SurfacesPtr::const_iterator it) : m_it_surface(it), m_idx_contour(0) {}
|
||||
const Points& operator*() const { return (m_idx_contour == 0) ? (*m_it_surface)->expolygon.contour.points : (*m_it_surface)->expolygon.holes[m_idx_contour - 1].points; }
|
||||
bool operator==(const iterator &rhs) const { return m_it_surface == rhs.m_it_surface && m_idx_contour == rhs.m_idx_contour; }
|
||||
bool operator!=(const iterator &rhs) const { return !(*this == rhs); }
|
||||
iterator& operator++() {
|
||||
if (++ m_idx_contour == (*m_it_surface)->expolygon.holes.size() + 1) {
|
||||
++ m_it_surface;
|
||||
m_idx_contour = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
const Points& operator++(int) {
|
||||
const Points &out = **this;
|
||||
++ (*this);
|
||||
return out;
|
||||
}
|
||||
private:
|
||||
SurfacesPtr::const_iterator m_it_surface;
|
||||
size_t m_idx_contour;
|
||||
};
|
||||
|
||||
iterator cbegin() const { return iterator(m_surfaces.cbegin()); }
|
||||
iterator begin() const { return this->cbegin(); }
|
||||
iterator cend() const { return iterator(m_surfaces.cend()); }
|
||||
iterator end() const { return this->cend(); }
|
||||
size_t size() const { return m_size; }
|
||||
|
||||
private:
|
||||
const SurfacesPtr &m_surfaces;
|
||||
size_t m_size;
|
||||
};
|
||||
|
||||
|
||||
// For ClipperLib with Z coordinates.
|
||||
using ZPoint = Vec3i32;
|
||||
using ZPoints = std::vector<Vec3i32>;
|
||||
|
||||
// Clip source polygon to be used as a clipping polygon with a bouding box around the source (to be clipped) polygon.
|
||||
// Useful as an optimization for expensive ClipperLib operations, for example when clipping source polygons one by one
|
||||
// with a set of polygons covering the whole layer below.
|
||||
void clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox, Points &out, const bool get_entire_polygons = false);
|
||||
void clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox, ZPoints &out);
|
||||
[[nodiscard]] Points clip_clipper_polygon_with_subject_bbox(const Points &src, const BoundingBox &bbox);
|
||||
[[nodiscard]] ZPoints clip_clipper_polygon_with_subject_bbox(const ZPoints &src, const BoundingBox &bbox);
|
||||
void clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, Polygon &out);
|
||||
[[nodiscard]] Polygon clip_clipper_polygon_with_subject_bbox(const Polygon &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
|
||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const Polygons &src, const BoundingBox &bbox);
|
||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygon &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
|
||||
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
|
||||
|
||||
}
|
||||
|
||||
// Perform union of input polygons using the non-zero rule, convert to ExPolygons.
|
||||
ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, bool do_union = false);
|
||||
|
||||
// offset Polygons
|
||||
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
|
||||
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
|
||||
// offset Polylines
|
||||
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
|
||||
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons offset(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
// QDS
|
||||
inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{
|
||||
Slic3r::Polygons temp;
|
||||
temp.push_back(polygon);
|
||||
|
||||
return offset_ex(temp, delta, joinType, miterLimit);
|
||||
}
|
||||
|
||||
inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); }
|
||||
inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); }
|
||||
inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) { return offset_ex(polygons, ClipperSafetyOffset); }
|
||||
inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons) { return offset_ex(expolygons, ClipperSafetyOffset); }
|
||||
|
||||
Slic3r::Polygons union_safety_offset(const Slic3r::Polygons &expolygons);
|
||||
Slic3r::Polygons union_safety_offset(const Slic3r::ExPolygons &expolygons);
|
||||
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons);
|
||||
Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::ExPolygons &expolygons);
|
||||
|
||||
// Aliases for the various offset(...) functions, conveying the purpose of the offset.
|
||||
inline Slic3r::Polygons expand(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset(polygon, delta, joinType, miterLimit); }
|
||||
inline Slic3r::Polygons expand(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset(polygons, delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons expand_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset_ex(polygons, delta, joinType, miterLimit); }
|
||||
// Input polygons for shrinking shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons shrink_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
|
||||
// Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better.
|
||||
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
Slic3r::Polygons offset2(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset2_ex(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::ExPolygons offset2_ex(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
|
||||
// QDS
|
||||
Slic3r::ExPolygons _clipper_ex(ClipperLib::ClipType clipType,
|
||||
const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false);
|
||||
|
||||
|
||||
// Offset outside, then inside produces morphological closing. All deltas should be positive.
|
||||
Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
inline Slic3r::Polygons closing(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return closing(polygons, delta, delta, joinType, miterLimit); }
|
||||
Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
inline Slic3r::ExPolygons closing_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return closing_ex(polygons, delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons closing_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset2_ex(polygons, delta, - delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons closing_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset2_ex(surfaces, delta, - delta, joinType, miterLimit); }
|
||||
|
||||
// Offset inside, then outside produces morphological opening. All deltas should be positive.
|
||||
// Input polygons for opening shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta1, const float delta2, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||
inline Slic3r::Polygons opening(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return opening(polygons, delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::Polygons opening(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return opening(expolygons, delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::Polygons opening(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ return opening(surfaces, delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons opening_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset2_ex(polygons, - delta, delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons opening_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset2_ex(surfaces, - delta, delta, joinType, miterLimit); }
|
||||
|
||||
Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, const Slic3r::Polygons &clip);
|
||||
|
||||
// Safety offset is applied to the clipping polygons only.
|
||||
Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
// Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox().
|
||||
// To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon.
|
||||
Slic3r::Polygons diff_clipped(const Slic3r::Polygons &src, const Slic3r::Polygons &clipping, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_clipped(const Slic3r::ExPolygons &src, const Slic3r::Polygons &clipping, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_clipped(const Slic3r::ExPolygons &src, const Slic3r::ExPolygons &clipping, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons diff(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Polygon &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polyline& subject, const Slic3r::Polygons& clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygons &clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip);
|
||||
|
||||
// QDS
|
||||
inline Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon& subject, const Slic3r::ExPolygon& clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No)
|
||||
{
|
||||
Slic3r::ExPolygons subject_temp;
|
||||
Slic3r::ExPolygons clip_temp;
|
||||
|
||||
subject_temp.push_back(subject);
|
||||
clip_temp.push_back(clip);
|
||||
return diff_ex(subject_temp, clip_temp);
|
||||
}
|
||||
|
||||
inline Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon& subject, const Slic3r::ExPolygons& clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No)
|
||||
{
|
||||
Slic3r::ExPolygons subject_temp;
|
||||
subject_temp.push_back(subject);
|
||||
|
||||
return diff_ex(subject_temp, clip, do_safety_offset);
|
||||
}
|
||||
|
||||
inline Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons& subject, const Slic3r::ExPolygon& clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No)
|
||||
{
|
||||
Slic3r::ExPolygons clip_temp;
|
||||
clip_temp.push_back(clip);
|
||||
|
||||
return diff_ex(subject, clip_temp, do_safety_offset);
|
||||
}
|
||||
|
||||
inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip)
|
||||
{
|
||||
return _clipper_ln(ClipperLib::ctDifference, subject, clip);
|
||||
}
|
||||
|
||||
// Safety offset is applied to the clipping polygons only.
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
// Optimized version clipping the "clipping" polygon using clip_clipper_polygon_with_subject_bbox().
|
||||
// To be used with complex clipping polygons, where majority of the clipping polygons are outside of the source polygon.
|
||||
Slic3r::Polygons intersection_clipped(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polygons intersection(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
// QDS
|
||||
Slic3r::Polygons intersection(const Slic3r::Polygons& subject, const Slic3r::Polygon& clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon& subject, const Slic3r::ExPolygon& clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons& subject, const Slic3r::ExPolygon& clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygon& subject, const Slic3r::ExPolygons& clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygons &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip);
|
||||
Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip);
|
||||
|
||||
inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip)
|
||||
{
|
||||
return _clipper_ln(ClipperLib::ctIntersection, subject, clip);
|
||||
}
|
||||
|
||||
inline Slic3r::Lines intersection_ln(const Slic3r::Line &subject, const Slic3r::Polygons &clip)
|
||||
{
|
||||
Slic3r::Lines lines;
|
||||
lines.emplace_back(subject);
|
||||
return _clipper_ln(ClipperLib::ctIntersection, lines, clip);
|
||||
}
|
||||
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject);
|
||||
Slic3r::Polygons union_(const Slic3r::ExPolygons &subject);
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const ClipperLib::PolyFillType fillType);
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2);
|
||||
Slic3r::Polygons union_(const Slic3r::Polygons &subject, const Slic3r::ExPolygon &subject2);
|
||||
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, ClipperLib::PolyFillType fill_type = ClipperLib::pftNonZero);
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject);
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject);
|
||||
|
||||
Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons& poly1, const Slic3r::ExPolygons& poly2, bool safety_offset_ = false);
|
||||
|
||||
// Convert polygons / expolygons into ClipperLib::PolyTree using ClipperLib::pftEvenOdd, thus union will NOT be performed.
|
||||
// If the contours are not intersecting, their orientation shall not be modified by union_pt().
|
||||
ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject);
|
||||
ClipperLib::PolyTree union_pt(const Slic3r::ExPolygons &subject);
|
||||
|
||||
Slic3r::Polygons union_pt_chained_outside_in(const Slic3r::Polygons &subject);
|
||||
|
||||
ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes);
|
||||
|
||||
// Implementing generalized loop (foreach) over a list of nodes which can be
|
||||
// ordered or unordered (performance gain) based on template parameter
|
||||
enum class e_ordering {
|
||||
ON,
|
||||
OFF
|
||||
};
|
||||
|
||||
// Create a template struct, template functions can not be partially specialized
|
||||
template<e_ordering o, class Fn> struct _foreach_node {
|
||||
void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn);
|
||||
};
|
||||
|
||||
// Specialization with NO ordering
|
||||
template<class Fn> struct _foreach_node<e_ordering::OFF, Fn> {
|
||||
void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn)
|
||||
{
|
||||
for (auto &n : nodes) fn(n);
|
||||
}
|
||||
};
|
||||
|
||||
// Specialization with ordering
|
||||
template<class Fn> struct _foreach_node<e_ordering::ON, Fn> {
|
||||
void operator()(const ClipperLib::PolyNodes &nodes, Fn &&fn)
|
||||
{
|
||||
auto ordered_nodes = order_nodes(nodes);
|
||||
for (auto &n : nodes) fn(n);
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper function for the foreach_node which can deduce arguments automatically
|
||||
template<e_ordering o, class Fn>
|
||||
void foreach_node(const ClipperLib::PolyNodes &nodes, Fn &&fn)
|
||||
{
|
||||
_foreach_node<o, Fn>()(nodes, std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
// Collecting polygons of the tree into a list of Polygons, holes have clockwise
|
||||
// orientation.
|
||||
template<e_ordering ordering = e_ordering::OFF>
|
||||
void traverse_pt(const ClipperLib::PolyNode *tree, Polygons *out)
|
||||
{
|
||||
if (!tree) return; // terminates recursion
|
||||
|
||||
// Push the contour of the current level
|
||||
out->emplace_back(tree->Contour);
|
||||
|
||||
// Do the recursion for all the children.
|
||||
traverse_pt<ordering>(tree->Childs, out);
|
||||
}
|
||||
|
||||
// Collecting polygons of the tree into a list of ExPolygons.
|
||||
template<e_ordering ordering = e_ordering::OFF>
|
||||
void traverse_pt(const ClipperLib::PolyNode *tree, ExPolygons *out)
|
||||
{
|
||||
if (!tree) return;
|
||||
else if(tree->IsHole()) {
|
||||
// Levels of holes are skipped and handled together with the
|
||||
// contour levels.
|
||||
traverse_pt<ordering>(tree->Childs, out);
|
||||
return;
|
||||
}
|
||||
|
||||
ExPolygon level;
|
||||
level.contour.points = tree->Contour;
|
||||
|
||||
foreach_node<ordering>(tree->Childs,
|
||||
[out, &level] (const ClipperLib::PolyNode *node) {
|
||||
|
||||
// Holes are collected here.
|
||||
level.holes.emplace_back(node->Contour);
|
||||
|
||||
// By doing a recursion, a new level expoly is created with the contour
|
||||
// and holes of the lower level. Doing this for all the childs.
|
||||
traverse_pt<ordering>(node->Childs, out);
|
||||
});
|
||||
|
||||
out->emplace_back(level);
|
||||
}
|
||||
|
||||
template<e_ordering o = e_ordering::OFF, class ExOrJustPolygons>
|
||||
void traverse_pt(const ClipperLib::PolyNodes &nodes, ExOrJustPolygons *retval)
|
||||
{
|
||||
foreach_node<o>(nodes, [&retval](const ClipperLib::PolyNode *node) {
|
||||
traverse_pt<o>(node, retval);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* OTHER */
|
||||
Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false);
|
||||
Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject, bool preserve_collinear = false);
|
||||
|
||||
Polygons top_level_islands(const Slic3r::Polygons &polygons);
|
||||
|
||||
ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::vector<float> &deltas, double miter_limit);
|
||||
Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
|
||||
Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
|
||||
ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
|
||||
ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
147
src/libslic3r/ClipperZUtils.hpp
Normal file
147
src/libslic3r/ClipperZUtils.hpp
Normal file
@@ -0,0 +1,147 @@
|
||||
///|/ Copyright (c) Prusa Research 2022 - 2023 Vojtěch Bubník @bubnikv
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef slic3r_ClipperZUtils_hpp_
|
||||
#define slic3r_ClipperZUtils_hpp_
|
||||
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include <clipper/clipper_z.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace ClipperZUtils {
|
||||
|
||||
using ZPoint = ClipperLib_Z::IntPoint;
|
||||
using ZPoints = ClipperLib_Z::Path;
|
||||
using ZPath = ClipperLib_Z::Path;
|
||||
using ZPaths = ClipperLib_Z::Paths;
|
||||
|
||||
inline bool zpoint_lower(const ZPoint &l, const ZPoint &r)
|
||||
{
|
||||
return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && l.z() < r.z())));
|
||||
}
|
||||
|
||||
// Convert a single path to path with a given Z coordinate.
|
||||
// If Open, then duplicate the first point at the end.
|
||||
template<bool Open = false>
|
||||
inline ZPath to_zpath(const Points &path, coord_t z)
|
||||
{
|
||||
ZPath out;
|
||||
if (! path.empty()) {
|
||||
out.reserve((path.size() + Open) ? 1 : 0);
|
||||
for (const Point &p : path)
|
||||
out.emplace_back(p.x(), p.y(), z);
|
||||
if (Open)
|
||||
out.emplace_back(out.front());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert multiple paths to paths with a given Z coordinate.
|
||||
// If Open, then duplicate the first point of each path at its end.
|
||||
template<bool Open = false>
|
||||
inline ZPaths to_zpaths(const VecOfPoints &paths, coord_t z)
|
||||
{
|
||||
ZPaths out;
|
||||
out.reserve(paths.size());
|
||||
for (const Points &path : paths)
|
||||
out.emplace_back(to_zpath<Open>(path, z));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert multiple expolygons into z-paths with Z specified by an index of the source expolygon
|
||||
// offsetted by base_index.
|
||||
// If Open, then duplicate the first point of each path at its end.
|
||||
template<bool Open = false>
|
||||
inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t &base_idx)
|
||||
{
|
||||
ZPaths out;
|
||||
out.reserve(std::accumulate(src.begin(), src.end(), size_t(0),
|
||||
[](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); }));
|
||||
for (const ExPolygon &expoly : src) {
|
||||
out.emplace_back(to_zpath<Open>(expoly.contour.points, base_idx));
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
out.emplace_back(to_zpath<Open>(hole.points, base_idx));
|
||||
++ base_idx;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert a single path to path with a given Z coordinate.
|
||||
// If Open, then duplicate the first point at the end.
|
||||
template<bool Open = false>
|
||||
inline Points from_zpath(const ZPoints &path)
|
||||
{
|
||||
Points out;
|
||||
if (! path.empty()) {
|
||||
out.reserve((path.size() + Open) ? 1 : 0);
|
||||
for (const ZPoint &p : path)
|
||||
out.emplace_back(p.x(), p.y());
|
||||
if (Open)
|
||||
out.emplace_back(out.front());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Convert multiple paths to paths with a given Z coordinate.
|
||||
// If Open, then duplicate the first point of each path at its end.
|
||||
template<bool Open = false>
|
||||
inline void from_zpaths(const ZPaths &paths, VecOfPoints &out)
|
||||
{
|
||||
out.reserve(out.size() + paths.size());
|
||||
for (const ZPoints &path : paths)
|
||||
out.emplace_back(from_zpath<Open>(path));
|
||||
}
|
||||
template<bool Open = false>
|
||||
inline VecOfPoints from_zpaths(const ZPaths &paths)
|
||||
{
|
||||
VecOfPoints out;
|
||||
from_zpaths(paths, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
class ClipperZIntersectionVisitor {
|
||||
public:
|
||||
using Intersection = std::pair<coord_t, coord_t>;
|
||||
using Intersections = std::vector<Intersection>;
|
||||
ClipperZIntersectionVisitor(Intersections &intersections) : m_intersections(intersections) {}
|
||||
void reset() { m_intersections.clear(); }
|
||||
void operator()(const ZPoint &e1bot, const ZPoint &e1top, const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt) {
|
||||
coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() };
|
||||
coord_t *begin = srcs;
|
||||
coord_t *end = srcs + 4;
|
||||
//FIXME buqdte sort manually?
|
||||
std::sort(begin, end);
|
||||
end = std::unique(begin, end);
|
||||
if (begin + 1 == end) {
|
||||
// Self intersection may happen on source contour. Just copy the Z value.
|
||||
pt.z() = *begin;
|
||||
} else {
|
||||
assert(begin + 2 == end);
|
||||
if (begin + 2 <= end) {
|
||||
// store a -1 based negative index into the "intersections" vector here.
|
||||
m_intersections.emplace_back(srcs[0], srcs[1]);
|
||||
pt.z() = -coord_t(m_intersections.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
ClipperLib_Z::ZFillCallback clipper_callback() {
|
||||
return [this](const ZPoint &e1bot, const ZPoint &e1top,
|
||||
const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt)
|
||||
{ return (*this)(e1bot, e1top, e2bot, e2top, pt); };
|
||||
}
|
||||
|
||||
const std::vector<std::pair<coord_t, coord_t>>& intersections() const { return m_intersections; }
|
||||
|
||||
private:
|
||||
std::vector<std::pair<coord_t, coord_t>> &m_intersections;
|
||||
};
|
||||
|
||||
} // namespace ClipperZUtils
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_ClipperZUtils_hpp_
|
||||
433
src/libslic3r/Color.cpp
Normal file
433
src/libslic3r/Color.cpp
Normal file
@@ -0,0 +1,433 @@
|
||||
#include "libslic3r.h"
|
||||
#include "Color.hpp"
|
||||
|
||||
#include <random>
|
||||
|
||||
static const float INV_255 = 1.0f / 255.0f;
|
||||
|
||||
namespace Slic3r {
|
||||
bool color_is_equal(const RGBA a, const RGBA& b)
|
||||
{
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
if (abs(a[i] - b[i]) > 0.01) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Conversion from RGB to HSV color space
|
||||
// The input RGB values are in the range [0, 1]
|
||||
// The output HSV values are in the ranges h = [0, 360], and s, v = [0, 1]
|
||||
static void RGBtoHSV(float r, float g, float b, float& h, float& s, float& v)
|
||||
{
|
||||
assert(0.0f <= r && r <= 1.0f);
|
||||
assert(0.0f <= g && g <= 1.0f);
|
||||
assert(0.0f <= b && b <= 1.0f);
|
||||
|
||||
const float max_comp = std::max(std::max(r, g), b);
|
||||
const float min_comp = std::min(std::min(r, g), b);
|
||||
const float delta = max_comp - min_comp;
|
||||
|
||||
if (delta > 0.0f) {
|
||||
if (max_comp == r)
|
||||
h = 60.0f * (std::fmod(((g - b) / delta), 6.0f));
|
||||
else if (max_comp == g)
|
||||
h = 60.0f * (((b - r) / delta) + 2.0f);
|
||||
else if (max_comp == b)
|
||||
h = 60.0f * (((r - g) / delta) + 4.0f);
|
||||
|
||||
s = (max_comp > 0.0f) ? delta / max_comp : 0.0f;
|
||||
}
|
||||
else {
|
||||
h = 0.0f;
|
||||
s = 0.0f;
|
||||
}
|
||||
v = max_comp;
|
||||
|
||||
while (h < 0.0f) { h += 360.0f; }
|
||||
while (h > 360.0f) { h -= 360.0f; }
|
||||
|
||||
assert(0.0f <= s && s <= 1.0f);
|
||||
assert(0.0f <= v && v <= 1.0f);
|
||||
assert(0.0f <= h && h <= 360.0f);
|
||||
}
|
||||
|
||||
// Conversion from HSV to RGB color space
|
||||
// The input HSV values are in the ranges h = [0, 360], and s, v = [0, 1]
|
||||
// The output RGB values are in the range [0, 1]
|
||||
static void HSVtoRGB(float h, float s, float v, float& r, float& g, float& b)
|
||||
{
|
||||
assert(0.0f <= s && s <= 1.0f);
|
||||
assert(0.0f <= v && v <= 1.0f);
|
||||
assert(0.0f <= h && h <= 360.0f);
|
||||
|
||||
const float chroma = v * s;
|
||||
const float h_prime = std::fmod(h / 60.0f, 6.0f);
|
||||
const float x = chroma * (1.0f - std::abs(std::fmod(h_prime, 2.0f) - 1.0f));
|
||||
const float m = v - chroma;
|
||||
|
||||
if (0.0f <= h_prime && h_prime < 1.0f) {
|
||||
r = chroma;
|
||||
g = x;
|
||||
b = 0.0f;
|
||||
}
|
||||
else if (1.0f <= h_prime && h_prime < 2.0f) {
|
||||
r = x;
|
||||
g = chroma;
|
||||
b = 0.0f;
|
||||
}
|
||||
else if (2.0f <= h_prime && h_prime < 3.0f) {
|
||||
r = 0.0f;
|
||||
g = chroma;
|
||||
b = x;
|
||||
}
|
||||
else if (3.0f <= h_prime && h_prime < 4.0f) {
|
||||
r = 0.0f;
|
||||
g = x;
|
||||
b = chroma;
|
||||
}
|
||||
else if (4.0f <= h_prime && h_prime < 5.0f) {
|
||||
r = x;
|
||||
g = 0.0f;
|
||||
b = chroma;
|
||||
}
|
||||
else if (5.0f <= h_prime && h_prime < 6.0f) {
|
||||
r = chroma;
|
||||
g = 0.0f;
|
||||
b = x;
|
||||
}
|
||||
else {
|
||||
r = 0.0f;
|
||||
g = 0.0f;
|
||||
b = 0.0f;
|
||||
}
|
||||
|
||||
r += m;
|
||||
g += m;
|
||||
b += m;
|
||||
|
||||
assert(0.0f <= r && r <= 1.0f);
|
||||
assert(0.0f <= g && g <= 1.0f);
|
||||
assert(0.0f <= b && b <= 1.0f);
|
||||
}
|
||||
|
||||
class Randomizer
|
||||
{
|
||||
std::random_device m_rd;
|
||||
|
||||
public:
|
||||
float random_float(float min, float max) {
|
||||
std::mt19937 rand_generator(m_rd());
|
||||
std::uniform_real_distribution<float> distrib(min, max);
|
||||
return distrib(rand_generator);
|
||||
}
|
||||
};
|
||||
|
||||
ColorRGB::ColorRGB(float r, float g, float b)
|
||||
: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f) })
|
||||
{
|
||||
}
|
||||
|
||||
ColorRGB::ColorRGB(unsigned char r, unsigned char g, unsigned char b)
|
||||
: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f) })
|
||||
{
|
||||
}
|
||||
|
||||
bool ColorRGB::operator < (const ColorRGB& other) const
|
||||
{
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
if (m_data[i] < other.m_data[i])
|
||||
return true;
|
||||
else if (m_data[i] > other.m_data[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ColorRGB::operator > (const ColorRGB& other) const
|
||||
{
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
if (m_data[i] > other.m_data[i])
|
||||
return true;
|
||||
else if (m_data[i] < other.m_data[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ColorRGB ColorRGB::operator + (const ColorRGB& other) const
|
||||
{
|
||||
ColorRGB ret;
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ColorRGB ColorRGB::operator * (float value) const
|
||||
{
|
||||
assert(value >= 0.0f);
|
||||
ColorRGB ret;
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ColorRGBA::ColorRGBA(float r, float g, float b, float a)
|
||||
: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f), std::clamp(a, 0.0f, 1.0f) })
|
||||
{
|
||||
}
|
||||
|
||||
ColorRGBA::ColorRGBA(std::array<float, 4> color)
|
||||
: m_data({std::clamp(color[0], 0.0f, 1.0f), std::clamp(color[1], 0.0f, 1.0f), std::clamp(color[2], 0.0f, 1.0f), std::clamp(color[3], 0.0f, 1.0f)})
|
||||
{
|
||||
|
||||
}
|
||||
ColorRGBA::ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
|
||||
: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f), std::clamp(a * INV_255, 0.0f, 1.0f) })
|
||||
{
|
||||
}
|
||||
|
||||
bool ColorRGBA::operator < (const ColorRGBA& other) const
|
||||
{
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
if (m_data[i] < other.m_data[i])
|
||||
return true;
|
||||
else if (m_data[i] > other.m_data[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ColorRGBA::operator > (const ColorRGBA& other) const
|
||||
{
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
if (m_data[i] > other.m_data[i])
|
||||
return true;
|
||||
else if (m_data[i] < other.m_data[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ColorRGBA ColorRGBA::operator + (const ColorRGBA& other) const
|
||||
{
|
||||
ColorRGBA ret;
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ColorRGBA ColorRGBA::operator * (float value) const
|
||||
{
|
||||
assert(value >= 0.0f);
|
||||
ColorRGBA ret;
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f);
|
||||
}
|
||||
ret.m_data[3] = this->m_data[3];
|
||||
return ret;
|
||||
}
|
||||
|
||||
ColorRGB operator * (float value, const ColorRGB& other) { return other * value; }
|
||||
ColorRGBA operator * (float value, const ColorRGBA& other) { return other * value; }
|
||||
|
||||
ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t)
|
||||
{
|
||||
assert(0.0f <= t && t <= 1.0f);
|
||||
return (1.0f - t) * a + t * b;
|
||||
}
|
||||
|
||||
ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t)
|
||||
{
|
||||
assert(0.0f <= t && t <= 1.0f);
|
||||
return (1.0f - t) * a + t * b;
|
||||
}
|
||||
|
||||
ColorRGB complementary(const ColorRGB& color)
|
||||
{
|
||||
return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b() };
|
||||
}
|
||||
|
||||
ColorRGBA complementary(const ColorRGBA& color)
|
||||
{
|
||||
return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b(), color.a() };
|
||||
}
|
||||
|
||||
ColorRGB saturate(const ColorRGB& color, float factor)
|
||||
{
|
||||
float h, s, v;
|
||||
RGBtoHSV(color.r(), color.g(), color.b(), h, s, v);
|
||||
s = std::clamp(s * factor, 0.0f, 1.0f);
|
||||
float r, g, b;
|
||||
HSVtoRGB(h, s, v, r, g, b);
|
||||
return { r, g, b };
|
||||
}
|
||||
|
||||
ColorRGBA saturate(const ColorRGBA& color, float factor)
|
||||
{
|
||||
return to_rgba(saturate(to_rgb(color), factor), color.a());
|
||||
}
|
||||
|
||||
ColorRGB opposite(const ColorRGB& color)
|
||||
{
|
||||
float h, s, v;
|
||||
RGBtoHSV(color.r(), color.g(), color.b(), h, s, v);
|
||||
|
||||
h += 65.0f; // 65 instead 60 to avoid circle values
|
||||
if (h > 360.0f)
|
||||
h -= 360.0f;
|
||||
|
||||
Randomizer rnd;
|
||||
s = rnd.random_float(0.65f, 1.0f);
|
||||
v = rnd.random_float(0.65f, 1.0f);
|
||||
|
||||
float r, g, b;
|
||||
HSVtoRGB(h, s, v, r, g, b);
|
||||
return { r, g, b };
|
||||
}
|
||||
|
||||
ColorRGB opposite(const ColorRGB& a, const ColorRGB& b)
|
||||
{
|
||||
float ha, sa, va;
|
||||
RGBtoHSV(a.r(), a.g(), a.b(), ha, sa, va);
|
||||
float hb, sb, vb;
|
||||
RGBtoHSV(b.r(), b.g(), b.b(), hb, sb, vb);
|
||||
|
||||
float delta_h = std::abs(ha - hb);
|
||||
float start_h = (delta_h > 180.0f) ? std::min(ha, hb) : std::max(ha, hb);
|
||||
|
||||
start_h += 5.0f; // to avoid circle change of colors for 120 deg
|
||||
if (delta_h < 180.0f)
|
||||
delta_h = 360.0f - delta_h;
|
||||
|
||||
Randomizer rnd;
|
||||
float out_h = start_h + 0.5f * delta_h;
|
||||
if (out_h > 360.0f)
|
||||
out_h -= 360.0f;
|
||||
float out_s = rnd.random_float(0.65f, 1.0f);
|
||||
float out_v = rnd.random_float(0.65f, 1.0f);
|
||||
|
||||
float out_r, out_g, out_b;
|
||||
HSVtoRGB(out_h, out_s, out_v, out_r, out_g, out_b);
|
||||
return { out_r, out_g, out_b };
|
||||
}
|
||||
|
||||
bool can_decode_color(const std::string &color)
|
||||
{
|
||||
return (color.size() == 7 && color.front() == '#') || (color.size() == 9 && color.front() == '#');
|
||||
}
|
||||
|
||||
bool decode_color(const std::string& color_in, ColorRGB& color_out)
|
||||
{
|
||||
ColorRGBA rgba;
|
||||
if (!decode_color(color_in, rgba))
|
||||
return false;
|
||||
|
||||
color_out = to_rgb(rgba);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool decode_color(const std::string& color_in, ColorRGBA& color_out)
|
||||
{
|
||||
auto hex_digit_to_int = [](const char c) {
|
||||
return
|
||||
(c >= '0' && c <= '9') ? int(c - '0') :
|
||||
(c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
|
||||
(c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
|
||||
};
|
||||
|
||||
color_out = ColorRGBA::BLACK();
|
||||
if (can_decode_color(color_in)) {
|
||||
const char *c = color_in.data() + 1;
|
||||
if (color_in.size() == 7) {
|
||||
for (unsigned int i = 0; i < 3; ++i) {
|
||||
const int digit1 = hex_digit_to_int(*c++);
|
||||
const int digit2 = hex_digit_to_int(*c++);
|
||||
if (digit1 != -1 && digit2 != -1)
|
||||
color_out.set(i, float(digit1 * 16 + digit2) * INV_255);
|
||||
}
|
||||
} else {
|
||||
for (unsigned int i = 0; i < 4; ++i) {
|
||||
const int digit1 = hex_digit_to_int(*c++);
|
||||
const int digit2 = hex_digit_to_int(*c++);
|
||||
if (digit1 != -1 && digit2 != -1)
|
||||
color_out.set(i, float(digit1 * 16 + digit2) * INV_255);
|
||||
}
|
||||
}
|
||||
} else
|
||||
return false;
|
||||
|
||||
assert(0.0f <= color_out.r() && color_out.r() <= 1.0f);
|
||||
assert(0.0f <= color_out.g() && color_out.g() <= 1.0f);
|
||||
assert(0.0f <= color_out.b() && color_out.b() <= 1.0f);
|
||||
assert(0.0f <= color_out.a() && color_out.a() <= 1.0f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool decode_colors(const std::vector<std::string>& colors_in, std::vector<ColorRGB>& colors_out)
|
||||
{
|
||||
colors_out = std::vector<ColorRGB>(colors_in.size(), ColorRGB::BLACK());
|
||||
for (size_t i = 0; i < colors_in.size(); ++i) {
|
||||
if (!decode_color(colors_in[i], colors_out[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool decode_colors(const std::vector<std::string>& colors_in, std::vector<ColorRGBA>& colors_out)
|
||||
{
|
||||
colors_out = std::vector<ColorRGBA>(colors_in.size(), ColorRGBA::BLACK());
|
||||
for (size_t i = 0; i < colors_in.size(); ++i) {
|
||||
if (!decode_color(colors_in[i], colors_out[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string encode_color(const ColorRGB& color)
|
||||
{
|
||||
char buffer[64];
|
||||
::sprintf(buffer, "#%02X%02X%02X", color.r_uchar(), color.g_uchar(), color.b_uchar());
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
std::string encode_color(const ColorRGBA& color) { return encode_color(to_rgb(color)); }
|
||||
|
||||
ColorRGB to_rgb(const ColorRGBA& other_rgba) { return { other_rgba.r(), other_rgba.g(), other_rgba.b() }; }
|
||||
ColorRGBA to_rgba(const ColorRGB& other_rgb) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), 1.0f }; }
|
||||
ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), alpha }; }
|
||||
|
||||
ColorRGBA picking_decode(unsigned int id)
|
||||
{
|
||||
return {
|
||||
float((id >> 0) & 0xff) * INV_255, // red
|
||||
float((id >> 8) & 0xff) * INV_255, // green
|
||||
float((id >> 16) & 0xff) * INV_255, // blue
|
||||
float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff)) * INV_255 // checksum for validating against unwanted alpha blending and multi sampling
|
||||
};
|
||||
}
|
||||
|
||||
unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b) { return r + (g << 8) + (b << 16); }
|
||||
|
||||
unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue)
|
||||
{
|
||||
// 8 bit hash for the color
|
||||
unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff;
|
||||
// Increase enthropy by a bit reversal
|
||||
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
|
||||
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
|
||||
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
|
||||
// Flip every second bit to increase the enthropy even more.
|
||||
b ^= 0x55;
|
||||
return b;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
186
src/libslic3r/Color.hpp
Normal file
186
src/libslic3r/Color.hpp
Normal file
@@ -0,0 +1,186 @@
|
||||
#ifndef slic3r_Color_hpp_
|
||||
#define slic3r_Color_hpp_
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
|
||||
namespace Slic3r {
|
||||
using RGB = std::array<float, 3>;
|
||||
using RGBA = std::array<float, 4>;
|
||||
const RGBA UNDEFINE_COLOR = {0,0,0,0};
|
||||
bool color_is_equal(const RGBA a, const RGBA &b);
|
||||
class ColorRGB
|
||||
{
|
||||
std::array<float, 3> m_data{1.0f, 1.0f, 1.0f};
|
||||
|
||||
public:
|
||||
ColorRGB() = default;
|
||||
ColorRGB(float r, float g, float b);
|
||||
ColorRGB(unsigned char r, unsigned char g, unsigned char b);
|
||||
ColorRGB(const ColorRGB& other) = default;
|
||||
|
||||
ColorRGB& operator = (const ColorRGB& other) { m_data = other.m_data; return *this; }
|
||||
|
||||
bool operator == (const ColorRGB& other) const { return m_data == other.m_data; }
|
||||
bool operator != (const ColorRGB& other) const { return !operator==(other); }
|
||||
bool operator < (const ColorRGB& other) const;
|
||||
bool operator > (const ColorRGB& other) const;
|
||||
|
||||
ColorRGB operator + (const ColorRGB& other) const;
|
||||
ColorRGB operator * (float value) const;
|
||||
|
||||
const float* const data() const { return m_data.data(); }
|
||||
|
||||
float r() const { return m_data[0]; }
|
||||
float g() const { return m_data[1]; }
|
||||
float b() const { return m_data[2]; }
|
||||
|
||||
void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); }
|
||||
void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); }
|
||||
void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); }
|
||||
|
||||
void set(unsigned int comp, float value) {
|
||||
assert(0 <= comp && comp <= 2);
|
||||
m_data[comp] = std::clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
unsigned char r_uchar() const { return static_cast<unsigned char>(m_data[0] * 255.0f); }
|
||||
unsigned char g_uchar() const { return static_cast<unsigned char>(m_data[1] * 255.0f); }
|
||||
unsigned char b_uchar() const { return static_cast<unsigned char>(m_data[2] * 255.0f); }
|
||||
|
||||
static const ColorRGB BLACK() { return { 0.0f, 0.0f, 0.0f }; }
|
||||
static const ColorRGB BLUE() { return { 0.0f, 0.0f, 1.0f }; }
|
||||
static const ColorRGB BLUEISH() { return { 0.5f, 0.5f, 1.0f }; }
|
||||
static const ColorRGB CYAN() { return { 0.0f, 1.0f, 1.0f }; }
|
||||
static const ColorRGB DARK_GRAY() { return { 0.25f, 0.25f, 0.25f }; }
|
||||
static const ColorRGB DARK_YELLOW() { return { 0.5f, 0.5f, 0.0f }; }
|
||||
static const ColorRGB GRAY() { return { 0.5f, 0.5f, 0.5f }; }
|
||||
static const ColorRGB GREEN() { return { 0.0f, 1.0f, 0.0f }; }
|
||||
static const ColorRGB GREENISH() { return { 0.5f, 1.0f, 0.5f }; }
|
||||
static const ColorRGB LIGHT_GRAY() { return { 0.75f, 0.75f, 0.75f }; }
|
||||
static const ColorRGB MAGENTA() { return { 1.0f, 0.0f, 1.0f }; }
|
||||
static const ColorRGB ORANGE() { return { 0.92f, 0.50f, 0.26f }; }
|
||||
static const ColorRGB RED() { return { 1.0f, 0.0f, 0.0f }; }
|
||||
static const ColorRGB REDISH() { return { 1.0f, 0.5f, 0.5f }; }
|
||||
static const ColorRGB YELLOW() { return { 1.0f, 1.0f, 0.0f }; }
|
||||
static const ColorRGB WHITE() { return { 1.0f, 1.0f, 1.0f }; }
|
||||
//B
|
||||
static const ColorRGB ORCA() { return {68.0f/ 255.0f, 121.f / 255.0f, 251.0f / 255}; }
|
||||
static const ColorRGB WARNING() { return {241.0f / 255, 117.f / 255.0f, 78.0f / 255}; }
|
||||
|
||||
static const ColorRGB X() { return { 0.75f, 0.0f, 0.0f }; }
|
||||
static const ColorRGB Y() { return { 0.0f, 0.75f, 0.0f }; }
|
||||
static const ColorRGB Z() { return { 0.0f, 0.0f, 0.75f }; }
|
||||
};
|
||||
|
||||
class ColorRGBA
|
||||
{
|
||||
std::array<float, 4> m_data{ 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
|
||||
public:
|
||||
ColorRGBA() = default;
|
||||
ColorRGBA(float r, float g, float b, float a);
|
||||
ColorRGBA(std::array<float, 4> color);
|
||||
ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a);
|
||||
ColorRGBA(const ColorRGBA& other) = default;
|
||||
|
||||
ColorRGBA& operator = (const ColorRGBA& other) { m_data = other.m_data; return *this; }
|
||||
|
||||
bool operator==(const ColorRGBA &other) const{
|
||||
return color_is_equal(m_data, other.m_data);
|
||||
}
|
||||
bool operator != (const ColorRGBA& other) const { return !operator==(other); }
|
||||
bool operator < (const ColorRGBA& other) const;
|
||||
bool operator > (const ColorRGBA& other) const;
|
||||
|
||||
ColorRGBA operator + (const ColorRGBA& other) const;
|
||||
ColorRGBA operator * (float value) const;
|
||||
|
||||
const float* const data() const { return m_data.data(); }
|
||||
const std::array<float, 4> &get_data() const { return m_data; }
|
||||
|
||||
float r() const { return m_data[0]; }
|
||||
float g() const { return m_data[1]; }
|
||||
float b() const { return m_data[2]; }
|
||||
float a() const { return m_data[3]; }
|
||||
|
||||
void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); }
|
||||
void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); }
|
||||
void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); }
|
||||
void a(float a) { m_data[3] = std::clamp(a, 0.0f, 1.0f); }
|
||||
|
||||
void set(unsigned int comp, float value) {
|
||||
assert(0 <= comp && comp <= 3);
|
||||
m_data[comp] = std::clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
unsigned char r_uchar() const { return static_cast<unsigned char>(m_data[0] * 255.0f); }
|
||||
unsigned char g_uchar() const { return static_cast<unsigned char>(m_data[1] * 255.0f); }
|
||||
unsigned char b_uchar() const { return static_cast<unsigned char>(m_data[2] * 255.0f); }
|
||||
unsigned char a_uchar() const { return static_cast<unsigned char>(m_data[3] * 255.0f); }
|
||||
|
||||
bool is_transparent() const { return m_data[3] < 1.0f; }
|
||||
|
||||
static const ColorRGBA BLACK() { return { 0.0f, 0.0f, 0.0f, 1.0f }; }
|
||||
static const ColorRGBA BLUE() { return { 0.0f, 0.0f, 1.0f, 1.0f }; }
|
||||
static const ColorRGBA BLUEISH() { return { 0.5f, 0.5f, 1.0f, 1.0f }; }
|
||||
static const ColorRGBA CYAN() { return { 0.0f, 1.0f, 1.0f, 1.0f }; }
|
||||
static const ColorRGBA DARK_GRAY() { return { 0.25f, 0.25f, 0.25f, 1.0f }; }
|
||||
static const ColorRGBA DARK_YELLOW() { return { 0.5f, 0.5f, 0.0f, 1.0f }; }
|
||||
static const ColorRGBA GRAY() { return { 0.5f, 0.5f, 0.5f, 1.0f }; }
|
||||
static const ColorRGBA GREEN() { return { 0.0f, 1.0f, 0.0f, 1.0f }; }
|
||||
static const ColorRGBA GREENISH() { return { 0.5f, 1.0f, 0.5f, 1.0f }; }
|
||||
static const ColorRGBA LIGHT_GRAY() { return { 0.75f, 0.75f, 0.75f, 1.0f }; }
|
||||
static const ColorRGBA MAGENTA() { return { 1.0f, 0.0f, 1.0f, 1.0f }; }
|
||||
static const ColorRGBA ORANGE() { return { 0.923f, 0.504f, 0.264f, 1.0f }; }
|
||||
static const ColorRGBA RED() { return { 1.0f, 0.0f, 0.0f, 1.0f }; }
|
||||
static const ColorRGBA REDISH() { return { 1.0f, 0.5f, 0.5f, 1.0f }; }
|
||||
static const ColorRGBA YELLOW() { return { 1.0f, 1.0f, 0.0f, 1.0f }; }
|
||||
static const ColorRGBA WHITE() { return { 1.0f, 1.0f, 1.0f, 1.0f }; }
|
||||
//B
|
||||
static const ColorRGBA ORCA() { return {68.0f / 255.0f, 121.f / 255.0f, 251.0f / 255, 1.0f}; }
|
||||
|
||||
static const ColorRGBA X() { return { 0.75f, 0.0f, 0.0f, 1.0f }; }
|
||||
static const ColorRGBA Y() { return { 0.0f, 0.75f, 0.0f, 1.0f }; }
|
||||
static const ColorRGBA Z() { return { 0.0f, 0.0f, 0.75f, 1.0f }; }
|
||||
};
|
||||
|
||||
ColorRGB operator * (float value, const ColorRGB& other);
|
||||
ColorRGBA operator * (float value, const ColorRGBA& other);
|
||||
|
||||
ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t);
|
||||
ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t);
|
||||
|
||||
ColorRGB complementary(const ColorRGB& color);
|
||||
ColorRGBA complementary(const ColorRGBA& color);
|
||||
|
||||
ColorRGB saturate(const ColorRGB& color, float factor);
|
||||
ColorRGBA saturate(const ColorRGBA& color, float factor);
|
||||
|
||||
ColorRGB opposite(const ColorRGB& color);
|
||||
ColorRGB opposite(const ColorRGB& a, const ColorRGB& b);
|
||||
|
||||
bool can_decode_color(const std::string& color);
|
||||
|
||||
bool decode_color(const std::string& color_in, ColorRGB& color_out);
|
||||
bool decode_color(const std::string& color_in, ColorRGBA& color_out);
|
||||
|
||||
bool decode_colors(const std::vector<std::string>& colors_in, std::vector<ColorRGB>& colors_out);
|
||||
bool decode_colors(const std::vector<std::string>& colors_in, std::vector<ColorRGBA>& colors_out);
|
||||
|
||||
std::string encode_color(const ColorRGB& color);
|
||||
std::string encode_color(const ColorRGBA& color);
|
||||
|
||||
ColorRGB to_rgb(const ColorRGBA& other_rgba);
|
||||
ColorRGBA to_rgba(const ColorRGB& other_rgb);
|
||||
ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha);
|
||||
|
||||
ColorRGBA picking_decode(unsigned int id);
|
||||
unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b);
|
||||
// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components
|
||||
// were not interpolated by alpha blending or multi sampling.
|
||||
unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Color_hpp_ */
|
||||
1780
src/libslic3r/Config.cpp
Normal file
1780
src/libslic3r/Config.cpp
Normal file
File diff suppressed because it is too large
Load Diff
2371
src/libslic3r/Config.hpp
Normal file
2371
src/libslic3r/Config.hpp
Normal file
File diff suppressed because it is too large
Load Diff
203
src/libslic3r/CurveAnalyzer.cpp
Normal file
203
src/libslic3r/CurveAnalyzer.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
#include "CurveAnalyzer.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
||||
static const int curvatures_sampling_number = 6;
|
||||
static const double curvatures_densify_width = 1; // mm
|
||||
static const double curvatures_sampling_width = 6; // mm
|
||||
static const double curvatures_angle_best = PI/6;
|
||||
static const double curvatures_angle_worst = 5*PI/6;
|
||||
|
||||
static const double curvatures_best = (curvatures_angle_best * 1000 / curvatures_sampling_width);
|
||||
static const double curvatures_worst = (curvatures_angle_worst * 1000 / curvatures_sampling_width);
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// This function is used to calculate curvature for paths.
|
||||
// Paths must be generated from a closed polygon.
|
||||
// Data in paths may be modify, and paths will be spilited and regenerated
|
||||
// arrording to different curve degree.
|
||||
void CurveAnalyzer::calculate_curvatures(ExtrusionPaths& paths, ECurveAnalyseMode mode)
|
||||
{
|
||||
Polygon polygon;
|
||||
std::vector<float> paths_length(paths.size(), 0.0);
|
||||
for (size_t i = 0; i < paths.size(); i++) {
|
||||
if (i == 0) {
|
||||
paths_length[i] = paths[i].polyline.length();
|
||||
}
|
||||
else {
|
||||
paths_length[i] = paths_length[i - 1] + paths[i].polyline.length();
|
||||
}
|
||||
polygon.points.insert(polygon.points.end(), paths[i].polyline.points.begin(), paths[i].polyline.points.end() - 1);
|
||||
}
|
||||
// 1 generate point series which is on the line of polygon, point distance along the polygon is smaller than 1mm
|
||||
polygon.densify(scale_(curvatures_densify_width));
|
||||
std::vector<float> polygon_length = polygon.parameter_by_length();
|
||||
|
||||
// 2 calculate angle of every segment
|
||||
size_t point_num = polygon.points.size();
|
||||
std::vector<float> angles(point_num, 0.f);
|
||||
for (size_t i = 0; i < point_num; i++) {
|
||||
size_t curr = i;
|
||||
size_t prev = (curr == 0) ? point_num - 1 : curr - 1;
|
||||
size_t next = (curr == point_num - 1) ? 0 : curr + 1;
|
||||
const Point v1 = polygon.points[curr] - polygon.points[prev];
|
||||
const Point v2 = polygon.points[next] - polygon.points[curr];
|
||||
int64_t dot = int64_t(v1(0)) * int64_t(v2(0)) + int64_t(v1(1)) * int64_t(v2(1));
|
||||
int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v1(1)) * int64_t(v2(0));
|
||||
if (mode == ECurveAnalyseMode::RelativeMode)
|
||||
cross = abs(cross);
|
||||
angles[curr] = float(atan2(double(cross), double(dot)));
|
||||
}
|
||||
|
||||
// 3 generate sum of angle and length of the adjacent segment for eveny point, range is approximately curvatures_sampling_width.
|
||||
// And then calculate the curvature
|
||||
std::vector<float> sum_angles(point_num, 0.f);
|
||||
std::vector<double> average_curvatures(point_num, 0.f);
|
||||
if (paths_length.back() < scale_(curvatures_sampling_width)) {
|
||||
// loop is too short, so the curvatures is max
|
||||
double temp = 1000.0 * 2.0 * PI / ((double)(paths_length.back()) * SCALING_FACTOR);
|
||||
for (size_t i = 0; i < point_num; i++) {
|
||||
average_curvatures[i] = temp;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (size_t i = 0; i < point_num; i++) {
|
||||
// right segment
|
||||
size_t j = i;
|
||||
float right_length = 0;
|
||||
while (right_length < scale_(curvatures_sampling_width / 2)) {
|
||||
int next_j = (j + 1 >= point_num) ? 0 : j + 1;
|
||||
sum_angles[i] += angles[j];
|
||||
right_length += (polygon.points[next_j] - polygon.points[j]).cast<float>().norm();
|
||||
j = next_j;
|
||||
}
|
||||
// left segment
|
||||
size_t k = i;
|
||||
float left_length = 0;
|
||||
while (left_length < scale_(curvatures_sampling_width / 2)) {
|
||||
size_t next_k = (k < 1) ? point_num - 1 : k - 1;
|
||||
sum_angles[i] += angles[k];
|
||||
left_length += (polygon.points[k] - polygon.points[next_k]).cast<float>().norm();
|
||||
k = next_k;
|
||||
}
|
||||
sum_angles[i] = sum_angles[i] - angles[i];
|
||||
average_curvatures[i] = (1000.0 * (double)abs(sum_angles[i]) / (double)curvatures_sampling_width);
|
||||
}
|
||||
}
|
||||
|
||||
// 4 calculate the degree of curve
|
||||
// For angle >= curvatures_angle_worst, we think it's enough to be worst. Should make the speed to be slowest.
|
||||
// For angle <= curvatures_angle_best, we thins it's enough to be best. Should make the speed to be fastest.
|
||||
// Use several steps [0 1 2...curvatures_sampling_number - 1] to describe the degree of curve. 0 is the flatest. curvatures_sampling_number - 1 is the sharpest
|
||||
std::vector<int> curvatures_norm(point_num, 0.f);
|
||||
std::vector<int> sampling_step(curvatures_sampling_number - 1, 0);
|
||||
for (size_t i = 0; i < curvatures_sampling_number - 1; i++) {
|
||||
sampling_step[i] = (2 * i + 1) * 50 / (curvatures_sampling_number - 1);
|
||||
}
|
||||
sampling_step[0] = 0;
|
||||
sampling_step[curvatures_sampling_number - 2] = 100;
|
||||
for (size_t i = 0; i < point_num; i++) {
|
||||
curvatures_norm[i] = (int)(100 * (average_curvatures[i] - curvatures_best) / (curvatures_worst - curvatures_best));
|
||||
if (curvatures_norm[i] >= 100)
|
||||
curvatures_norm[i] = curvatures_sampling_number - 1;
|
||||
else
|
||||
for (size_t j = 0; j < curvatures_sampling_number - 1; j++) {
|
||||
if (curvatures_norm[i] < sampling_step[j]) {
|
||||
curvatures_norm[i] = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<std::pair<std::pair<Point, int>, int>> curvature_list; // point, index, curve_degree
|
||||
int last_curvature_norm = -1;
|
||||
for (int i = 0; i < point_num; i++) {
|
||||
if (curvatures_norm[i] != last_curvature_norm) {
|
||||
last_curvature_norm = curvatures_norm[i];
|
||||
curvature_list.push_back(std::pair<std::pair<Point, int>, int>(std::pair<Point, int>(polygon.points[i], i), last_curvature_norm));
|
||||
}
|
||||
}
|
||||
curvature_list.push_back(std::pair<std::pair<Point, int>, int>(std::pair<Point, int>(polygon.points[0], point_num), curvatures_norm[0])); // the last point should be the first point
|
||||
|
||||
//5 split and modify the path according to the degree of curve
|
||||
if (curvature_list.size() == 2) { // all paths has same curva_degree
|
||||
for (size_t i = 0; i < paths.size(); i++) {
|
||||
paths[i].set_curve_degree(curvature_list[0].second);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ExtrusionPaths out;
|
||||
out.reserve(paths.size() + curvature_list.size() - 1);
|
||||
size_t j = 1;
|
||||
int current_curva_norm = curvature_list[0].second;
|
||||
for (size_t i = 0; i < paths.size() && j < curvature_list.size(); i++) {
|
||||
if (paths[i].last_point() == curvature_list[j].first.first) {
|
||||
paths[i].set_curve_degree(current_curva_norm);
|
||||
out.push_back(paths[i]);
|
||||
current_curva_norm = curvature_list[j].second;
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
else if (paths[i].first_point() == curvature_list[j].first.first) {
|
||||
if (paths[i].polyline.points.front() == paths[i].polyline.points.back()) {
|
||||
paths[i].set_curve_degree(current_curva_norm);
|
||||
out.push_back(paths[i]);
|
||||
current_curva_norm = curvature_list[j].second;
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
// should never happen
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (paths_length[i] <= polygon_length[curvature_list[j].first.second] ||
|
||||
paths[i].last_point() == curvature_list[j].first.first) {
|
||||
// save paths[i] directly
|
||||
paths[i].set_curve_degree(current_curva_norm);
|
||||
out.push_back(paths[i]);
|
||||
if (paths[i].last_point() == curvature_list[j].first.first) {
|
||||
current_curva_norm = curvature_list[j].second;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//split paths[i]
|
||||
ExtrusionPath current_path = paths[i];
|
||||
while (j < curvature_list.size()) {
|
||||
Polyline left, right;
|
||||
current_path.polyline.split_at(curvature_list[j].first.first, &left, &right);
|
||||
ExtrusionPath left_path(left, current_path);
|
||||
left_path.set_curve_degree(current_curva_norm);
|
||||
out.push_back(left_path);
|
||||
ExtrusionPath right_path(right, current_path);
|
||||
current_path = right_path;
|
||||
|
||||
current_curva_norm = curvature_list[j].second;
|
||||
j++;
|
||||
if (j < curvature_list.size() &&
|
||||
(paths_length[i] <= polygon_length[curvature_list[j].first.second] ||
|
||||
paths[i].last_point() == curvature_list[j].first.first)) {
|
||||
current_path.set_curve_degree(current_curva_norm);
|
||||
out.push_back(current_path);
|
||||
if (current_path.last_point() == curvature_list[j].first.first) {
|
||||
current_curva_norm = curvature_list[j].second;
|
||||
j++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paths.clear();
|
||||
paths.reserve(out.size());
|
||||
for (int i = 0; i < out.size(); i++) {
|
||||
paths.push_back(out[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
27
src/libslic3r/CurveAnalyzer.hpp
Normal file
27
src/libslic3r/CurveAnalyzer.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef slic3r_CurvaAnalyzer_hpp_
|
||||
#define slic3r_CurvaAnalyzer_hpp_
|
||||
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class ECurveAnalyseMode : unsigned char
|
||||
{
|
||||
RelativeMode,
|
||||
AbsoluteMode,
|
||||
Count
|
||||
};
|
||||
|
||||
//QDS: CurvaAnalyzer, ansolutely new file
|
||||
class CurveAnalyzer {
|
||||
public:
|
||||
// This function is used to calculate curvature for paths.
|
||||
// Paths must be generated from a closed polygon.
|
||||
// Data in paths may be modify, and paths will be spilited and regenerated
|
||||
// arrording to different curve degree.
|
||||
void calculate_curvatures(ExtrusionPaths& paths, ECurveAnalyseMode mode = ECurveAnalyseMode::RelativeMode);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
76
src/libslic3r/CustomGCode.cpp
Normal file
76
src/libslic3r/CustomGCode.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "CustomGCode.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include "GCodeWriter.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace CustomGCode {
|
||||
|
||||
//QDS: useless config and function
|
||||
#if 0
|
||||
// If loaded configuration has a "colorprint_heights" option (if it was imported from older Slicer),
|
||||
// and if CustomGCode::Info.gcodes is empty (there is no color print data available in a new format
|
||||
// then CustomGCode::Info.gcodes should be updated considering this option.
|
||||
extern void update_custom_gcode_per_print_z_from_config(Info& info, DynamicPrintConfig* config)
|
||||
{
|
||||
auto *colorprint_heights = config->option<ConfigOptionFloats>("colorprint_heights");
|
||||
if (colorprint_heights == nullptr)
|
||||
return;
|
||||
if (info.gcodes.empty() && ! colorprint_heights->values.empty()) {
|
||||
// Convert the old colorprint_heighs only if there is no equivalent data in a new format.
|
||||
const std::vector<std::string>& colors = ColorPrintColors::get();
|
||||
const auto& colorprint_values = colorprint_heights->values;
|
||||
info.gcodes.clear();
|
||||
info.gcodes.reserve(colorprint_values.size());
|
||||
int i = 0;
|
||||
for (auto val : colorprint_values)
|
||||
info.gcodes.emplace_back(Item{ val, ColorChange, 1, colors[(++i)%7] });
|
||||
|
||||
info.mode = SingleExtruder;
|
||||
}
|
||||
|
||||
// The "colorprint_heights" config value has been deprecated. At this point of time it has been converted
|
||||
// to a new format and therefore it shall be erased.
|
||||
config->erase("colorprint_heights");
|
||||
}
|
||||
#endif
|
||||
|
||||
// If information for custom Gcode per print Z was imported from older Slicer, mode will be undefined.
|
||||
// So, we should set CustomGCode::Info.mode should be updated considering code values from items.
|
||||
extern void check_mode_for_custom_gcode_per_print_z(Info& info)
|
||||
{
|
||||
if (info.mode != Undef)
|
||||
return;
|
||||
|
||||
bool is_single_extruder = true;
|
||||
for (auto item : info.gcodes)
|
||||
{
|
||||
if (item.type == ToolChange) {
|
||||
info.mode = MultiAsSingle;
|
||||
return;
|
||||
}
|
||||
if (item.type == ColorChange && item.extruder > 1)
|
||||
is_single_extruder = false;
|
||||
}
|
||||
|
||||
info.mode = is_single_extruder ? SingleExtruder : MultiExtruder;
|
||||
}
|
||||
|
||||
// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
|
||||
// print_z corresponds to the first layer printed with the new extruder.
|
||||
std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& custom_gcode_per_print_z, size_t num_extruders)
|
||||
{
|
||||
std::vector<std::pair<double, unsigned int>> custom_tool_changes;
|
||||
for (const Item& custom_gcode : custom_gcode_per_print_z.gcodes)
|
||||
if (custom_gcode.type == ToolChange) {
|
||||
// If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders
|
||||
assert(custom_gcode.extruder >= 0);
|
||||
custom_tool_changes.emplace_back(custom_gcode.print_z, static_cast<unsigned int>(size_t(custom_gcode.extruder) > num_extruders ? 1 : custom_gcode.extruder));
|
||||
}
|
||||
return custom_tool_changes;
|
||||
}
|
||||
|
||||
} // namespace CustomGCode
|
||||
|
||||
} // namespace Slic3r
|
||||
133
src/libslic3r/CustomGCode.hpp
Normal file
133
src/libslic3r/CustomGCode.hpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#ifndef slic3r_CustomGCode_hpp_
|
||||
#define slic3r_CustomGCode_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
|
||||
namespace CustomGCode {
|
||||
|
||||
enum Type
|
||||
{
|
||||
ColorChange,
|
||||
PausePrint,
|
||||
ToolChange,
|
||||
Template,
|
||||
Custom,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
struct Item
|
||||
{
|
||||
bool operator<(const Item& rhs) const { return this->print_z < rhs.print_z; }
|
||||
bool operator==(const Item& rhs) const
|
||||
{
|
||||
return (rhs.print_z == this->print_z ) &&
|
||||
(rhs.type == this->type ) &&
|
||||
(rhs.extruder == this->extruder ) &&
|
||||
(rhs.color == this->color ) &&
|
||||
(rhs.extra == this->extra );
|
||||
}
|
||||
bool operator!=(const Item& rhs) const { return ! (*this == rhs); }
|
||||
|
||||
double print_z;
|
||||
Type type;
|
||||
int extruder; // Informative value for ColorChangeCode and ToolChangeCode
|
||||
// "gcode" == ColorChangeCode => M600 will be applied for "extruder" extruder
|
||||
// "gcode" == ToolChangeCode => for whole print tool will be switched to "extruder" extruder
|
||||
std::string color; // if gcode is equal to PausePrintCode,
|
||||
// this field is used for save a short message shown on Printer display
|
||||
std::string extra; // this field is used for the extra data like :
|
||||
// - G-code text for the Type::Custom
|
||||
// - message text for the Type::PausePrint
|
||||
void from_json(const nlohmann::json& j) {
|
||||
std::string type_str;
|
||||
j.at("type").get_to(type_str);
|
||||
std::map<std::string,Type> str2type = { {"ColorChange", ColorChange },
|
||||
{"PausePrint",PausePrint},
|
||||
{"ToolChange",ToolChange},
|
||||
{"Template",Template},
|
||||
{"Custom",Custom},
|
||||
{"Unknown",Unknown} };
|
||||
type = Unknown;
|
||||
if (str2type.find(type_str) != str2type.end())
|
||||
type = str2type[type_str];
|
||||
j.at("print_z").get_to(print_z);
|
||||
j.at("color").get_to(color);
|
||||
j.at("extruder").get_to(extruder);
|
||||
if(j.contains("extra"))
|
||||
j.at("extra").get_to(extra);
|
||||
}
|
||||
};
|
||||
|
||||
enum Mode
|
||||
{
|
||||
Undef,
|
||||
SingleExtruder, // Single extruder printer preset is selected
|
||||
MultiAsSingle, // Multiple extruder printer preset is selected, but
|
||||
// this mode works just for Single extruder print
|
||||
// (The same extruder is assigned to all ModelObjects and ModelVolumes).
|
||||
MultiExtruder // Multiple extruder printer preset is selected
|
||||
};
|
||||
|
||||
// string anlogue of custom_code_per_height mode
|
||||
static constexpr char SingleExtruderMode[] = "SingleExtruder";
|
||||
static constexpr char MultiAsSingleMode [] = "MultiAsSingle";
|
||||
static constexpr char MultiExtruderMode [] = "MultiExtruder";
|
||||
|
||||
struct Info
|
||||
{
|
||||
Mode mode = Undef;
|
||||
std::vector<Item> gcodes;
|
||||
|
||||
bool operator==(const Info& rhs) const
|
||||
{
|
||||
return (rhs.mode == this->mode ) &&
|
||||
(rhs.gcodes == this->gcodes );
|
||||
}
|
||||
bool operator!=(const Info& rhs) const { return !(*this == rhs); }
|
||||
|
||||
void from_json(const nlohmann::json& j) {
|
||||
std::string mode_str;
|
||||
if (j.contains("mode"))
|
||||
j.at("mode").get_to(mode_str);
|
||||
if (mode_str == "SingleExtruder") mode = SingleExtruder;
|
||||
else if (mode_str == "MultiAsSingle") mode = MultiAsSingle;
|
||||
else if (mode_str == "MultiExtruder") mode = MultiExtruder;
|
||||
else mode = Undef;
|
||||
|
||||
auto j_gcodes = j.at("gcodes");
|
||||
gcodes.reserve(j_gcodes.size());
|
||||
for (auto& jj : j_gcodes) {
|
||||
Item item;
|
||||
item.from_json(jj);
|
||||
gcodes.push_back(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If loaded configuration has a "colorprint_heights" option (if it was imported from older Slicer),
|
||||
// and if CustomGCode::Info.gcodes is empty (there is no color print data available in a new format
|
||||
// then CustomGCode::Info.gcodes should be updated considering this option.
|
||||
//QDS
|
||||
//extern void update_custom_gcode_per_print_z_from_config(Info& info, DynamicPrintConfig* config);
|
||||
|
||||
// If information for custom Gcode per print Z was imported from older Slicer, mode will be undefined.
|
||||
// So, we should set CustomGCode::Info.mode should be updated considering code values from items.
|
||||
extern void check_mode_for_custom_gcode_per_print_z(Info& info);
|
||||
|
||||
// Return pairs of <print_z, 1-based extruder ID> sorted by increasing print_z from custom_gcode_per_print_z.
|
||||
// print_z corresponds to the first layer printed with the new extruder.
|
||||
std::vector<std::pair<double, unsigned int>> custom_tool_changes(const Info& custom_gcode_per_print_z, size_t num_extruders);
|
||||
|
||||
} // namespace CustomGCode
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
|
||||
#endif /* slic3r_CustomGCode_hpp_ */
|
||||
668
src/libslic3r/CutUtils.cpp
Normal file
668
src/libslic3r/CutUtils.cpp
Normal file
@@ -0,0 +1,668 @@
|
||||
#include "CutUtils.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "Model.hpp"
|
||||
#include "TriangleMeshSlicer.hpp"
|
||||
#include "TriangleSelector.hpp"
|
||||
#include "ObjectID.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
static void apply_tolerance(ModelVolume *vol)
|
||||
{
|
||||
ModelVolume::CutInfo &cut_info = vol->cut_info;
|
||||
|
||||
assert(cut_info.is_connector);
|
||||
if (!cut_info.is_processed) return;
|
||||
|
||||
Vec3d sf = vol->get_scaling_factor();
|
||||
|
||||
// make a "hole" wider
|
||||
sf[X] += double(cut_info.radius_tolerance);
|
||||
sf[Y] += double(cut_info.radius_tolerance);
|
||||
|
||||
// make a "hole" dipper
|
||||
sf[Z] += double(cut_info.height_tolerance);
|
||||
|
||||
vol->set_scaling_factor(sf);
|
||||
|
||||
// correct offset in respect to the new depth
|
||||
Vec3d rot_norm = rotation_transform(vol->get_rotation()) * Vec3d::UnitZ();
|
||||
if (rot_norm.norm() != 0.0) rot_norm.normalize();
|
||||
|
||||
double z_offset = 0.5 * static_cast<double>(cut_info.height_tolerance);
|
||||
if (cut_info.connector_type == CutConnectorType::Plug || cut_info.connector_type == CutConnectorType::Snap) z_offset -= 0.05; // add small Z offset to better preview
|
||||
|
||||
vol->set_offset(vol->get_offset() + rot_norm * z_offset);
|
||||
}
|
||||
|
||||
static void add_cut_volume(TriangleMesh & mesh,
|
||||
ModelObject * object,
|
||||
const ModelVolume *src_volume,
|
||||
const Transform3d &cut_matrix,
|
||||
const std::string &suffix = {},
|
||||
ModelVolumeType type = ModelVolumeType::MODEL_PART)
|
||||
{
|
||||
if (mesh.empty())
|
||||
return;
|
||||
|
||||
mesh.transform(cut_matrix);
|
||||
ModelVolume *vol = object->add_volume(mesh);
|
||||
vol->set_type(type);
|
||||
|
||||
vol->name = src_volume->name + suffix;
|
||||
// Don't copy the config's ID.
|
||||
vol->config.assign_config(src_volume->config);
|
||||
assert(vol->config.id().valid());
|
||||
assert(vol->config.id() != src_volume->config.id());
|
||||
vol->set_material(src_volume->material_id(), *src_volume->material());
|
||||
vol->cut_info = src_volume->cut_info;
|
||||
}
|
||||
|
||||
static void process_volume_cut(ModelVolume * volume,
|
||||
const Transform3d & instance_matrix,
|
||||
const Transform3d & cut_matrix,
|
||||
ModelObjectCutAttributes attributes,
|
||||
TriangleMesh & upper_mesh,
|
||||
TriangleMesh & lower_mesh)
|
||||
{
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
const Transformation cut_transformation = Transformation(cut_matrix);
|
||||
|
||||
const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset());
|
||||
|
||||
// Transform the mesh by the combined transformation matrix.
|
||||
// Flip the triangles in case the composite transformation is left handed.
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
|
||||
|
||||
indexed_triangle_set upper_its, lower_its;
|
||||
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
upper_mesh = TriangleMesh(upper_its);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
lower_mesh = TriangleMesh(lower_its);
|
||||
}
|
||||
|
||||
static void process_connector_cut(ModelVolume * volume,
|
||||
const Transform3d & instance_matrix,
|
||||
const Transform3d & cut_matrix,
|
||||
ModelObjectCutAttributes attributes,
|
||||
ModelObject * upper,
|
||||
ModelObject * lower,
|
||||
std::vector<ModelObject *> &dowels)
|
||||
{
|
||||
assert(volume->cut_info.is_connector);
|
||||
volume->cut_info.set_processed();
|
||||
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
|
||||
// ! Don't apply instance transformation for the conntectors.
|
||||
// This transformation is already there
|
||||
if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
ModelVolume *vol = nullptr;
|
||||
if (volume->cut_info.connector_type == CutConnectorType::Snap) {
|
||||
TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.));
|
||||
|
||||
vol = upper->add_volume(std::move(mesh));
|
||||
vol->set_transformation(volume->get_transformation());
|
||||
vol->set_type(ModelVolumeType::NEGATIVE_VOLUME);
|
||||
|
||||
vol->cut_info = volume->cut_info;
|
||||
vol->name = volume->name;
|
||||
} else
|
||||
vol = upper->add_volume(*volume);
|
||||
|
||||
vol->set_transformation(volume_matrix);
|
||||
apply_tolerance(vol);
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
ModelVolume *vol = lower->add_volume(*volume);
|
||||
vol->set_transformation(volume_matrix);
|
||||
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
}
|
||||
} else {
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
|
||||
ModelObject *dowel{nullptr};
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
volume->get_object()->clone_for_cut(&dowel);
|
||||
|
||||
// add one more solid part same as connector if this connector is a dowel
|
||||
ModelVolume *vol = dowel->add_volume(*volume);
|
||||
vol->set_type(ModelVolumeType::MODEL_PART);
|
||||
|
||||
// But discard rotation and Z-offset for this volume
|
||||
vol->set_rotation(Vec3d::Zero());
|
||||
vol->set_offset(Z, 0.0);
|
||||
|
||||
dowels.push_back(dowel);
|
||||
}
|
||||
|
||||
// Cut the dowel
|
||||
apply_tolerance(volume);
|
||||
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// add small Z offset to better preview
|
||||
upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast<float>());
|
||||
|
||||
// Add cut parts to the related objects
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
|
||||
}
|
||||
}
|
||||
|
||||
static void process_modifier_cut(ModelVolume *volume, const Transform3d &instance_matrix, const Transform3d &inverse_cut_matrix, ModelObjectCutAttributes attributes, ModelObject *upper, ModelObject *lower)
|
||||
{
|
||||
const auto volume_matrix = instance_matrix * volume->get_matrix();
|
||||
|
||||
// Modifiers are not cut, but we still need to add the instance transformation
|
||||
// to the modifier volume transformation to preserve their shape properly.
|
||||
volume->set_transformation(Transformation(volume_matrix));
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::CutToParts)) {
|
||||
upper->add_volume(*volume);
|
||||
return;
|
||||
}
|
||||
|
||||
// Some logic for the negative volumes/connectors. Add only needed modifiers
|
||||
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
|
||||
bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
|
||||
upper->add_volume(*volume);
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
|
||||
lower->add_volume(*volume);
|
||||
}
|
||||
|
||||
static void process_solid_part_cut(
|
||||
ModelVolume *volume, const Transform3d &instance_matrix, const Transform3d &cut_matrix, ModelObjectCutAttributes attributes, ModelObject *upper, ModelObject *lower)
|
||||
{
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
|
||||
|
||||
// Add required cut parts to the objects
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::CutToParts)) {
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
|
||||
if (!lower_mesh.empty()) {
|
||||
add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B");
|
||||
upper->volumes.back()->cut_info.is_from_upper = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) {
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_instance_transformation(ModelObject * object,
|
||||
size_t src_instance_idx,
|
||||
const Transform3d &cut_matrix = Transform3d::Identity(),
|
||||
bool place_on_cut = false,
|
||||
bool flip = false,
|
||||
bool is_set_offset = false,
|
||||
bool offset_pos_dir = true)
|
||||
{
|
||||
// Reset instance transformation except offset and Z-rotation
|
||||
|
||||
for (size_t i = 0; i < object->instances.size(); ++i) {
|
||||
auto & obj_instance = object->instances[i];
|
||||
const double rot_z = obj_instance->get_rotation().z();
|
||||
|
||||
Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor());
|
||||
// add respect to mirroring
|
||||
if (obj_instance->is_left_handed())
|
||||
inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1)));
|
||||
|
||||
obj_instance->set_transformation(inst_trafo);
|
||||
|
||||
if (is_set_offset && object->volumes.size() > 0) {
|
||||
BoundingBoxf3 curBox;
|
||||
for (size_t i = 0; i < object->volumes.size(); i++) {
|
||||
curBox.merge(object->volumes[i]->mesh().bounding_box());
|
||||
}
|
||||
auto offset_x = curBox.size().x() * 0.7 * (offset_pos_dir ? 1 : -1);
|
||||
Vec3d displace(offset_x,0,0);
|
||||
displace = rotation_transform(obj_instance->get_rotation()) * displace;
|
||||
obj_instance->set_offset(obj_instance->get_offset() + displace);
|
||||
}
|
||||
|
||||
Vec3d rotation = Vec3d::Zero();
|
||||
if (!flip && !place_on_cut) {
|
||||
if (i != src_instance_idx)
|
||||
rotation[Z] = rot_z;
|
||||
} else {
|
||||
Transform3d rotation_matrix = Transform3d::Identity();
|
||||
if (flip)
|
||||
rotation_matrix = rotation_transform(PI * Vec3d::UnitX());
|
||||
|
||||
if (place_on_cut)
|
||||
rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse();
|
||||
|
||||
if (i != src_instance_idx)
|
||||
rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix;
|
||||
|
||||
rotation = Transformation(rotation_matrix).get_rotation();
|
||||
}
|
||||
|
||||
obj_instance->set_rotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
Cut::Cut(const ModelObject * object,
|
||||
int instance,
|
||||
const Transform3d & cut_matrix,
|
||||
ModelObjectCutAttributes attributes /*= ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::CutToParts*/)
|
||||
: m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes)
|
||||
{
|
||||
m_model = Model();
|
||||
if (object) m_model.add_object(*object);
|
||||
}
|
||||
|
||||
void Cut::post_process(ModelObject *object, bool is_upper, ModelObjectPtrs &cut_object_ptrs, bool keep, bool place_on_cut, bool flip)
|
||||
{
|
||||
if (!object) return;
|
||||
|
||||
if (keep && !object->volumes.empty()) {
|
||||
reset_instance_transformation(object, m_instance, m_cut_matrix, place_on_cut, flip,set_offset_for_two_part, is_upper);
|
||||
cut_object_ptrs.push_back(object);
|
||||
} else
|
||||
m_model.objects.push_back(object); // will be deleted in m_model.clear_objects();
|
||||
}
|
||||
|
||||
void Cut::post_process(ModelObject *upper, ModelObject *lower, ModelObjectPtrs &cut_object_ptrs)
|
||||
{
|
||||
post_process(upper,true, cut_object_ptrs, m_attributes.has(ModelObjectCutAttribute::KeepUpper), m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
|
||||
m_attributes.has(ModelObjectCutAttribute::FlipUpper));
|
||||
|
||||
post_process(lower, false, cut_object_ptrs, m_attributes.has(ModelObjectCutAttribute::KeepLower), m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
|
||||
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower));
|
||||
}
|
||||
|
||||
void Cut::finalize(const ModelObjectPtrs &objects)
|
||||
{
|
||||
// clear model from temporarry objects
|
||||
m_model.clear_objects();
|
||||
|
||||
// add to model result objects
|
||||
m_model.objects = objects;
|
||||
}
|
||||
|
||||
const ModelObjectPtrs &Cut::perform_with_plane()
|
||||
{
|
||||
if (!m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !m_attributes.has(ModelObjectCutAttribute::KeepLower)) {
|
||||
m_model.clear_objects();
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
ModelObject *mo = m_model.objects.front();
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject *upper{nullptr};
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
mo->clone_for_cut(&upper);
|
||||
|
||||
ModelObject *lower{nullptr};
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !m_attributes.has(ModelObjectCutAttribute::CutToParts))
|
||||
mo->clone_for_cut(&lower);
|
||||
|
||||
if (upper && lower &&!m_attributes.has(ModelObjectCutAttribute::CutToParts)) {
|
||||
upper->name = upper->name + "_A";
|
||||
lower->name = lower->name + "_B";
|
||||
}
|
||||
std::vector<ModelObject *> dowels;
|
||||
|
||||
// Because transformations are going to be applied to meshes directly,
|
||||
// we reset transformation of all instances and volumes,
|
||||
// except for translation and Z-rotation on instances, which are preserved
|
||||
// in the transformation matrix and not applied to the mesh transform.
|
||||
|
||||
const auto instance_matrix = mo->instances[m_instance]->get_transformation().get_matrix_no_offset();
|
||||
const Transformation cut_transformation = Transformation(m_cut_matrix);
|
||||
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
|
||||
|
||||
for (ModelVolume *volume : mo->volumes) {
|
||||
volume->reset_extra_facets();
|
||||
|
||||
if (!volume->is_model_part()) {
|
||||
if (volume->cut_info.is_processed){
|
||||
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, m_attributes, upper, lower);
|
||||
}
|
||||
else{
|
||||
process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels);
|
||||
}
|
||||
} else if (!volume->mesh().empty()) {
|
||||
process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower);
|
||||
}
|
||||
}
|
||||
|
||||
// Post-process cut parts
|
||||
if (m_attributes.has(ModelObjectCutAttribute::CutToParts) && upper->volumes.empty()) {
|
||||
m_model = Model();
|
||||
m_model.objects.push_back(upper);
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
ModelObjectPtrs cut_object_ptrs;
|
||||
|
||||
if (m_attributes.has(ModelObjectCutAttribute::CutToParts) && !upper->volumes.empty()) {
|
||||
reset_instance_transformation(upper, m_instance, m_cut_matrix);
|
||||
cut_object_ptrs.push_back(upper);
|
||||
} else {
|
||||
// Delete all modifiers which are not intersecting with solid parts bounding box
|
||||
auto delete_extra_modifiers = [this](ModelObject *mo) {
|
||||
if (!mo) return;
|
||||
const BoundingBoxf3 obj_bb = mo->instance_bounding_box(m_instance);
|
||||
const Transform3d inst_matrix = mo->instances[m_instance]->get_transformation().get_matrix();
|
||||
|
||||
for (int i = int(mo->volumes.size()) - 1; i >= 0; --i)
|
||||
if (const ModelVolume *vol = mo->volumes[i]; !vol->is_model_part() && !vol->is_cut_connector()) {
|
||||
auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
|
||||
if (!obj_bb.intersects(bb))
|
||||
mo->delete_volume(i);
|
||||
}
|
||||
};
|
||||
|
||||
post_process(upper, lower, cut_object_ptrs);
|
||||
delete_extra_modifiers(upper);
|
||||
delete_extra_modifiers(lower);
|
||||
|
||||
if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
|
||||
for (auto dowel : dowels) {
|
||||
reset_instance_transformation(dowel, m_instance);
|
||||
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
|
||||
cut_object_ptrs.push_back(dowel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
|
||||
|
||||
finalize(cut_object_ptrs);
|
||||
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
static void distribute_modifiers_from_object(ModelObject *from_obj, const int instance_idx, ModelObject *to_obj1, ModelObject *to_obj2)
|
||||
{
|
||||
auto obj1_bb = to_obj1 ? to_obj1->instance_bounding_box(instance_idx) : BoundingBoxf3();
|
||||
auto obj2_bb = to_obj2 ? to_obj2->instance_bounding_box(instance_idx) : BoundingBoxf3();
|
||||
const Transform3d inst_matrix = from_obj->instances[instance_idx]->get_transformation().get_matrix();
|
||||
|
||||
for (ModelVolume *vol : from_obj->volumes)
|
||||
if (!vol->is_model_part()) {
|
||||
auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
|
||||
// Don't add modifiers which are not intersecting with solid parts
|
||||
if (obj1_bb.intersects(bb)) to_obj1->add_volume(*vol);
|
||||
if (obj2_bb.intersects(bb)) to_obj2->add_volume(*vol);
|
||||
}
|
||||
}
|
||||
|
||||
static void merge_solid_parts_inside_object(ModelObjectPtrs &objects)
|
||||
{
|
||||
for (ModelObject *mo : objects) {
|
||||
TriangleMesh mesh;
|
||||
// Merge all SolidPart but not Connectors
|
||||
for (const ModelVolume *mv : mo->volumes) {
|
||||
if (mv->is_model_part() && !mv->is_cut_connector()) {
|
||||
TriangleMesh m = mv->mesh();
|
||||
m.transform(mv->get_matrix());
|
||||
mesh.merge(m);
|
||||
}
|
||||
}
|
||||
if (!mesh.empty()) {
|
||||
ModelVolume *new_volume = mo->add_volume(mesh);
|
||||
new_volume->name = mo->name;
|
||||
// Delete all merged SolidPart but not Connectors
|
||||
for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) {
|
||||
const ModelVolume *mv = mo->volumes[i];
|
||||
if (mv->is_model_part() && !mv->is_cut_connector()) mo->delete_volume(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ModelObjectPtrs &Cut::perform_by_contour(std::vector<Part> parts, int dowels_count)
|
||||
{
|
||||
ModelObject *cut_mo = m_model.objects.front();
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject *upper{nullptr};
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
cut_mo->clone_for_cut(&upper);
|
||||
ModelObject *lower{nullptr};
|
||||
if (m_attributes.has(ModelObjectCutAttribute::KeepLower))
|
||||
cut_mo->clone_for_cut(&lower);
|
||||
|
||||
if (upper && lower) {
|
||||
upper->name = upper->name + "_A";
|
||||
lower->name = lower->name + "_B";
|
||||
}
|
||||
|
||||
const size_t cut_parts_cnt = parts.size();
|
||||
bool has_modifiers = false;
|
||||
|
||||
// Distribute SolidParts to the Upper/Lower object
|
||||
for (size_t id = 0; id < cut_parts_cnt; ++id) {
|
||||
if (parts[id].is_modifier)
|
||||
has_modifiers = true; // modifiers will be added later to the related parts
|
||||
else if (ModelObject *obj = (parts[id].selected ? upper : lower))
|
||||
obj->add_volume(*(cut_mo->volumes[id]));
|
||||
}
|
||||
|
||||
if (has_modifiers) {
|
||||
// Distribute Modifiers to the Upper/Lower object
|
||||
distribute_modifiers_from_object(cut_mo, m_instance, upper, lower);
|
||||
}
|
||||
|
||||
ModelObjectPtrs cut_object_ptrs;
|
||||
|
||||
ModelVolumePtrs &volumes = cut_mo->volumes;
|
||||
if (volumes.size() == cut_parts_cnt) {
|
||||
// Means that object is cut without connectors
|
||||
|
||||
// Just add Upper and Lower objects to cut_object_ptrs
|
||||
post_process(upper, lower, cut_object_ptrs);
|
||||
// Now merge all model parts together:
|
||||
merge_solid_parts_inside_object(cut_object_ptrs);
|
||||
|
||||
finalize(cut_object_ptrs);
|
||||
} else if (volumes.size() > cut_parts_cnt) {
|
||||
// Means that object is cut with connectors
|
||||
|
||||
// All volumes are distributed to Upper / Lower object,
|
||||
// So we don’t need them anymore
|
||||
for (size_t id = 0; id < cut_parts_cnt; id++) delete *(volumes.begin() + id);
|
||||
volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt);
|
||||
|
||||
// Perform cut just to get connectors
|
||||
Cut cut(cut_mo, m_instance, m_cut_matrix, m_attributes);
|
||||
const ModelObjectPtrs &cut_connectors_obj = cut.perform_with_plane();
|
||||
assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2);
|
||||
|
||||
// Connectors from upper object
|
||||
for (const ModelVolume *volume : cut_connectors_obj[0]->volumes) upper->add_volume(*volume, volume->type());
|
||||
|
||||
// Connectors from lower object
|
||||
for (const ModelVolume *volume : cut_connectors_obj[1]->volumes) lower->add_volume(*volume, volume->type());
|
||||
|
||||
// Add Upper and Lower objects to cut_object_ptrs
|
||||
post_process(upper, lower, cut_object_ptrs);
|
||||
|
||||
// Now merge all model parts together:
|
||||
merge_solid_parts_inside_object(cut_object_ptrs);
|
||||
|
||||
finalize(cut_object_ptrs);
|
||||
// Add Dowel-connectors as separate objects to cut_object_ptrs
|
||||
if (cut_connectors_obj.size() >= 3)
|
||||
for (size_t id = 2; id < cut_connectors_obj.size(); id++)
|
||||
m_model.add_object(*cut_connectors_obj[id]);
|
||||
}
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
const ModelObjectPtrs &Cut::perform_with_groove(const Groove &groove, const Transform3d &rotation_m, bool keep_as_parts /* = false*/)
|
||||
{
|
||||
ModelObject *cut_mo = m_model.objects.front();
|
||||
|
||||
// Clone the object to duplicate instances, materials etc.
|
||||
ModelObject *upper{nullptr};
|
||||
cut_mo->clone_for_cut(&upper);
|
||||
ModelObject *lower{nullptr};
|
||||
cut_mo->clone_for_cut(&lower);
|
||||
|
||||
if (upper && lower) {
|
||||
upper->name = upper->name + "_A";
|
||||
lower->name = lower->name + "_B";
|
||||
}
|
||||
const double groove_half_depth = 0.5 * double(groove.depth);
|
||||
|
||||
Model tmp_model_for_cut = Model();
|
||||
|
||||
Model tmp_model = Model();
|
||||
tmp_model.add_object(*cut_mo);
|
||||
ModelObject *tmp_object = tmp_model.objects.front();
|
||||
|
||||
auto add_volumes_from_cut = [](ModelObject *object, const ModelObjectCutAttribute attribute, const Model &tmp_model_for_cut) {
|
||||
const auto &volumes = tmp_model_for_cut.objects.front()->volumes;
|
||||
for (const ModelVolume *volume : volumes)
|
||||
if (volume->is_model_part()) {
|
||||
if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) ||
|
||||
(attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper())) {
|
||||
ModelVolume *new_vol = object->add_volume(*volume);
|
||||
new_vol->reset_from_upper();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto cut = [this, add_volumes_from_cut](ModelObject *object, const Transform3d &cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model &tmp_model_for_cut) {
|
||||
Cut cut(object, m_instance, cut_matrix);
|
||||
|
||||
tmp_model_for_cut = Model();
|
||||
tmp_model_for_cut.add_object(*cut.perform_with_plane().front());
|
||||
assert(!tmp_model_for_cut.objects.empty());
|
||||
|
||||
object->clear_volumes();
|
||||
add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut);
|
||||
reset_instance_transformation(object, m_instance);
|
||||
};
|
||||
|
||||
// cut by upper plane
|
||||
|
||||
const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
|
||||
{
|
||||
cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// cut by lower plane
|
||||
|
||||
const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
|
||||
{
|
||||
cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// cut middle part with 2 angles and add parts to related upper/lower objects
|
||||
|
||||
const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle));
|
||||
|
||||
// cut by angle1 plane
|
||||
{
|
||||
const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix *
|
||||
rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
|
||||
|
||||
cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// cut by angle2 plane
|
||||
{
|
||||
const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix *
|
||||
rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
|
||||
|
||||
cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// apply tolerance to the middle part
|
||||
{
|
||||
const double h_groove_shift_tolerance = groove_half_depth - (double) groove.depth_tolerance;
|
||||
|
||||
const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix;
|
||||
cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
|
||||
const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance);
|
||||
|
||||
const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix *
|
||||
rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
|
||||
cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
|
||||
const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix *
|
||||
rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
|
||||
cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||||
}
|
||||
|
||||
// this part can be added to the upper object now
|
||||
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||||
|
||||
ModelObjectPtrs cut_object_ptrs;
|
||||
|
||||
if (keep_as_parts) {
|
||||
// add volumes from lower object to the upper, but mark them as a lower
|
||||
const auto &volumes = lower->volumes;
|
||||
for (const ModelVolume *volume : volumes) {
|
||||
ModelVolume *new_vol = upper->add_volume(*volume);
|
||||
new_vol->cut_info.is_from_upper = false;
|
||||
}
|
||||
|
||||
// add modifiers
|
||||
for (const ModelVolume *volume : cut_mo->volumes)
|
||||
if (!volume->is_model_part()) upper->add_volume(*volume);
|
||||
|
||||
cut_object_ptrs.push_back(upper);
|
||||
|
||||
// add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks
|
||||
cut_object_ptrs.push_back(lower);
|
||||
} else {
|
||||
// add modifiers if object has any
|
||||
for (const ModelVolume *volume : cut_mo->volumes)
|
||||
if (!volume->is_model_part()) {
|
||||
distribute_modifiers_from_object(cut_mo, m_instance, upper, lower);
|
||||
break;
|
||||
}
|
||||
|
||||
assert(!upper->volumes.empty() && !lower->volumes.empty());
|
||||
|
||||
// Add Upper and Lower parts to cut_object_ptrs
|
||||
|
||||
post_process(upper, lower, cut_object_ptrs);
|
||||
|
||||
// Now merge all model parts together:
|
||||
merge_solid_parts_inside_object(cut_object_ptrs);
|
||||
}
|
||||
|
||||
finalize(cut_object_ptrs);
|
||||
|
||||
return m_model.objects;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
58
src/libslic3r/CutUtils.hpp
Normal file
58
src/libslic3r/CutUtils.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef slic3r_CutUtils_hpp_
|
||||
#define slic3r_CutUtils_hpp_
|
||||
|
||||
#include "enum_bitmask.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
const float CUT_TOLERANCE = 0.1f;
|
||||
struct Groove
|
||||
{
|
||||
float depth{0.f};
|
||||
float width{0.f};
|
||||
float flaps_angle{0.f};
|
||||
float angle{0.f};
|
||||
float depth_init{0.f};
|
||||
float width_init{0.f};
|
||||
float flaps_angle_init{0.f};
|
||||
float angle_init{0.f};
|
||||
float depth_tolerance{CUT_TOLERANCE};
|
||||
float width_tolerance{CUT_TOLERANCE};
|
||||
};
|
||||
|
||||
class Cut {
|
||||
|
||||
Model m_model;
|
||||
int m_instance;
|
||||
const Transform3d m_cut_matrix;
|
||||
ModelObjectCutAttributes m_attributes;
|
||||
|
||||
void post_process(ModelObject *object, bool is_upper, ModelObjectPtrs &objects, bool keep, bool place_on_cut, bool flip);
|
||||
void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects);
|
||||
void finalize(const ModelObjectPtrs& objects);
|
||||
|
||||
public:
|
||||
|
||||
Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes = ModelObjectCutAttribute::KeepUpper |
|
||||
ModelObjectCutAttribute::KeepLower |
|
||||
ModelObjectCutAttribute::CutToParts );
|
||||
~Cut() { m_model.clear_objects(); }
|
||||
|
||||
struct Part
|
||||
{
|
||||
bool selected;
|
||||
bool is_modifier;
|
||||
};
|
||||
|
||||
const ModelObjectPtrs& perform_with_plane();
|
||||
const ModelObjectPtrs& perform_by_contour(std::vector<Part> parts, int dowels_count);
|
||||
const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false);
|
||||
bool set_offset_for_two_part{false};
|
||||
}; // namespace Cut
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_CutUtils_hpp_ */
|
||||
1632
src/libslic3r/EdgeGrid.cpp
Normal file
1632
src/libslic3r/EdgeGrid.cpp
Normal file
File diff suppressed because it is too large
Load Diff
418
src/libslic3r/EdgeGrid.hpp
Normal file
418
src/libslic3r/EdgeGrid.hpp
Normal file
@@ -0,0 +1,418 @@
|
||||
#ifndef slic3r_EdgeGrid_hpp_
|
||||
#define slic3r_EdgeGrid_hpp_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace EdgeGrid {
|
||||
|
||||
|
||||
class Contour {
|
||||
public:
|
||||
Contour() = default;
|
||||
Contour(const Slic3r::Point *begin, const Slic3r::Point *end, bool open) : m_begin(begin), m_end(end), m_open(open) {}
|
||||
Contour(const Slic3r::Point *data, size_t size, bool open) : Contour(data, data + size, open) {}
|
||||
Contour(const std::vector<Slic3r::Point> &pts, bool open) : Contour(pts.data(), pts.size(), open) {}
|
||||
|
||||
const Slic3r::Point *begin() const { return m_begin; }
|
||||
const Slic3r::Point *end() const { return m_end; }
|
||||
bool open() const { return m_open; }
|
||||
bool closed() const { return !m_open; }
|
||||
|
||||
const Slic3r::Point &front() const { return *m_begin; }
|
||||
const Slic3r::Point &back() const { return *(m_end - 1); }
|
||||
|
||||
// Start point of a segment idx.
|
||||
const Slic3r::Point& segment_start(size_t idx) const {
|
||||
assert(idx < this->num_segments());
|
||||
return m_begin[idx];
|
||||
}
|
||||
|
||||
// End point of a segment idx.
|
||||
const Slic3r::Point& segment_end(size_t idx) const {
|
||||
assert(idx < this->num_segments());
|
||||
const Slic3r::Point *ptr = m_begin + idx + 1;
|
||||
return ptr == m_end ? *m_begin : *ptr;
|
||||
}
|
||||
|
||||
// Start point of a segment preceding idx.
|
||||
const Slic3r::Point& segment_prev(size_t idx) const {
|
||||
assert(idx < this->num_segments());
|
||||
assert(idx > 0 || ! m_open);
|
||||
return idx == 0 ? m_end[-1] : m_begin[idx - 1];
|
||||
}
|
||||
|
||||
// Index of a segment preceding idx.
|
||||
const size_t segment_idx_prev(size_t idx) const {
|
||||
assert(idx < this->num_segments());
|
||||
assert(idx > 0 || ! m_open);
|
||||
return (idx == 0 ? this->size() : idx) - 1;
|
||||
}
|
||||
|
||||
// Index of a segment preceding idx.
|
||||
const size_t segment_idx_next(size_t idx) const {
|
||||
assert(idx < this->num_segments());
|
||||
++ idx;
|
||||
return m_begin + idx == m_end ? 0 : idx;
|
||||
}
|
||||
|
||||
size_t num_segments() const { return this->size() - (m_open ? 1 : 0); }
|
||||
|
||||
Line get_segment(size_t idx) const
|
||||
{
|
||||
assert(idx < this->num_segments());
|
||||
return Line(this->segment_start(idx), this->segment_end(idx));
|
||||
}
|
||||
|
||||
Lines get_segments() const
|
||||
{
|
||||
Lines lines;
|
||||
lines.reserve(this->num_segments());
|
||||
if (this->num_segments() > 2) {
|
||||
for (auto it = this->begin(); it != this->end() - 1; ++it) lines.push_back(Line(*it, *(it + 1)));
|
||||
if (!m_open) lines.push_back(Line(this->back(), this->front()));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t size() const { return m_end - m_begin; }
|
||||
|
||||
const Slic3r::Point *m_begin { nullptr };
|
||||
const Slic3r::Point *m_end { nullptr };
|
||||
bool m_open { false };
|
||||
};
|
||||
|
||||
class Grid
|
||||
{
|
||||
public:
|
||||
Grid() = default;
|
||||
Grid(const BoundingBox &bbox) : m_bbox(bbox) {}
|
||||
|
||||
void set_bbox(const BoundingBox &bbox) { m_bbox = bbox; }
|
||||
|
||||
// Fill in the grid with open polylines or closed contours.
|
||||
// If open flag is indicated, then polylines_or_polygons are considered to be open by default.
|
||||
// Only if the first point of a polyline is equal to the last point of a polyline,
|
||||
// then the polyline is considered to be closed and the last repeated point is removed when
|
||||
// inserted into the EdgeGrid.
|
||||
// Most of the Grid functions expect all the contours to be closed, you have been warned!
|
||||
void create(const std::vector<Points> &polylines_or_polygons, coord_t resolution, bool open);
|
||||
void create(const Polygons &polygons, const Polylines &polylines, coord_t resolution);
|
||||
|
||||
// Fill in the grid with closed contours.
|
||||
void create(const Polygons &polygons, coord_t resolution);
|
||||
void create(const std::vector<const Polygon*> &polygons, coord_t resolution);
|
||||
void create(const std::vector<Points> &polygons, coord_t resolution) { this->create(polygons, resolution, false); }
|
||||
void create(const ExPolygon &expoly, coord_t resolution);
|
||||
void create(const ExPolygons &expolygons, coord_t resolution);
|
||||
|
||||
const std::vector<Contour>& contours() const { return m_contours; }
|
||||
|
||||
#if 0
|
||||
// Test, whether the edges inside the grid intersect with the polygons provided.
|
||||
bool intersect(const MultiPoint &polyline, bool closed);
|
||||
bool intersect(const Polygon &polygon) { return intersect(static_cast<const MultiPoint&>(polygon), true); }
|
||||
bool intersect(const Polygons &polygons) { for (size_t i = 0; i < polygons.size(); ++ i) if (intersect(polygons[i])) return true; return false; }
|
||||
bool intersect(const ExPolygon &expoly) { if (intersect(expoly.contour)) return true; for (size_t i = 0; i < expoly.holes.size(); ++ i) if (intersect(expoly.holes[i])) return true; return false; }
|
||||
bool intersect(const ExPolygons &expolygons) { for (size_t i = 0; i < expolygons.size(); ++ i) if (intersect(expolygons[i])) return true; return false; }
|
||||
|
||||
// Test, whether a point is inside a contour.
|
||||
bool inside(const Point &pt);
|
||||
#endif
|
||||
|
||||
// Fill in a rough m_signed_distance_field from the edge grid.
|
||||
// The rough SDF is used by signed_distance() for distances outside of the search_radius.
|
||||
// Only call this function for closed contours!
|
||||
void calculate_sdf();
|
||||
|
||||
// Return an estimate of the signed distance based on m_signed_distance_field grid.
|
||||
float signed_distance_bilinear(const Point &pt) const;
|
||||
|
||||
// Calculate a signed distance to the contours in search_radius from the point.
|
||||
// Only call this function for closed contours!
|
||||
struct ClosestPointResult {
|
||||
size_t contour_idx = size_t(-1);
|
||||
size_t start_point_idx = size_t(-1);
|
||||
// Signed distance to the closest point.
|
||||
double distance = std::numeric_limits<double>::max();
|
||||
// Parameter of the closest point on edge starting with start_point_idx <0, 1)
|
||||
double t = 0.;
|
||||
|
||||
bool valid() const { return contour_idx != size_t(-1); }
|
||||
};
|
||||
ClosestPointResult closest_point_signed_distance(const Point &pt, coord_t search_radius) const;
|
||||
|
||||
// Only call this function for closed contours!
|
||||
bool signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment = nullptr) const;
|
||||
|
||||
// Calculate a signed distance to the contours in search_radius from the point. If no edge is found in search_radius,
|
||||
// return an interpolated value from m_signed_distance_field, if it exists.
|
||||
// Only call this function for closed contours!
|
||||
bool signed_distance(const Point &pt, coord_t search_radius, coordf_t &result_min_dist) const;
|
||||
|
||||
const BoundingBox& bbox() const { return m_bbox; }
|
||||
const coord_t resolution() const { return m_resolution; }
|
||||
const size_t rows() const { return m_rows; }
|
||||
const size_t cols() const { return m_cols; }
|
||||
|
||||
// For supports: Contours enclosing the rasterized edges.
|
||||
Polygons contours_simplified(coord_t offset, bool fill_holes) const;
|
||||
|
||||
typedef std::pair<const Contour*, size_t> ContourPoint;
|
||||
typedef std::pair<const Contour*, size_t> ContourEdge;
|
||||
std::vector<std::pair<ContourEdge, ContourEdge>> intersecting_edges() const;
|
||||
bool has_intersecting_edges() const;
|
||||
|
||||
template<typename VISITOR> void visit_cells_intersecting_line(Slic3r::Point p1, Slic3r::Point p2, VISITOR &visitor) const
|
||||
{
|
||||
// End points of the line segment.
|
||||
assert(m_bbox.contains(p1));
|
||||
assert(m_bbox.contains(p2));
|
||||
p1 -= m_bbox.min;
|
||||
p2 -= m_bbox.min;
|
||||
assert(p1.x() >= 0 && size_t(p1.x()) < m_cols * m_resolution);
|
||||
assert(p1.y() >= 0 && size_t(p1.y()) < m_rows * m_resolution);
|
||||
assert(p2.x() >= 0 && size_t(p2.x()) < m_cols * m_resolution);
|
||||
assert(p2.y() >= 0 && size_t(p2.y()) < m_rows * m_resolution);
|
||||
// Get the cells of the end points.
|
||||
coord_t ix = p1(0) / m_resolution;
|
||||
coord_t iy = p1(1) / m_resolution;
|
||||
coord_t ixb = p2(0) / m_resolution;
|
||||
coord_t iyb = p2(1) / m_resolution;
|
||||
assert(ix >= 0 && size_t(ix) < m_cols);
|
||||
assert(iy >= 0 && size_t(iy) < m_rows);
|
||||
assert(ixb >= 0 && size_t(ixb) < m_cols);
|
||||
assert(iyb >= 0 && size_t(iyb) < m_rows);
|
||||
// Account for the end points.
|
||||
if (! visitor(iy, ix) || (ix == ixb && iy == iyb))
|
||||
// Both ends fall into the same cell.
|
||||
return;
|
||||
// Raster the centeral part of the line.
|
||||
coord_t dx = std::abs(p2(0) - p1(0));
|
||||
coord_t dy = std::abs(p2(1) - p1(1));
|
||||
if (p1(0) < p2(0)) {
|
||||
int64_t ex = int64_t((ix + 1)*m_resolution - p1(0)) * int64_t(dy);
|
||||
if (p1(1) < p2(1)) {
|
||||
// x positive, y positive
|
||||
int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx);
|
||||
do {
|
||||
assert(ix <= ixb && iy <= iyb);
|
||||
if (ex < ey) {
|
||||
ey -= ex;
|
||||
ex = int64_t(dy) * m_resolution;
|
||||
ix += 1;
|
||||
assert(ix <= ixb);
|
||||
}
|
||||
else if (ex == ey) {
|
||||
ex = int64_t(dy) * m_resolution;
|
||||
ey = int64_t(dx) * m_resolution;
|
||||
ix += 1;
|
||||
iy += 1;
|
||||
assert(ix <= ixb);
|
||||
assert(iy <= iyb);
|
||||
}
|
||||
else {
|
||||
assert(ex > ey);
|
||||
ex -= ey;
|
||||
ey = int64_t(dx) * m_resolution;
|
||||
iy += 1;
|
||||
assert(iy <= iyb);
|
||||
}
|
||||
if (! visitor(iy, ix))
|
||||
return;
|
||||
} while (ix != ixb || iy != iyb);
|
||||
}
|
||||
else {
|
||||
// x positive, y non positive
|
||||
int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx);
|
||||
do {
|
||||
assert(ix <= ixb && iy >= iyb);
|
||||
if (ex <= ey) {
|
||||
ey -= ex;
|
||||
ex = int64_t(dy) * m_resolution;
|
||||
ix += 1;
|
||||
assert(ix <= ixb);
|
||||
}
|
||||
else {
|
||||
ex -= ey;
|
||||
ey = int64_t(dx) * m_resolution;
|
||||
iy -= 1;
|
||||
assert(iy >= iyb);
|
||||
}
|
||||
if (! visitor(iy, ix))
|
||||
return;
|
||||
} while (ix != ixb || iy != iyb);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int64_t ex = int64_t(p1(0) - ix*m_resolution) * int64_t(dy);
|
||||
if (p1(1) < p2(1)) {
|
||||
// x non positive, y positive
|
||||
int64_t ey = int64_t((iy + 1)*m_resolution - p1(1)) * int64_t(dx);
|
||||
do {
|
||||
assert(ix >= ixb && iy <= iyb);
|
||||
if (ex < ey) {
|
||||
ey -= ex;
|
||||
ex = int64_t(dy) * m_resolution;
|
||||
ix -= 1;
|
||||
assert(ix >= ixb);
|
||||
}
|
||||
else {
|
||||
assert(ex >= ey);
|
||||
ex -= ey;
|
||||
ey = int64_t(dx) * m_resolution;
|
||||
iy += 1;
|
||||
assert(iy <= iyb);
|
||||
}
|
||||
if (! visitor(iy, ix))
|
||||
return;
|
||||
} while (ix != ixb || iy != iyb);
|
||||
}
|
||||
else {
|
||||
// x non positive, y non positive
|
||||
int64_t ey = int64_t(p1(1) - iy*m_resolution) * int64_t(dx);
|
||||
do {
|
||||
assert(ix >= ixb && iy >= iyb);
|
||||
if (ex < ey) {
|
||||
ey -= ex;
|
||||
ex = int64_t(dy) * m_resolution;
|
||||
ix -= 1;
|
||||
assert(ix >= ixb);
|
||||
}
|
||||
else if (ex == ey) {
|
||||
// The lower edge of a grid cell belongs to the cell.
|
||||
// Handle the case where the ray may cross the lower left corner of a cell in a general case,
|
||||
// or a left or lower edge in a degenerate case (horizontal or vertical line).
|
||||
if (dx > 0) {
|
||||
ex = int64_t(dy) * m_resolution;
|
||||
ix -= 1;
|
||||
assert(ix >= ixb);
|
||||
}
|
||||
if (dy > 0) {
|
||||
ey = int64_t(dx) * m_resolution;
|
||||
iy -= 1;
|
||||
assert(iy >= iyb);
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(ex > ey);
|
||||
ex -= ey;
|
||||
ey = int64_t(dx) * m_resolution;
|
||||
iy -= 1;
|
||||
assert(iy >= iyb);
|
||||
}
|
||||
if (! visitor(iy, ix))
|
||||
return;
|
||||
} while (ix != ixb || iy != iyb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename VISITOR> void visit_cells_intersecting_box(BoundingBox bbox, VISITOR &visitor) const
|
||||
{
|
||||
// End points of the line segment.
|
||||
bbox.min -= m_bbox.min;
|
||||
bbox.max -= m_bbox.min + Point(1, 1);
|
||||
// Get the cells of the end points.
|
||||
bbox.min /= m_resolution;
|
||||
bbox.max /= m_resolution;
|
||||
// Trim with the cells.
|
||||
bbox.min.x() = std::max<coord_t>(bbox.min.x(), 0);
|
||||
bbox.min.y() = std::max<coord_t>(bbox.min.y(), 0);
|
||||
bbox.max.x() = std::min<coord_t>(bbox.max.x(), (coord_t)m_cols - 1);
|
||||
bbox.max.y() = std::min<coord_t>(bbox.max.y(), (coord_t)m_rows - 1);
|
||||
for (coord_t iy = bbox.min.y(); iy <= bbox.max.y(); ++ iy)
|
||||
for (coord_t ix = bbox.min.x(); ix <= bbox.max.x(); ++ ix)
|
||||
if (! visitor(iy, ix))
|
||||
return;
|
||||
}
|
||||
|
||||
std::pair<std::vector<std::pair<size_t, size_t>>::const_iterator, std::vector<std::pair<size_t, size_t>>::const_iterator> cell_data_range(coord_t row, coord_t col) const
|
||||
{
|
||||
assert(row >= 0 && size_t(row) < m_rows);
|
||||
assert(col >= 0 && size_t(col) < m_cols);
|
||||
const EdgeGrid::Grid::Cell &cell = m_cells[row * m_cols + col];
|
||||
return std::make_pair(m_cell_data.begin() + cell.begin, m_cell_data.begin() + cell.end);
|
||||
}
|
||||
|
||||
std::pair<const Slic3r::Point&, const Slic3r::Point&> segment(const std::pair<size_t, size_t> &contour_and_segment_idx) const
|
||||
{
|
||||
const Contour &contour = m_contours[contour_and_segment_idx.first];
|
||||
size_t iseg = contour_and_segment_idx.second;
|
||||
return std::pair<const Slic3r::Point&, const Slic3r::Point&>(contour.segment_start(iseg), contour.segment_end(iseg));
|
||||
}
|
||||
|
||||
Line line(const std::pair<size_t, size_t> &contour_and_segment_idx) const
|
||||
{
|
||||
const Contour &contour = m_contours[contour_and_segment_idx.first];
|
||||
size_t iseg = contour_and_segment_idx.second;
|
||||
return Line(contour.segment_start(iseg), contour.segment_end(iseg));
|
||||
}
|
||||
|
||||
protected:
|
||||
struct Cell {
|
||||
Cell() : begin(0), end(0) {}
|
||||
size_t begin;
|
||||
size_t end;
|
||||
};
|
||||
|
||||
void create_from_m_contours(coord_t resolution);
|
||||
#if 0
|
||||
bool line_cell_intersect(const Point &p1, const Point &p2, const Cell &cell);
|
||||
#endif
|
||||
bool cell_inside_or_crossing(int r, int c) const
|
||||
{
|
||||
if (r < 0 || (size_t)r >= m_rows ||
|
||||
c < 0 || (size_t)c >= m_cols)
|
||||
// The cell is outside the domain. Hoping that the contours were correctly oriented, so
|
||||
// there is a CCW outmost contour so the out of domain cells are outside.
|
||||
return false;
|
||||
const Cell &cell = m_cells[r * m_cols + c];
|
||||
return
|
||||
(cell.begin < cell.end) ||
|
||||
(! m_signed_distance_field.empty() && m_signed_distance_field[r * (m_cols + 1) + c] <= 0.f);
|
||||
}
|
||||
|
||||
// Bounding box around the contours.
|
||||
BoundingBox m_bbox;
|
||||
// Grid dimensions.
|
||||
coord_t m_resolution;
|
||||
size_t m_rows = 0;
|
||||
size_t m_cols = 0;
|
||||
|
||||
// Referencing the source contours.
|
||||
// This format allows one to work with any Slic3r fixed point contour format
|
||||
// (Polygon, ExPolygon, ExPolygons etc).
|
||||
std::vector<Contour> m_contours;
|
||||
|
||||
// Referencing a contour and a line segment of m_contours.
|
||||
std::vector<std::pair<size_t, size_t> > m_cell_data;
|
||||
|
||||
// Full grid of cells.
|
||||
std::vector<Cell> m_cells;
|
||||
|
||||
// Distance field derived from the edge grid, seed filled by the Danielsson chamfer metric.
|
||||
// May be empty.
|
||||
std::vector<float> m_signed_distance_field;
|
||||
};
|
||||
|
||||
// Debugging utility. Save the signed distance field.
|
||||
extern void save_png(const Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path, size_t scale = 1);
|
||||
|
||||
} // namespace EdgeGrid
|
||||
|
||||
// Find all pairs of intersectiong edges from the set of polygons.
|
||||
extern std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersecting_edges(const Polygons &polygons);
|
||||
|
||||
// Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG.
|
||||
extern void export_intersections_to_svg(const std::string &filename, const Polygons &polygons);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_EdgeGrid_hpp_ */
|
||||
646
src/libslic3r/ElephantFootCompensation.cpp
Normal file
646
src/libslic3r/ElephantFootCompensation.cpp
Normal file
@@ -0,0 +1,646 @@
|
||||
#include "clipper/clipper_z.hpp"
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ElephantFootCompensation.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
||||
// #define CONTOUR_DISTANCE_DEBUG_SVG
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct ResampledPoint {
|
||||
ResampledPoint(size_t idx_src, bool interpolated, double curve_parameter) : idx_src(idx_src), interpolated(interpolated), curve_parameter(curve_parameter) {}
|
||||
|
||||
size_t idx_src;
|
||||
// Is this point interpolated or initial?
|
||||
bool interpolated;
|
||||
// Euclidean distance along the curve from the 0th point.
|
||||
double curve_parameter;
|
||||
};
|
||||
|
||||
// Distance calculated using SDF (Shape Diameter Function).
|
||||
// The distance is calculated by casting a fan of rays and measuring the intersection distance.
|
||||
// Thus the calculation is relatively slow. For the Elephant foot compensation purpose, this distance metric does not avoid
|
||||
// pinching off small pieces of a contour, thus this function has been superseded by contour_distance2().
|
||||
std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx_contour, const Slic3r::Points &contour, const std::vector<ResampledPoint> &resampled_point_parameters, double search_radius)
|
||||
{
|
||||
assert(! contour.empty());
|
||||
assert(contour.size() >= 2);
|
||||
|
||||
std::vector<float> out;
|
||||
|
||||
if (contour.size() > 2)
|
||||
{
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
static int iRun = 0;
|
||||
++ iRun;
|
||||
BoundingBox bbox = get_extents(contour);
|
||||
bbox.merge(grid.bbox());
|
||||
ExPolygon expoly_grid;
|
||||
expoly_grid.contour = Polygon(*grid.contours().front());
|
||||
for (size_t i = 1; i < grid.contours().size(); ++ i)
|
||||
expoly_grid.holes.emplace_back(Polygon(*grid.contours()[i]));
|
||||
#endif
|
||||
struct Visitor {
|
||||
Visitor(const EdgeGrid::Grid &grid, const size_t idx_contour, const std::vector<ResampledPoint> &resampled_point_parameters, double dist_same_contour_reject) :
|
||||
grid(grid), idx_contour(idx_contour), resampled_point_parameters(resampled_point_parameters), dist_same_contour_reject(dist_same_contour_reject) {}
|
||||
|
||||
void init(const size_t aidx_point_start, const Point &apt_start, Vec2d dir, const double radius) {
|
||||
this->idx_point_start = aidx_point_start;
|
||||
this->pt = apt_start.cast<double>() + SCALED_EPSILON * dir;
|
||||
dir *= radius;
|
||||
this->pt_start = this->pt.cast<coord_t>();
|
||||
// Trim the vector by the grid's bounding box.
|
||||
const BoundingBox &bbox = this->grid.bbox();
|
||||
double t = 1.;
|
||||
for (size_t axis = 0; axis < 2; ++ axis) {
|
||||
double dx = std::abs(dir(axis));
|
||||
if (dx >= EPSILON) {
|
||||
double tedge = (dir(axis) > 0) ? (double(bbox.max(axis)) - SCALED_EPSILON - this->pt(axis)) : (this->pt(axis) - double(bbox.min(axis)) - SCALED_EPSILON);
|
||||
if (tedge < dx)
|
||||
t = std::min(t, tedge / dx);
|
||||
}
|
||||
}
|
||||
this->dir = dir;
|
||||
if (t < 1.)
|
||||
dir *= t;
|
||||
this->pt_end = (this->pt + dir).cast<coord_t>();
|
||||
this->t_min = 1.;
|
||||
assert(this->grid.bbox().contains(this->pt_start) && this->grid.bbox().contains(this->pt_end));
|
||||
}
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix) {
|
||||
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||
auto cell_data_range = this->grid.cell_data_range(iy, ix);
|
||||
bool valid = true;
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
|
||||
// End points of the line segment and their vector.
|
||||
auto segment = this->grid.segment(*it_contour_and_segment);
|
||||
if (Geometry::segments_intersect(segment.first, segment.second, this->pt_start, this->pt_end)) {
|
||||
// The two segments intersect. Calculate the intersection.
|
||||
Vec2d pt2 = segment.first.cast<double>();
|
||||
Vec2d dir2 = segment.second.cast<double>() - pt2;
|
||||
Vec2d vptpt2 = pt - pt2;
|
||||
double denom = dir(0) * dir2(1) - dir2(0) * dir(1);
|
||||
|
||||
if (std::abs(denom) >= EPSILON) {
|
||||
double t = cross2(dir2, vptpt2) / denom;
|
||||
assert(t > - EPSILON && t < 1. + EPSILON);
|
||||
bool this_valid = true;
|
||||
if (it_contour_and_segment->first == idx_contour) {
|
||||
// The intersected segment originates from the same contour as the starting point.
|
||||
// Reject the intersection if it is close to the starting point.
|
||||
// Find the start and end points of this segment
|
||||
double param_lo = resampled_point_parameters[idx_point_start].curve_parameter;
|
||||
double param_hi;
|
||||
double param_end = resampled_point_parameters.back().curve_parameter;
|
||||
{
|
||||
const EdgeGrid::Contour &contour = grid.contours()[it_contour_and_segment->first];
|
||||
size_t ipt = it_contour_and_segment->second;
|
||||
ResampledPoint key(ipt, false, 0.);
|
||||
auto lower = [](const ResampledPoint& l, const ResampledPoint r) { return l.idx_src < r.idx_src || (l.idx_src == r.idx_src && int(l.interpolated) > int(r.interpolated)); };
|
||||
auto it = std::lower_bound(resampled_point_parameters.begin(), resampled_point_parameters.end(), key, lower);
|
||||
assert(it != resampled_point_parameters.end() && it->idx_src == ipt && ! it->interpolated);
|
||||
double t2 = cross2(dir, vptpt2) / denom;
|
||||
assert(t2 > - EPSILON && t2 < 1. + EPSILON);
|
||||
if (contour.begin() + (++ ipt) == contour.end())
|
||||
param_hi = t2 * dir2.norm();
|
||||
else
|
||||
param_hi = it->curve_parameter + t2 * dir2.norm();
|
||||
}
|
||||
if (param_lo > param_hi)
|
||||
std::swap(param_lo, param_hi);
|
||||
assert(param_lo >= 0. && param_lo <= param_end);
|
||||
assert(param_hi >= 0. && param_hi <= param_end);
|
||||
this_valid = param_hi > param_lo + dist_same_contour_reject && param_hi - param_end < param_lo - dist_same_contour_reject;
|
||||
}
|
||||
if (t < this->t_min) {
|
||||
this->t_min = t;
|
||||
valid = this_valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! valid)
|
||||
this->t_min = 1.;
|
||||
}
|
||||
// Continue traversing the grid along the edge.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid &grid;
|
||||
const size_t idx_contour;
|
||||
const std::vector<ResampledPoint> &resampled_point_parameters;
|
||||
const double dist_same_contour_reject;
|
||||
|
||||
size_t idx_point_start;
|
||||
Point pt_start;
|
||||
Point pt_end;
|
||||
Vec2d pt;
|
||||
Vec2d dir;
|
||||
// Minium parameter along the vector (pt_end - pt_start).
|
||||
double t_min;
|
||||
} visitor(grid, idx_contour, resampled_point_parameters, search_radius);
|
||||
|
||||
const Point *pt_this = &contour.back();
|
||||
size_t idx_pt_this = contour.size() - 1;
|
||||
const Point *pt_prev = pt_this - 1;
|
||||
// perpenduclar vector
|
||||
auto perp = [](const Vec2d& v) -> Vec2d { return Vec2d(v.y(), -v.x()); };
|
||||
Vec2d vprev = (*pt_this - *pt_prev).cast<double>().normalized();
|
||||
out.reserve(contour.size() + 1);
|
||||
for (const Point &pt_next : contour) {
|
||||
Vec2d vnext = (pt_next - *pt_this).cast<double>().normalized();
|
||||
Vec2d dir = - (perp(vprev) + perp(vnext)).normalized();
|
||||
Vec2d dir_perp = perp(dir);
|
||||
double cross = cross2(vprev, vnext);
|
||||
double dot = vprev.dot(vnext);
|
||||
double a = (cross < 0 || dot > 0.5) ? (M_PI / 3.) : (0.48 * acos(std::min(1., - dot)));
|
||||
// Throw rays, collect distances.
|
||||
std::vector<double> distances;
|
||||
int num_rays = 15;
|
||||
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
SVG svg(debug_out_path("contour_distance_raycasted-%d-%d.svg", iRun, &pt_next - contour.data()).c_str(), bbox);
|
||||
svg.draw(expoly_grid);
|
||||
svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
|
||||
svg.draw(*pt_this, "red", coord_t(scale_(0.1)));
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
|
||||
for (int i = - num_rays + 1; i < num_rays; ++ i) {
|
||||
double angle = a * i / (int)num_rays;
|
||||
double c = cos(angle);
|
||||
double s = sin(angle);
|
||||
Vec2d v = c * dir + s * dir_perp;
|
||||
visitor.init(idx_pt_this, *pt_this, v, search_radius);
|
||||
grid.visit_cells_intersecting_line(visitor.pt_start, visitor.pt_end, visitor);
|
||||
distances.emplace_back(visitor.t_min);
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
svg.draw(Line(visitor.pt_start, visitor.pt_end), "yellow", scale_(0.01));
|
||||
if (visitor.t_min < 1.) {
|
||||
Vec2d pt = visitor.pt + visitor.dir * visitor.t_min;
|
||||
svg.draw(Point(pt), "red", coord_t(scale_(0.1)));
|
||||
}
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
}
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
svg.Close();
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
std::sort(distances.begin(), distances.end());
|
||||
#if 0
|
||||
double median = distances[distances.size() / 2];
|
||||
double standard_deviation = 0;
|
||||
for (double d : distances)
|
||||
standard_deviation += (d - median) * (d - median);
|
||||
standard_deviation = sqrt(standard_deviation / (distances.size() - 1));
|
||||
double avg = 0;
|
||||
size_t cnt = 0;
|
||||
for (double d : distances)
|
||||
if (d > median - standard_deviation - EPSILON && d < median + standard_deviation + EPSILON) {
|
||||
avg += d;
|
||||
++ cnt;
|
||||
}
|
||||
avg /= double(cnt);
|
||||
out.emplace_back(float(avg * search_radius));
|
||||
#else
|
||||
out.emplace_back(float(distances.front() * search_radius));
|
||||
#endif
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
printf("contour_distance_raycasted-%d-%d.svg - distance %lf\n", iRun, int(&pt_next - contour.data()), unscale<double>(out.back()));
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
pt_this = &pt_next;
|
||||
idx_pt_this = &pt_next - contour.data();
|
||||
vprev = vnext;
|
||||
}
|
||||
// Rotate the vector by one item.
|
||||
out.emplace_back(out.front());
|
||||
out.erase(out.begin());
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Contour distance by measuring the closest point of an ExPolygon stored inside the EdgeGrid, while filtering out points of the same contour
|
||||
// at concave regions, or convex regions with low curvature (curvature is estimated as a ratio between contour length and chordal distance crossing the contour ends).
|
||||
std::vector<float> contour_distance2(const EdgeGrid::Grid &grid, const size_t idx_contour, const Slic3r::Points &contour, const std::vector<ResampledPoint> &resampled_point_parameters, double compensation, double search_radius)
|
||||
{
|
||||
assert(! contour.empty());
|
||||
assert(contour.size() >= 2);
|
||||
|
||||
std::vector<float> out;
|
||||
|
||||
if (contour.size() > 2)
|
||||
{
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
static int iRun = 0;
|
||||
++ iRun;
|
||||
BoundingBox bbox = get_extents(contour);
|
||||
bbox.merge(grid.bbox());
|
||||
ExPolygon expoly_grid;
|
||||
expoly_grid.contour = Polygon(*grid.contours().front());
|
||||
for (size_t i = 1; i < grid.contours().size(); ++ i)
|
||||
expoly_grid.holes.emplace_back(Polygon(*grid.contours()[i]));
|
||||
#endif
|
||||
struct Visitor {
|
||||
Visitor(const EdgeGrid::Grid &grid, const size_t idx_contour, const std::vector<ResampledPoint> &resampled_point_parameters, double dist_same_contour_accept, double dist_same_contour_reject) :
|
||||
grid(grid), idx_contour(idx_contour), contour(grid.contours()[idx_contour]), resampled_point_parameters(resampled_point_parameters), dist_same_contour_accept(dist_same_contour_accept), dist_same_contour_reject(dist_same_contour_reject) {}
|
||||
|
||||
void init(const Points &contour, const Point &apoint) {
|
||||
this->idx_point = &apoint - contour.data();
|
||||
this->point = apoint;
|
||||
this->found = false;
|
||||
this->dir_inside = this->dir_inside_at_point(contour, this->idx_point);
|
||||
this->distance = std::numeric_limits<double>::max();
|
||||
}
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix) {
|
||||
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||
auto cell_data_range = this->grid.cell_data_range(iy, ix);
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
|
||||
// End points of the line segment and their vector.
|
||||
std::pair<const Point&, const Point&> segment = this->grid.segment(*it_contour_and_segment);
|
||||
const Vec2d v = (segment.second - segment.first).cast<double>();
|
||||
const Vec2d va = (this->point - segment.first).cast<double>();
|
||||
const double l2 = v.squaredNorm(); // avoid a sqrt
|
||||
const double t = (l2 == 0.0) ? 0. : std::clamp(va.dot(v) / l2, 0., 1.);
|
||||
// Closest point from this->point to the segment.
|
||||
const Vec2d foot = segment.first.cast<double>() + t * v;
|
||||
const Vec2d bisector = foot - this->point.cast<double>();
|
||||
const double dist = bisector.norm();
|
||||
if ((! this->found || dist < this->distance) && this->dir_inside.dot(bisector) > 0) {
|
||||
bool accept = true;
|
||||
if (it_contour_and_segment->first == idx_contour) {
|
||||
// Complex case: The closest segment originates from the same contour as the starting point.
|
||||
// Reject the closest point if its distance along the contour is reasonable compared to the current contour bisector (this->pt, foot).
|
||||
double param_lo = resampled_point_parameters[this->idx_point].curve_parameter;
|
||||
double param_hi;
|
||||
double param_end = resampled_point_parameters.back().curve_parameter;
|
||||
const EdgeGrid::Contour &contour = grid.contours()[it_contour_and_segment->first];
|
||||
const size_t ipt = it_contour_and_segment->second;
|
||||
{
|
||||
ResampledPoint key(ipt, false, 0.);
|
||||
auto lower = [](const ResampledPoint& l, const ResampledPoint r) { return l.idx_src < r.idx_src || (l.idx_src == r.idx_src && int(l.interpolated) > int(r.interpolated)); };
|
||||
auto it = std::lower_bound(resampled_point_parameters.begin(), resampled_point_parameters.end(), key, lower);
|
||||
assert(it != resampled_point_parameters.end() && it->idx_src == ipt && ! it->interpolated);
|
||||
param_hi = t * sqrt(l2);
|
||||
if (contour.begin() + ipt + 1 < contour.end())
|
||||
param_hi += it->curve_parameter;
|
||||
}
|
||||
if (param_lo > param_hi)
|
||||
std::swap(param_lo, param_hi);
|
||||
assert(param_lo > - SCALED_EPSILON && param_lo <= param_end + SCALED_EPSILON);
|
||||
assert(param_hi > - SCALED_EPSILON && param_hi <= param_end + SCALED_EPSILON);
|
||||
double dist_along_contour = std::min(param_hi - param_lo, param_lo + param_end - param_hi);
|
||||
if (dist_along_contour < dist_same_contour_accept)
|
||||
accept = false;
|
||||
else if (dist < dist_same_contour_reject + SCALED_EPSILON) {
|
||||
// this->point is close to foot. This point will only be accepted if the path along the contour is significantly
|
||||
// longer than the bisector. That is, the path shall not bulge away from the bisector too much.
|
||||
// Bulge is estimated by 0.6 of the circle circumference drawn around the bisector.
|
||||
// Test whether the contour is convex or concave.
|
||||
bool inside =
|
||||
(t == 0.) ? this->inside_corner(contour, ipt, this->point) :
|
||||
(t == 1.) ? this->inside_corner(contour, contour.segment_idx_next(ipt), this->point) :
|
||||
this->left_of_segment(contour, ipt, this->point);
|
||||
accept = inside && dist_along_contour > 0.6 * M_PI * dist;
|
||||
}
|
||||
}
|
||||
if (accept && (! this->found || dist < this->distance)) {
|
||||
// Simple case: Just measure the shortest distance.
|
||||
this->distance = dist;
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
this->closest_point = foot.cast<coord_t>();
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
this->found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Continue traversing the grid.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid &grid;
|
||||
const size_t idx_contour;
|
||||
const EdgeGrid::Contour &contour;
|
||||
const std::vector<ResampledPoint> &resampled_point_parameters;
|
||||
const double dist_same_contour_accept;
|
||||
const double dist_same_contour_reject;
|
||||
|
||||
size_t idx_point;
|
||||
Point point;
|
||||
// Direction inside the contour from idx_point, not normalized.
|
||||
Vec2d dir_inside;
|
||||
bool found;
|
||||
double distance;
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
Point closest_point;
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
|
||||
private:
|
||||
static Vec2d dir_inside_at_point(const Points &contour, size_t i) {
|
||||
size_t iprev = prev_idx_modulo(i, contour);
|
||||
size_t inext = next_idx_modulo(i, contour);
|
||||
Vec2d v1 = (contour[i] - contour[iprev]).cast<double>();
|
||||
Vec2d v2 = (contour[inext] - contour[i]).cast<double>();
|
||||
return Vec2d(- v1.y() - v2.y(), v1.x() + v2.x());
|
||||
}
|
||||
static Vec2d dir_inside_at_segment(const Points& contour, size_t i) {
|
||||
size_t inext = next_idx_modulo(i, contour);
|
||||
Vec2d v = (contour[inext] - contour[i]).cast<double>();
|
||||
return Vec2d(- v.y(), v.x());
|
||||
}
|
||||
|
||||
static bool inside_corner(const EdgeGrid::Contour &contour, size_t i, const Point &pt_oposite)
|
||||
{
|
||||
const Vec2d pt = pt_oposite.cast<double>();
|
||||
const Point &pt_prev = contour.segment_prev(i);
|
||||
const Point &pt_this = contour.segment_start(i);
|
||||
const Point &pt_next = contour.segment_end(i);
|
||||
Vec2d v1 = (pt_this - pt_prev).cast<double>();
|
||||
Vec2d v2 = (pt_next - pt_this).cast<double>();
|
||||
bool left_of_v1 = cross2(v1, pt - pt_prev.cast<double>()) > 0.;
|
||||
bool left_of_v2 = cross2(v2, pt - pt_this.cast<double>()) > 0.;
|
||||
return cross2(v1, v2) > 0 ? left_of_v1 && left_of_v2 : // convex corner
|
||||
left_of_v1 || left_of_v2; // concave corner
|
||||
}
|
||||
|
||||
static bool left_of_segment(const EdgeGrid::Contour &contour, size_t i, const Point &pt_oposite)
|
||||
{
|
||||
const Vec2d pt = pt_oposite.cast<double>();
|
||||
const Point &pt_this = contour.segment_start(i);
|
||||
const Point &pt_next = contour.segment_end(i);
|
||||
Vec2d v = (pt_next - pt_this).cast<double>();
|
||||
return cross2(v, pt - pt_this.cast<double>()) > 0.;
|
||||
}
|
||||
} visitor(grid, idx_contour, resampled_point_parameters, 0.5 * compensation * M_PI, search_radius);
|
||||
|
||||
out.reserve(contour.size());
|
||||
Point radius_vector(search_radius, search_radius);
|
||||
for (const Point &pt : contour) {
|
||||
visitor.init(contour, pt);
|
||||
grid.visit_cells_intersecting_box(BoundingBox(pt - radius_vector, pt + radius_vector), visitor);
|
||||
out.emplace_back(float(visitor.found ? std::min(visitor.distance, search_radius) : search_radius));
|
||||
|
||||
#if 0
|
||||
//#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
if (out.back() < search_radius) {
|
||||
SVG svg(debug_out_path("contour_distance_filtered-%d-%d.svg", iRun, int(&pt - contour.data())).c_str(), bbox);
|
||||
svg.draw(expoly_grid);
|
||||
svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
|
||||
svg.draw(pt, "green", coord_t(scale_(0.1)));
|
||||
svg.draw(visitor.closest_point, "red", coord_t(scale_(0.1)));
|
||||
printf("contour_distance_filtered-%d-%d.svg - distance %lf\n", iRun, int(&pt - contour.data()), unscale<double>(out.back()));
|
||||
}
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
}
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
if (out.back() < search_radius) {
|
||||
SVG svg(debug_out_path("contour_distance_filtered-final-%d.svg", iRun).c_str(), bbox);
|
||||
svg.draw(expoly_grid);
|
||||
svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
|
||||
for (size_t i = 0; i < contour.size(); ++ i)
|
||||
svg.draw(contour[i], out[i] < float(search_radius - SCALED_EPSILON) ? "red" : "green", coord_t(scale_(0.1)));
|
||||
}
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Points resample_polygon(const Points &contour, double dist, std::vector<ResampledPoint> &resampled_point_parameters)
|
||||
{
|
||||
Points out;
|
||||
out.reserve(contour.size());
|
||||
resampled_point_parameters.reserve(contour.size());
|
||||
if (contour.size() > 2) {
|
||||
Vec2d pt_prev = contour.back().cast<double>();
|
||||
for (const Point &pt : contour) {
|
||||
size_t idx_this = &pt - contour.data();
|
||||
const Vec2d pt_this = pt.cast<double>();
|
||||
const Vec2d v = pt_this - pt_prev;
|
||||
const double l = v.norm();
|
||||
const size_t n = size_t(ceil(l / dist));
|
||||
const double l_step = l / n;
|
||||
for (size_t i = 1; i < n; ++ i) {
|
||||
double interpolation_parameter = double(i) / n;
|
||||
Vec2d new_pt = pt_prev + v * interpolation_parameter;
|
||||
out.emplace_back(new_pt.cast<coord_t>());
|
||||
resampled_point_parameters.emplace_back(idx_this, true, l_step);
|
||||
}
|
||||
out.emplace_back(pt);
|
||||
resampled_point_parameters.emplace_back(idx_this, false, l_step);
|
||||
pt_prev = pt_this;
|
||||
}
|
||||
for (size_t i = 1; i < resampled_point_parameters.size(); ++i)
|
||||
resampled_point_parameters[i].curve_parameter += resampled_point_parameters[i - 1].curve_parameter;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static inline void smooth_compensation(std::vector<float> &compensation, float strength, size_t num_iterations)
|
||||
{
|
||||
std::vector<float> out(compensation);
|
||||
for (size_t iter = 0; iter < num_iterations; ++ iter) {
|
||||
for (size_t i = 0; i < compensation.size(); ++ i) {
|
||||
float prev = prev_value_modulo(i, compensation);
|
||||
float next = next_value_modulo(i, compensation);
|
||||
float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next);
|
||||
// Compensations are negative. Only apply the laplacian if it leads to lower compensation.
|
||||
out[i] = std::max(laplacian, compensation[i]);
|
||||
}
|
||||
out.swap(compensation);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void smooth_compensation_banded(const Points &contour, float band, std::vector<float> &compensation, float strength, size_t num_iterations)
|
||||
{
|
||||
assert(contour.size() == compensation.size());
|
||||
assert(contour.size() > 2);
|
||||
std::vector<float> out(compensation);
|
||||
float dist_min2 = band * band;
|
||||
static constexpr bool use_min = false;
|
||||
for (size_t iter = 0; iter < num_iterations; ++ iter) {
|
||||
for (int i = 0; i < int(compensation.size()); ++ i) {
|
||||
const Vec2f pthis = contour[i].cast<float>();
|
||||
|
||||
int j = prev_idx_modulo(i, contour);
|
||||
Vec2f pprev = contour[j].cast<float>();
|
||||
float prev = compensation[j];
|
||||
float l2 = (pthis - pprev).squaredNorm();
|
||||
if (l2 < dist_min2) {
|
||||
float l = sqrt(l2);
|
||||
int jprev = std::exchange(j, prev_idx_modulo(j, contour));
|
||||
while (j != i) {
|
||||
const Vec2f pp = contour[j].cast<float>();
|
||||
const float lthis = (pp - pprev).norm();
|
||||
const float lnext = l + lthis;
|
||||
if (lnext > band) {
|
||||
// Interpolate the compensation value.
|
||||
prev = use_min ?
|
||||
std::min(prev, lerp(compensation[jprev], compensation[j], (band - l) / lthis)) :
|
||||
lerp(compensation[jprev], compensation[j], (band - l) / lthis);
|
||||
break;
|
||||
}
|
||||
prev = use_min ? std::min(prev, compensation[j]) : compensation[j];
|
||||
pprev = pp;
|
||||
l = lnext;
|
||||
jprev = std::exchange(j, prev_idx_modulo(j, contour));
|
||||
}
|
||||
}
|
||||
|
||||
j = next_idx_modulo(i, contour);
|
||||
pprev = contour[j].cast<float>();
|
||||
float next = compensation[j];
|
||||
l2 = (pprev - pthis).squaredNorm();
|
||||
if (l2 < dist_min2) {
|
||||
float l = sqrt(l2);
|
||||
int jprev = std::exchange(j, next_idx_modulo(j, contour));
|
||||
while (j != i) {
|
||||
const Vec2f pp = contour[j].cast<float>();
|
||||
const float lthis = (pp - pprev).norm();
|
||||
const float lnext = l + lthis;
|
||||
if (lnext > band) {
|
||||
// Interpolate the compensation value.
|
||||
next = use_min ?
|
||||
std::min(next, lerp(compensation[jprev], compensation[j], (band - l) / lthis)) :
|
||||
lerp(compensation[jprev], compensation[j], (band - l) / lthis);
|
||||
break;
|
||||
}
|
||||
next = use_min ? std::min(next, compensation[j]) : compensation[j];
|
||||
pprev = pp;
|
||||
l = lnext;
|
||||
jprev = std::exchange(j, next_idx_modulo(j, contour));
|
||||
}
|
||||
}
|
||||
|
||||
float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next);
|
||||
// Compensations are negative. Only apply the laplacian if it leads to lower compensation.
|
||||
out[i] = std::max(laplacian, compensation[i]);
|
||||
}
|
||||
out.swap(compensation);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
static bool validate_expoly_orientation(const ExPolygon &expoly)
|
||||
{
|
||||
bool valid = expoly.contour.is_counter_clockwise();
|
||||
for (auto &h : expoly.holes)
|
||||
valid &= h.is_clockwise();
|
||||
return valid;
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, double min_contour_width, const double compensation)
|
||||
{
|
||||
assert(validate_expoly_orientation(input_expoly));
|
||||
|
||||
double scaled_compensation = scale_(compensation);
|
||||
min_contour_width = scale_(min_contour_width);
|
||||
double min_contour_width_compensated = min_contour_width + 2. * scaled_compensation;
|
||||
// Make the search radius a bit larger for the averaging in contour_distance over a fan of rays to work.
|
||||
double search_radius = min_contour_width_compensated + min_contour_width * 0.5;
|
||||
|
||||
BoundingBox bbox = get_extents(input_expoly.contour);
|
||||
Point bbox_size = bbox.size();
|
||||
ExPolygon out;
|
||||
if (bbox_size.x() < min_contour_width_compensated + SCALED_EPSILON ||
|
||||
bbox_size.y() < min_contour_width_compensated + SCALED_EPSILON ||
|
||||
input_expoly.area() < min_contour_width_compensated * min_contour_width_compensated * 5.)
|
||||
{
|
||||
// The contour is tiny. Don't correct it.
|
||||
out = input_expoly;
|
||||
}
|
||||
else
|
||||
{
|
||||
EdgeGrid::Grid grid;
|
||||
ExPolygon simplified = input_expoly.simplify(SCALED_EPSILON).front();
|
||||
assert(validate_expoly_orientation(simplified));
|
||||
BoundingBox bbox = get_extents(simplified.contour);
|
||||
bbox.offset(SCALED_EPSILON);
|
||||
grid.set_bbox(bbox);
|
||||
grid.create(simplified, coord_t(0.7 * search_radius));
|
||||
std::vector<std::vector<float>> deltas;
|
||||
deltas.reserve(simplified.holes.size() + 1);
|
||||
ExPolygon resampled(simplified);
|
||||
double resample_interval = scale_(0.5);
|
||||
for (size_t idx_contour = 0; idx_contour <= simplified.holes.size(); ++ idx_contour) {
|
||||
Polygon &poly = (idx_contour == 0) ? resampled.contour : resampled.holes[idx_contour - 1];
|
||||
std::vector<ResampledPoint> resampled_point_parameters;
|
||||
poly.points = resample_polygon(poly.points, resample_interval, resampled_point_parameters);
|
||||
assert(poly.is_counter_clockwise() == (idx_contour == 0));
|
||||
std::vector<float> dists = contour_distance2(grid, idx_contour, poly.points, resampled_point_parameters, scaled_compensation, search_radius);
|
||||
for (float &d : dists) {
|
||||
// printf("Point %d, Distance: %lf\n", int(&d - dists.data()), unscale<double>(d));
|
||||
// Convert contour width to available compensation distance.
|
||||
if (d < min_contour_width)
|
||||
d = 0.f;
|
||||
else if (d > min_contour_width_compensated)
|
||||
d = - float(scaled_compensation);
|
||||
else
|
||||
d = - (d - float(min_contour_width)) / 2.f;
|
||||
assert(d >= - float(scaled_compensation) && d <= 0.f);
|
||||
}
|
||||
// smooth_compensation(dists, 0.4f, 10);
|
||||
smooth_compensation_banded(poly.points, float(0.8 * resample_interval), dists, 0.3f, 3);
|
||||
deltas.emplace_back(dists);
|
||||
}
|
||||
|
||||
ExPolygons out_vec = variable_offset_inner_ex(resampled, deltas, 2.);
|
||||
if (out_vec.size() == 1)
|
||||
out = std::move(out_vec.front());
|
||||
else {
|
||||
// Something went wrong, don't compensate.
|
||||
out = input_expoly;
|
||||
#ifdef TESTS_EXPORT_SVGS
|
||||
if (out_vec.size() > 1) {
|
||||
static int iRun = 0;
|
||||
SVG::export_expolygons(debug_out_path("elephant_foot_compensation-many_contours-%d.svg", iRun ++).c_str(),
|
||||
{ { { input_expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
|
||||
{ { out_vec }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
|
||||
}
|
||||
#endif /* TESTS_EXPORT_SVGS */
|
||||
assert(out_vec.size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
assert(validate_expoly_orientation(out));
|
||||
return out;
|
||||
}
|
||||
|
||||
ExPolygon elephant_foot_compensation(const ExPolygon &input, const Flow &external_perimeter_flow, const double compensation)
|
||||
{
|
||||
// The contour shall be wide enough to apply the external perimeter plus compensation on both sides.
|
||||
double min_contour_width = double(external_perimeter_flow.width() + external_perimeter_flow.spacing());
|
||||
return elephant_foot_compensation(input, min_contour_width, compensation);
|
||||
}
|
||||
|
||||
ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation)
|
||||
{
|
||||
ExPolygons out;
|
||||
out.reserve(input.size());
|
||||
for (const ExPolygon &expoly : input)
|
||||
out.emplace_back(elephant_foot_compensation(expoly, external_perimeter_flow, compensation));
|
||||
return out;
|
||||
}
|
||||
|
||||
ExPolygons elephant_foot_compensation(const ExPolygons &input, double min_contour_width, const double compensation)
|
||||
{
|
||||
ExPolygons out;
|
||||
out.reserve(input.size());
|
||||
for (const ExPolygon &expoly : input)
|
||||
out.emplace_back(elephant_foot_compensation(expoly, min_contour_width, compensation));
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
19
src/libslic3r/ElephantFootCompensation.hpp
Normal file
19
src/libslic3r/ElephantFootCompensation.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef slic3r_ElephantFootCompensation_hpp_
|
||||
#define slic3r_ElephantFootCompensation_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ExPolygon.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Flow;
|
||||
|
||||
ExPolygon elephant_foot_compensation(const ExPolygon &input, double min_countour_width, const double compensation);
|
||||
ExPolygons elephant_foot_compensation(const ExPolygons &input, double min_countour_width, const double compensation);
|
||||
ExPolygon elephant_foot_compensation(const ExPolygon &input, const Flow &external_perimeter_flow, const double compensation);
|
||||
ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation);
|
||||
|
||||
} // Slic3r
|
||||
|
||||
#endif /* slic3r_ElephantFootCompensation_hpp_ */
|
||||
523
src/libslic3r/ExPolygon.cpp
Normal file
523
src/libslic3r/ExPolygon.cpp
Normal file
@@ -0,0 +1,523 @@
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "Geometry/MedialAxis.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <list>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void ExPolygon::scale(double factor)
|
||||
{
|
||||
contour.scale(factor);
|
||||
for (Polygon &hole : holes)
|
||||
hole.scale(factor);
|
||||
}
|
||||
|
||||
void ExPolygon::scale(double factor_x, double factor_y)
|
||||
{
|
||||
contour.scale(factor_x, factor_y);
|
||||
for (Polygon &hole : holes)
|
||||
hole.scale(factor_x, factor_y);
|
||||
}
|
||||
|
||||
void ExPolygon::translate(const Point &p)
|
||||
{
|
||||
contour.translate(p);
|
||||
for (Polygon &hole : holes)
|
||||
hole.translate(p);
|
||||
}
|
||||
|
||||
void ExPolygon::rotate(double angle)
|
||||
{
|
||||
contour.rotate(angle);
|
||||
for (Polygon &hole : holes)
|
||||
hole.rotate(angle);
|
||||
}
|
||||
|
||||
void ExPolygon::rotate(double angle, const Point ¢er)
|
||||
{
|
||||
contour.rotate(angle, center);
|
||||
for (Polygon &hole : holes)
|
||||
hole.rotate(angle, center);
|
||||
}
|
||||
|
||||
double ExPolygon::area() const
|
||||
{
|
||||
double a = this->contour.area();
|
||||
for (const Polygon &hole : holes)
|
||||
a -= - hole.area(); // holes have negative area
|
||||
return a;
|
||||
}
|
||||
|
||||
bool ExPolygon::is_valid() const
|
||||
{
|
||||
if (!this->contour.is_valid() || !this->contour.is_counter_clockwise()) return false;
|
||||
for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) {
|
||||
if (!(*it).is_valid() || (*it).is_counter_clockwise()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExPolygon::douglas_peucker(double tolerance)
|
||||
{
|
||||
this->contour.douglas_peucker(tolerance);
|
||||
for (Polygon &poly : this->holes)
|
||||
poly.douglas_peucker(tolerance);
|
||||
}
|
||||
|
||||
bool ExPolygon::contains(const Line &line) const
|
||||
{
|
||||
return this->contains(Polyline(line.a, line.b));
|
||||
}
|
||||
|
||||
bool ExPolygon::contains(const Polyline &polyline) const
|
||||
{
|
||||
BoundingBox bbox1 = get_extents(*this);
|
||||
BoundingBox bbox2 = get_extents(polyline);
|
||||
bbox2.inflated(1);
|
||||
if (!bbox1.overlap(bbox2))
|
||||
return false;
|
||||
|
||||
return diff_pl(polyline, *this).empty();
|
||||
}
|
||||
|
||||
bool ExPolygon::contains(const Polylines &polylines) const
|
||||
{
|
||||
#if 0
|
||||
BoundingBox bbox = get_extents(polylines);
|
||||
bbox.merge(get_extents(*this));
|
||||
SVG svg(debug_out_path("ExPolygon_contains.svg"), bbox);
|
||||
svg.draw(*this);
|
||||
svg.draw_outline(*this);
|
||||
svg.draw(polylines, "blue");
|
||||
#endif
|
||||
Polylines pl_out = diff_pl(polylines, *this);
|
||||
#if 0
|
||||
svg.draw(pl_out, "red");
|
||||
#endif
|
||||
return pl_out.empty();
|
||||
}
|
||||
|
||||
bool ExPolygon::contains(const Point &point, bool border_result /* = true */) const
|
||||
{
|
||||
if (! Slic3r::contains(contour, point, border_result))
|
||||
// Outside the outer contour, not on the contour boundary.
|
||||
return false;
|
||||
for (const Polygon &hole : this->holes)
|
||||
if (Slic3r::contains(hole, point, ! border_result))
|
||||
// Inside a hole, not on the hole boundary.
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ExPolygon::on_boundary(const Point &point, double eps) const
|
||||
{
|
||||
if (this->contour.on_boundary(point, eps))
|
||||
return true;
|
||||
for (const Polygon &hole : this->holes)
|
||||
if (hole.on_boundary(point, eps))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Projection of a point onto the polygon.
|
||||
Point ExPolygon::point_projection(const Point &point) const
|
||||
{
|
||||
if (this->holes.empty()) {
|
||||
return this->contour.point_projection(point);
|
||||
} else {
|
||||
double dist_min2 = std::numeric_limits<double>::max();
|
||||
Point closest_pt_min;
|
||||
for (size_t i = 0; i < this->num_contours(); ++ i) {
|
||||
Point closest_pt = this->contour_or_hole(i).point_projection(point);
|
||||
double d2 = (closest_pt - point).cast<double>().squaredNorm();
|
||||
if (d2 < dist_min2) {
|
||||
dist_min2 = d2;
|
||||
closest_pt_min = closest_pt;
|
||||
}
|
||||
}
|
||||
return closest_pt_min;
|
||||
}
|
||||
}
|
||||
|
||||
bool ExPolygon::overlaps(const ExPolygon &other) const
|
||||
{
|
||||
if (this->empty() || other.empty())
|
||||
return false;
|
||||
|
||||
#if 0
|
||||
BoundingBox bbox = get_extents(other);
|
||||
bbox.merge(get_extents(*this));
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("ExPolygon_overlaps-%d.svg", iRun ++), bbox);
|
||||
svg.draw(*this);
|
||||
svg.draw_outline(*this);
|
||||
svg.draw_outline(other, "blue");
|
||||
#endif
|
||||
|
||||
Polylines pl_out = intersection_pl(to_polylines(other), *this);
|
||||
|
||||
#if 0
|
||||
svg.draw(pl_out, "red");
|
||||
#endif
|
||||
|
||||
// See unit test SCENARIO("Clipper diff with polyline", "[Clipper]")
|
||||
// for in which case the intersection_pl produces any intersection.
|
||||
return ! pl_out.empty() ||
|
||||
// If *this is completely inside other, then pl_out is empty, but the expolygons overlap. Test for that situation.
|
||||
other.contains(this->contour.points.front());
|
||||
}
|
||||
|
||||
bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2)
|
||||
{
|
||||
for (const ExPolygon& expoly1 : expolys1) {
|
||||
for (const ExPolygon& expoly2 : expolys2) {
|
||||
if (expoly1.overlaps(expoly2))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Point projection_onto(const ExPolygons& polygons, const Point& from)
|
||||
{
|
||||
Point projected_pt;
|
||||
double min_dist = std::numeric_limits<double>::max();
|
||||
|
||||
for (const auto& poly : polygons) {
|
||||
for (int i = 0; i < poly.num_contours(); i++) {
|
||||
Point p = from.projection_onto(poly.contour_or_hole(i));
|
||||
double dist = (from - p).cast<double>().squaredNorm();
|
||||
if (dist < min_dist) {
|
||||
projected_pt = p;
|
||||
min_dist = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return projected_pt;
|
||||
}
|
||||
|
||||
void ExPolygon::simplify_p(double tolerance, Polygons* polygons) const
|
||||
{
|
||||
Polygons pp = this->simplify_p(tolerance);
|
||||
polygons->insert(polygons->end(), pp.begin(), pp.end());
|
||||
}
|
||||
|
||||
Polygons ExPolygon::simplify_p(double tolerance) const
|
||||
{
|
||||
Polygons pp;
|
||||
pp.reserve(this->holes.size() + 1);
|
||||
// contour
|
||||
{
|
||||
Polygon p = this->contour;
|
||||
p.points.push_back(p.points.front());
|
||||
p.points = MultiPoint::_douglas_peucker(p.points, tolerance);
|
||||
p.points.pop_back();
|
||||
pp.emplace_back(std::move(p));
|
||||
}
|
||||
// holes
|
||||
for (Polygon p : this->holes) {
|
||||
p.points.push_back(p.points.front());
|
||||
p.points = MultiPoint::_douglas_peucker(p.points, tolerance);
|
||||
p.points.pop_back();
|
||||
pp.emplace_back(std::move(p));
|
||||
}
|
||||
return simplify_polygons(pp);
|
||||
}
|
||||
|
||||
ExPolygons ExPolygon::simplify(double tolerance) const
|
||||
{
|
||||
return union_ex(this->simplify_p(tolerance));
|
||||
}
|
||||
|
||||
void ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const
|
||||
{
|
||||
append(*expolygons, this->simplify(tolerance));
|
||||
}
|
||||
|
||||
void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* polylines) const
|
||||
{
|
||||
// init helper object
|
||||
Slic3r::Geometry::MedialAxis ma(min_width, max_width, *this);
|
||||
|
||||
// compute the Voronoi diagram and extract medial axis polylines
|
||||
ThickPolylines pp;
|
||||
ma.build(&pp);
|
||||
|
||||
/*
|
||||
SVG svg("medial_axis.svg");
|
||||
svg.draw(*this);
|
||||
svg.draw(pp);
|
||||
svg.Close();
|
||||
*/
|
||||
|
||||
/* Find the maximum width returned; we're going to use this for validating and
|
||||
filtering the output segments. */
|
||||
double max_w = 0;
|
||||
for (ThickPolylines::const_iterator it = pp.begin(); it != pp.end(); ++it)
|
||||
max_w = fmaxf(max_w, *std::max_element(it->width.begin(), it->width.end()));
|
||||
|
||||
/* Loop through all returned polylines in order to extend their endpoints to the
|
||||
expolygon boundaries */
|
||||
bool removed = false;
|
||||
for (size_t i = 0; i < pp.size(); ++i) {
|
||||
ThickPolyline& polyline = pp[i];
|
||||
|
||||
// extend initial and final segments of each polyline if they're actual endpoints
|
||||
/* We assign new endpoints to temporary variables because in case of a single-line
|
||||
polyline, after we extend the start point it will be caught by the intersection()
|
||||
call, so we keep the inner point until we perform the second intersection() as well */
|
||||
Point new_front = polyline.points.front();
|
||||
Point new_back = polyline.points.back();
|
||||
if (polyline.endpoints.first && !this->on_boundary(new_front, SCALED_EPSILON)) {
|
||||
Vec2d p1 = polyline.points.front().cast<double>();
|
||||
Vec2d p2 = polyline.points[1].cast<double>();
|
||||
// prevent the line from touching on the other side, otherwise intersection() might return that solution
|
||||
if (polyline.points.size() == 2)
|
||||
p2 = (p1 + p2) * 0.5;
|
||||
// Extend the start of the segment.
|
||||
p1 -= (p2 - p1).normalized() * max_width;
|
||||
this->contour.intersection(Line(p1.cast<coord_t>(), p2.cast<coord_t>()), &new_front);
|
||||
}
|
||||
if (polyline.endpoints.second && !this->on_boundary(new_back, SCALED_EPSILON)) {
|
||||
Vec2d p1 = (polyline.points.end() - 2)->cast<double>();
|
||||
Vec2d p2 = polyline.points.back().cast<double>();
|
||||
// prevent the line from touching on the other side, otherwise intersection() might return that solution
|
||||
if (polyline.points.size() == 2)
|
||||
p1 = (p1 + p2) * 0.5;
|
||||
// Extend the start of the segment.
|
||||
p2 += (p2 - p1).normalized() * max_width;
|
||||
this->contour.intersection(Line(p1.cast<coord_t>(), p2.cast<coord_t>()), &new_back);
|
||||
}
|
||||
polyline.points.front() = new_front;
|
||||
polyline.points.back() = new_back;
|
||||
|
||||
/* remove too short polylines
|
||||
(we can't do this check before endpoints extension and clipping because we don't
|
||||
know how long will the endpoints be extended since it depends on polygon thickness
|
||||
which is variable - extension will be <= max_width/2 on each side) */
|
||||
if ((polyline.endpoints.first || polyline.endpoints.second)
|
||||
&& polyline.length() < max_w*2) {
|
||||
pp.erase(pp.begin() + i);
|
||||
--i;
|
||||
removed = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* If we removed any short polylines we now try to connect consecutive polylines
|
||||
in order to allow loop detection. Note that this algorithm is greedier than
|
||||
MedialAxis::process_edge_neighbors() as it will connect random pairs of
|
||||
polylines even when more than two start from the same point. This has no
|
||||
drawbacks since we optimize later using nearest-neighbor which would do the
|
||||
same, but should we use a more sophisticated optimization algorithm we should
|
||||
not connect polylines when more than two meet. */
|
||||
if (removed) {
|
||||
for (size_t i = 0; i < pp.size(); ++i) {
|
||||
ThickPolyline& polyline = pp[i];
|
||||
if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization
|
||||
|
||||
// find another polyline starting here
|
||||
for (size_t j = i+1; j < pp.size(); ++j) {
|
||||
ThickPolyline& other = pp[j];
|
||||
if (polyline.last_point() == other.last_point()) {
|
||||
other.reverse();
|
||||
} else if (polyline.first_point() == other.last_point()) {
|
||||
polyline.reverse();
|
||||
other.reverse();
|
||||
} else if (polyline.first_point() == other.first_point()) {
|
||||
polyline.reverse();
|
||||
} else if (polyline.last_point() != other.first_point()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
polyline.points.insert(polyline.points.end(), other.points.begin() + 1, other.points.end());
|
||||
polyline.width.insert(polyline.width.end(), other.width.begin(), other.width.end());
|
||||
polyline.endpoints.second = other.endpoints.second;
|
||||
assert(polyline.width.size() == polyline.points.size()*2 - 2);
|
||||
|
||||
pp.erase(pp.begin() + j);
|
||||
j = i; // restart search from i+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
polylines->insert(polylines->end(), pp.begin(), pp.end());
|
||||
}
|
||||
|
||||
void ExPolygon::medial_axis(double min_width, double max_width, Polylines* polylines) const
|
||||
{
|
||||
ThickPolylines tp;
|
||||
this->medial_axis(min_width, max_width, &tp);
|
||||
polylines->reserve(polylines->size() + tp.size());
|
||||
for (auto &pl : tp)
|
||||
polylines->emplace_back(pl.points);
|
||||
}
|
||||
|
||||
Lines ExPolygon::lines() const
|
||||
{
|
||||
Lines lines = this->contour.lines();
|
||||
for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) {
|
||||
Lines hole_lines = h->lines();
|
||||
lines.insert(lines.end(), hole_lines.begin(), hole_lines.end());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
// Do expolygons match? If they match, they must have the same topology,
|
||||
// however their contours may be rotated.
|
||||
bool expolygons_match(const ExPolygon &l, const ExPolygon &r)
|
||||
{
|
||||
if (l.holes.size() != r.holes.size() || ! polygons_match(l.contour, r.contour))
|
||||
return false;
|
||||
for (size_t hole_idx = 0; hole_idx < l.holes.size(); ++ hole_idx)
|
||||
if (! polygons_match(l.holes[hole_idx], r.holes[hole_idx]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
BoundingBox get_extents(const ExPolygon &expolygon)
|
||||
{
|
||||
return get_extents(expolygon.contour);
|
||||
}
|
||||
|
||||
BoundingBox get_extents(const ExPolygons &expolygons)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
if (! expolygons.empty()) {
|
||||
for (size_t i = 0; i < expolygons.size(); ++ i)
|
||||
if (! expolygons[i].contour.points.empty())
|
||||
bbox.merge(get_extents(expolygons[i]));
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
BoundingBox get_extents_rotated(const ExPolygon &expolygon, double angle)
|
||||
{
|
||||
return get_extents_rotated(expolygon.contour, angle);
|
||||
}
|
||||
|
||||
BoundingBox get_extents_rotated(const ExPolygons &expolygons, double angle)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
if (! expolygons.empty()) {
|
||||
bbox = get_extents_rotated(expolygons.front().contour, angle);
|
||||
for (size_t i = 1; i < expolygons.size(); ++ i)
|
||||
bbox.merge(get_extents_rotated(expolygons[i].contour, angle));
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
extern std::vector<BoundingBox> get_extents_vector(const ExPolygons &polygons)
|
||||
{
|
||||
std::vector<BoundingBox> out;
|
||||
out.reserve(polygons.size());
|
||||
for (ExPolygons::const_iterator it = polygons.begin(); it != polygons.end(); ++ it)
|
||||
out.push_back(get_extents(*it));
|
||||
return out;
|
||||
}
|
||||
|
||||
bool has_duplicate_points(const ExPolygon &expoly)
|
||||
{
|
||||
#if 1
|
||||
// Check globally.
|
||||
size_t cnt = expoly.contour.points.size();
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
cnt += hole.points.size();
|
||||
std::vector<Point> allpts;
|
||||
allpts.reserve(cnt);
|
||||
allpts.insert(allpts.begin(), expoly.contour.points.begin(), expoly.contour.points.end());
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
allpts.insert(allpts.end(), hole.points.begin(), hole.points.end());
|
||||
return has_duplicate_points(std::move(allpts));
|
||||
#else
|
||||
// Check per contour.
|
||||
if (has_duplicate_points(expoly.contour))
|
||||
return true;
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
if (has_duplicate_points(hole))
|
||||
return true;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool has_duplicate_points(const ExPolygons &expolys)
|
||||
{
|
||||
#if 1
|
||||
// Check globally.
|
||||
size_t cnt = 0;
|
||||
for (const ExPolygon &expoly : expolys) {
|
||||
cnt += expoly.contour.points.size();
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
cnt += hole.points.size();
|
||||
}
|
||||
std::vector<Point> allpts;
|
||||
allpts.reserve(cnt);
|
||||
for (const ExPolygon &expoly : expolys) {
|
||||
allpts.insert(allpts.begin(), expoly.contour.points.begin(), expoly.contour.points.end());
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
allpts.insert(allpts.end(), hole.points.begin(), hole.points.end());
|
||||
}
|
||||
return has_duplicate_points(std::move(allpts));
|
||||
#else
|
||||
// Check per contour.
|
||||
for (const ExPolygon &expoly : expolys)
|
||||
if (has_duplicate_points(expoly))
|
||||
return true;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool remove_sticks(ExPolygon &poly)
|
||||
{
|
||||
return remove_sticks(poly.contour) || remove_sticks(poly.holes);
|
||||
}
|
||||
|
||||
bool remove_small_and_small_holes(ExPolygons &expolygons, double min_area)
|
||||
{
|
||||
bool modified = false;
|
||||
size_t free_idx = 0;
|
||||
for (size_t expoly_idx = 0; expoly_idx < expolygons.size(); ++expoly_idx) {
|
||||
if (std::abs(expolygons[expoly_idx].area()) >= min_area) {
|
||||
// Expolygon is big enough, so also check all its holes
|
||||
modified |= remove_small(expolygons[expoly_idx].holes, min_area);
|
||||
if (free_idx < expoly_idx) {
|
||||
std::swap(expolygons[expoly_idx].contour, expolygons[free_idx].contour);
|
||||
std::swap(expolygons[expoly_idx].holes, expolygons[free_idx].holes);
|
||||
}
|
||||
++free_idx;
|
||||
} else
|
||||
modified = true;
|
||||
}
|
||||
if (free_idx < expolygons.size())
|
||||
expolygons.erase(expolygons.begin() + free_idx, expolygons.end());
|
||||
return modified;
|
||||
}
|
||||
|
||||
void keep_largest_contour_only(ExPolygons &polygons)
|
||||
{
|
||||
if (polygons.size() > 1) {
|
||||
double max_area = 0.;
|
||||
ExPolygon* max_area_polygon = nullptr;
|
||||
for (ExPolygon& p : polygons) {
|
||||
double a = p.contour.area();
|
||||
if (a > max_area) {
|
||||
max_area = a;
|
||||
max_area_polygon = &p;
|
||||
}
|
||||
}
|
||||
assert(max_area_polygon != nullptr);
|
||||
ExPolygon p(std::move(*max_area_polygon));
|
||||
polygons.clear();
|
||||
polygons.emplace_back(std::move(p));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
583
src/libslic3r/ExPolygon.hpp
Normal file
583
src/libslic3r/ExPolygon.hpp
Normal file
@@ -0,0 +1,583 @@
|
||||
#ifndef slic3r_ExPolygon_hpp_
|
||||
#define slic3r_ExPolygon_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
using ExPolygons = std::vector<ExPolygon>;
|
||||
|
||||
class ExPolygon
|
||||
{
|
||||
public:
|
||||
ExPolygon() = default;
|
||||
ExPolygon(const ExPolygon &other) = default;
|
||||
ExPolygon(ExPolygon &&other) = default;
|
||||
explicit ExPolygon(const Polygon &contour) : contour(contour) {}
|
||||
explicit ExPolygon(Polygon &&contour) : contour(std::move(contour)) {}
|
||||
explicit ExPolygon(const Points &contour) : contour(contour) {}
|
||||
explicit ExPolygon(Points &&contour) : contour(std::move(contour)) {}
|
||||
explicit ExPolygon(const Polygon &contour, const Polygon &hole) : contour(contour) { holes.emplace_back(hole); }
|
||||
explicit ExPolygon(Polygon &&contour, Polygon &&hole) : contour(std::move(contour)) { holes.emplace_back(std::move(hole)); }
|
||||
explicit ExPolygon(const Points &contour, const Points &hole) : contour(contour) { holes.emplace_back(hole); }
|
||||
explicit ExPolygon(Points &&contour, Polygon &&hole) : contour(std::move(contour)) { holes.emplace_back(std::move(hole)); }
|
||||
ExPolygon(std::initializer_list<Point> contour) : contour(contour) {}
|
||||
ExPolygon(std::initializer_list<Point> contour, std::initializer_list<Point> hole) : contour(contour), holes({ hole }) {}
|
||||
|
||||
ExPolygon& operator=(const ExPolygon &other) = default;
|
||||
ExPolygon& operator=(ExPolygon &&other) = default;
|
||||
|
||||
Polygon contour; //CCW
|
||||
Polygons holes; //CW
|
||||
|
||||
void clear() { contour.points.clear(); holes.clear(); }
|
||||
void scale(double factor);
|
||||
void scale(double factor_x, double factor_y);
|
||||
void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); }
|
||||
void translate(const Point &vector);
|
||||
void rotate(double angle);
|
||||
void rotate(double angle, const Point ¢er);
|
||||
double area() const;
|
||||
bool empty() const { return contour.points.empty(); }
|
||||
bool is_valid() const;
|
||||
void douglas_peucker(double tolerance);
|
||||
|
||||
// Contains the line / polyline / polylines etc COMPLETELY.
|
||||
bool contains(const Line &line) const;
|
||||
bool contains(const Polyline &polyline) const;
|
||||
bool contains(const Polylines &polylines) const;
|
||||
bool contains(const Point &point, bool border_result = true) const;
|
||||
// Approximate on boundary test.
|
||||
bool on_boundary(const Point &point, double eps) const;
|
||||
// Projection of a point onto the polygon.
|
||||
Point point_projection(const Point &point) const;
|
||||
|
||||
// Does this expolygon overlap another expolygon?
|
||||
// Either the ExPolygons intersect, or one is fully inside the other,
|
||||
// and it is not inside a hole of the other expolygon.
|
||||
// The test may not be commutative if the two expolygons touch by a boundary only,
|
||||
// see unit test SCENARIO("Clipper diff with polyline", "[Clipper]").
|
||||
// Namely expolygons touching at a vertical boundary are considered overlapping, while expolygons touching
|
||||
// at a horizontal boundary are NOT considered overlapping.
|
||||
bool overlaps(const ExPolygon &other) const;
|
||||
|
||||
void simplify_p(double tolerance, Polygons* polygons) const;
|
||||
Polygons simplify_p(double tolerance) const;
|
||||
ExPolygons simplify(double tolerance) const;
|
||||
void simplify(double tolerance, ExPolygons* expolygons) const;
|
||||
void medial_axis(double min_width, double max_width, ThickPolylines* polylines) const;
|
||||
void medial_axis(double min_width, double max_width, Polylines* polylines) const;
|
||||
Polylines medial_axis(double min_width, double max_width) const
|
||||
{ Polylines out; this->medial_axis(min_width, max_width, &out); return out; }
|
||||
Lines lines() const;
|
||||
|
||||
// Number of contours (outer contour with holes).
|
||||
size_t num_contours() const { return this->holes.size() + 1; }
|
||||
Polygon& contour_or_hole(size_t idx) { return (idx == 0) ? this->contour : this->holes[idx - 1]; }
|
||||
const Polygon& contour_or_hole(size_t idx) const { return (idx == 0) ? this->contour : this->holes[idx - 1]; }
|
||||
};
|
||||
|
||||
inline bool operator==(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour == rhs.contour && lhs.holes == rhs.holes; }
|
||||
inline bool operator!=(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour != rhs.contour || lhs.holes != rhs.holes; }
|
||||
|
||||
inline size_t count_points(const ExPolygons &expolys)
|
||||
{
|
||||
size_t n_points = 0;
|
||||
for (const auto &expoly : expolys) {
|
||||
n_points += expoly.contour.points.size();
|
||||
for (const auto &hole : expoly.holes)
|
||||
n_points += hole.points.size();
|
||||
}
|
||||
return n_points;
|
||||
}
|
||||
|
||||
inline size_t count_points(const ExPolygon &expoly)
|
||||
{
|
||||
size_t n_points = expoly.contour.points.size();
|
||||
for (const auto &hole : expoly.holes)
|
||||
n_points += hole.points.size();
|
||||
return n_points;
|
||||
}
|
||||
|
||||
// Count a nuber of polygons stored inside the vector of expolygons.
|
||||
// Useful for allocating space for polygons when converting expolygons to polygons.
|
||||
inline size_t number_polygons(const ExPolygons &expolys)
|
||||
{
|
||||
size_t n_polygons = 0;
|
||||
for (const ExPolygon &ex : expolys)
|
||||
n_polygons += ex.holes.size() + 1;
|
||||
return n_polygons;
|
||||
}
|
||||
|
||||
inline Lines to_lines(const ExPolygon &src)
|
||||
{
|
||||
Lines lines;
|
||||
lines.reserve(count_points(src));
|
||||
for (size_t i = 0; i <= src.holes.size(); ++ i) {
|
||||
const Polygon &poly = (i == 0) ? src.contour : src.holes[i - 1];
|
||||
for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it)
|
||||
lines.push_back(Line(*it, *(it + 1)));
|
||||
lines.push_back(Line(poly.points.back(), poly.points.front()));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
inline Lines to_lines(const ExPolygons &src)
|
||||
{
|
||||
Lines lines;
|
||||
lines.reserve(count_points(src));
|
||||
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) {
|
||||
for (size_t i = 0; i <= it_expoly->holes.size(); ++ i) {
|
||||
const Points &points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points;
|
||||
for (Points::const_iterator it = points.begin(); it != points.end()-1; ++it)
|
||||
lines.push_back(Line(*it, *(it + 1)));
|
||||
lines.push_back(Line(points.back(), points.front()));
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
// Line is from point index(see to_points) to next point.
|
||||
// Next point of last point in polygon is first polygon point.
|
||||
inline Linesf to_linesf(const ExPolygons &src, uint32_t count_lines = 0)
|
||||
{
|
||||
assert(count_lines == 0 || count_lines == count_points(src));
|
||||
if (count_lines == 0) count_lines = count_points(src);
|
||||
Linesf lines;
|
||||
lines.reserve(count_lines);
|
||||
Vec2d prev_pd;
|
||||
auto to_lines = [&lines, &prev_pd](const Points &pts) {
|
||||
assert(pts.size() >= 3);
|
||||
if (pts.size() < 2) return;
|
||||
bool is_first = true;
|
||||
for (const Point &p : pts) {
|
||||
Vec2d pd = p.cast<double>();
|
||||
if (is_first) is_first = false;
|
||||
else lines.emplace_back(prev_pd, pd);
|
||||
prev_pd = pd;
|
||||
}
|
||||
lines.emplace_back(prev_pd, pts.front().cast<double>());
|
||||
};
|
||||
for (const ExPolygon& expoly: src) {
|
||||
to_lines(expoly.contour.points);
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
to_lines(hole.points);
|
||||
}
|
||||
assert(lines.size() == count_lines);
|
||||
return lines;
|
||||
}
|
||||
|
||||
inline Linesf to_unscaled_linesf(const ExPolygons &src)
|
||||
{
|
||||
Linesf lines;
|
||||
lines.reserve(count_points(src));
|
||||
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) {
|
||||
for (size_t i = 0; i <= it_expoly->holes.size(); ++ i) {
|
||||
const Points &points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points;
|
||||
Vec2d unscaled_a = unscaled(points.front());
|
||||
Vec2d unscaled_b = unscaled_a;
|
||||
for (Points::const_iterator it = points.begin()+1; it != points.end(); ++it){
|
||||
unscaled_b = unscaled(*(it));
|
||||
lines.push_back(Linef(unscaled_a, unscaled_b));
|
||||
unscaled_a = unscaled_b;
|
||||
}
|
||||
lines.push_back(Linef(unscaled_a, unscaled(points.front())));
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
|
||||
inline Points to_points(const ExPolygons &src)
|
||||
{
|
||||
Points points;
|
||||
size_t count = count_points(src);
|
||||
points.reserve(count);
|
||||
for (const ExPolygon &expolygon : src) {
|
||||
append(points, expolygon.contour.points);
|
||||
for (const Polygon &hole : expolygon.holes)
|
||||
append(points, hole.points);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
inline Polylines to_polylines(const ExPolygon &src)
|
||||
{
|
||||
Polylines polylines;
|
||||
polylines.assign(src.holes.size() + 1, Polyline());
|
||||
size_t idx = 0;
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = src.contour.points;
|
||||
pl.points.push_back(pl.points.front());
|
||||
for (Polygons::const_iterator ith = src.holes.begin(); ith != src.holes.end(); ++ith) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = ith->points;
|
||||
pl.points.push_back(ith->points.front());
|
||||
}
|
||||
assert(idx == polylines.size());
|
||||
return polylines;
|
||||
}
|
||||
|
||||
inline Polylines to_polylines(const ExPolygons &src)
|
||||
{
|
||||
Polylines polylines;
|
||||
polylines.assign(number_polygons(src), Polyline());
|
||||
size_t idx = 0;
|
||||
for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++it) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = it->contour.points;
|
||||
pl.points.push_back(pl.points.front());
|
||||
for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = ith->points;
|
||||
pl.points.push_back(ith->points.front());
|
||||
}
|
||||
}
|
||||
assert(idx == polylines.size());
|
||||
return polylines;
|
||||
}
|
||||
|
||||
inline Polylines to_polylines(ExPolygon &&src)
|
||||
{
|
||||
Polylines polylines;
|
||||
polylines.assign(src.holes.size() + 1, Polyline());
|
||||
size_t idx = 0;
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = std::move(src.contour.points);
|
||||
pl.points.push_back(pl.points.front());
|
||||
for (auto ith = src.holes.begin(); ith != src.holes.end(); ++ith) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = std::move(ith->points);
|
||||
pl.points.push_back(pl.points.front());
|
||||
}
|
||||
assert(idx == polylines.size());
|
||||
return polylines;
|
||||
}
|
||||
|
||||
inline Polylines to_polylines(ExPolygons &&src)
|
||||
{
|
||||
Polylines polylines;
|
||||
polylines.assign(number_polygons(src), Polyline());
|
||||
size_t idx = 0;
|
||||
for (auto it = src.begin(); it != src.end(); ++it) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = std::move(it->contour.points);
|
||||
pl.points.push_back(pl.points.front());
|
||||
for (auto ith = it->holes.begin(); ith != it->holes.end(); ++ith) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = std::move(ith->points);
|
||||
pl.points.push_back(pl.points.front());
|
||||
}
|
||||
}
|
||||
assert(idx == polylines.size());
|
||||
return polylines;
|
||||
}
|
||||
|
||||
inline Polygons to_polygons(const ExPolygon &src)
|
||||
{
|
||||
Polygons polygons;
|
||||
polygons.reserve(src.holes.size() + 1);
|
||||
polygons.push_back(src.contour);
|
||||
polygons.insert(polygons.end(), src.holes.begin(), src.holes.end());
|
||||
return polygons;
|
||||
}
|
||||
|
||||
inline Polygons to_polygons(const ExPolygons &src)
|
||||
{
|
||||
Polygons polygons;
|
||||
polygons.reserve(number_polygons(src));
|
||||
for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++it) {
|
||||
polygons.push_back(it->contour);
|
||||
polygons.insert(polygons.end(), it->holes.begin(), it->holes.end());
|
||||
}
|
||||
return polygons;
|
||||
}
|
||||
|
||||
inline ConstPolygonPtrs to_polygon_ptrs(const ExPolygon &src)
|
||||
{
|
||||
ConstPolygonPtrs polygons;
|
||||
polygons.reserve(src.holes.size() + 1);
|
||||
polygons.emplace_back(&src.contour);
|
||||
for (const Polygon &hole : src.holes)
|
||||
polygons.emplace_back(&hole);
|
||||
return polygons;
|
||||
}
|
||||
|
||||
inline ConstPolygonPtrs to_polygon_ptrs(const ExPolygons &src)
|
||||
{
|
||||
ConstPolygonPtrs polygons;
|
||||
polygons.reserve(number_polygons(src));
|
||||
for (const ExPolygon &expoly : src) {
|
||||
polygons.emplace_back(&expoly.contour);
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
polygons.emplace_back(&hole);
|
||||
}
|
||||
return polygons;
|
||||
}
|
||||
|
||||
inline Polygons to_polygons(ExPolygon &&src)
|
||||
{
|
||||
Polygons polygons;
|
||||
polygons.reserve(src.holes.size() + 1);
|
||||
polygons.push_back(std::move(src.contour));
|
||||
polygons.insert(polygons.end(),
|
||||
std::make_move_iterator(src.holes.begin()),
|
||||
std::make_move_iterator(src.holes.end()));
|
||||
return polygons;
|
||||
}
|
||||
|
||||
inline Polygons to_polygons(ExPolygons &&src)
|
||||
{
|
||||
Polygons polygons;
|
||||
polygons.reserve(number_polygons(src));
|
||||
for (ExPolygon& expoly: src) {
|
||||
polygons.push_back(std::move(expoly.contour));
|
||||
polygons.insert(polygons.end(),
|
||||
std::make_move_iterator(expoly.holes.begin()),
|
||||
std::make_move_iterator(expoly.holes.end()));
|
||||
}
|
||||
return polygons;
|
||||
}
|
||||
|
||||
inline ExPolygons to_expolygons(const Polygons &polys)
|
||||
{
|
||||
ExPolygons ex_polys;
|
||||
ex_polys.assign(polys.size(), ExPolygon());
|
||||
for (size_t idx = 0; idx < polys.size(); ++idx)
|
||||
ex_polys[idx].contour = polys[idx];
|
||||
return ex_polys;
|
||||
}
|
||||
|
||||
inline ExPolygons to_expolygons(Polygons &&polys)
|
||||
{
|
||||
ExPolygons ex_polys;
|
||||
ex_polys.assign(polys.size(), ExPolygon());
|
||||
for (size_t idx = 0; idx < polys.size(); ++idx)
|
||||
ex_polys[idx].contour = std::move(polys[idx]);
|
||||
return ex_polys;
|
||||
}
|
||||
|
||||
inline Points to_points(const ExPolygon &expoly)
|
||||
{
|
||||
Points out;
|
||||
out.reserve(count_points(expoly));
|
||||
append(out, expoly.contour.points);
|
||||
for (const Polygon &hole : expoly.holes)
|
||||
append(out, hole.points);
|
||||
return out;
|
||||
}
|
||||
|
||||
inline void polygons_append(Polygons &dst, const ExPolygon &src)
|
||||
{
|
||||
dst.reserve(dst.size() + src.holes.size() + 1);
|
||||
dst.push_back(src.contour);
|
||||
dst.insert(dst.end(), src.holes.begin(), src.holes.end());
|
||||
}
|
||||
|
||||
inline void polygons_append(Polygons &dst, const ExPolygons &src)
|
||||
{
|
||||
dst.reserve(dst.size() + number_polygons(src));
|
||||
for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) {
|
||||
dst.push_back(it->contour);
|
||||
dst.insert(dst.end(), it->holes.begin(), it->holes.end());
|
||||
}
|
||||
}
|
||||
|
||||
inline void polygons_append(Polygons &dst, ExPolygon &&src)
|
||||
{
|
||||
dst.reserve(dst.size() + src.holes.size() + 1);
|
||||
dst.push_back(std::move(src.contour));
|
||||
dst.insert(dst.end(),
|
||||
std::make_move_iterator(src.holes.begin()),
|
||||
std::make_move_iterator(src.holes.end()));
|
||||
}
|
||||
|
||||
inline void polygons_append(Polygons &dst, ExPolygons &&src)
|
||||
{
|
||||
dst.reserve(dst.size() + number_polygons(src));
|
||||
for (ExPolygon& expoly: src) {
|
||||
dst.push_back(std::move(expoly.contour));
|
||||
dst.insert(dst.end(),
|
||||
std::make_move_iterator(expoly.holes.begin()),
|
||||
std::make_move_iterator(expoly.holes.end()));
|
||||
}
|
||||
}
|
||||
|
||||
inline void expolygons_append(ExPolygons &dst, const ExPolygons &src)
|
||||
{
|
||||
dst.insert(dst.end(), src.begin(), src.end());
|
||||
}
|
||||
|
||||
inline void expolygons_append(ExPolygons &dst, ExPolygons &&src)
|
||||
{
|
||||
if (dst.empty()) {
|
||||
dst = std::move(src);
|
||||
} else {
|
||||
dst.insert(dst.end(),
|
||||
std::make_move_iterator(src.begin()),
|
||||
std::make_move_iterator(src.end()));
|
||||
}
|
||||
}
|
||||
|
||||
inline void expolygons_rotate(ExPolygons &expolys, double angle)
|
||||
{
|
||||
for (ExPolygon &expoly : expolys)
|
||||
expoly.rotate(angle);
|
||||
}
|
||||
|
||||
inline bool expolygons_contain(ExPolygons &expolys, const Point &pt, bool border_result = true)
|
||||
{
|
||||
for (const ExPolygon &expoly : expolys)
|
||||
if (expoly.contains(pt, border_result))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline ExPolygons expolygons_simplify(const ExPolygons &expolys, double tolerance)
|
||||
{
|
||||
ExPolygons out;
|
||||
out.reserve(expolys.size());
|
||||
for (const ExPolygon &exp : expolys)
|
||||
exp.simplify(tolerance, &out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Do expolygons match? If they match, they must have the same topology,
|
||||
// however their contours may be rotated.
|
||||
bool expolygons_match(const ExPolygon &l, const ExPolygon &r);
|
||||
|
||||
bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2);
|
||||
|
||||
Point projection_onto(const ExPolygons& expolys, const Point& pt);
|
||||
|
||||
BoundingBox get_extents(const ExPolygon &expolygon);
|
||||
BoundingBox get_extents(const ExPolygons &expolygons);
|
||||
BoundingBox get_extents_rotated(const ExPolygon &poly, double angle);
|
||||
BoundingBox get_extents_rotated(const ExPolygons &polygons, double angle);
|
||||
std::vector<BoundingBox> get_extents_vector(const ExPolygons &polygons);
|
||||
|
||||
// Test for duplicate points. The points are copied, sorted and checked for duplicates globally.
|
||||
bool has_duplicate_points(const ExPolygon &expoly);
|
||||
bool has_duplicate_points(const ExPolygons &expolys);
|
||||
|
||||
bool remove_sticks(ExPolygon &poly);
|
||||
void keep_largest_contour_only(ExPolygons &polygons);
|
||||
|
||||
inline double area(const ExPolygon &poly) { return poly.area(); }
|
||||
inline double area(const ExPolygons &polys) { double s = 0.; for (auto &p : polys) s += p.area(); return s; }
|
||||
|
||||
// Removes all expolygons smaller than min_area and also removes all holes smaller than min_area
|
||||
bool remove_small_and_small_holes(ExPolygons &expolygons, double min_area);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
// start Boost
|
||||
#include <boost/polygon/polygon.hpp>
|
||||
namespace boost { namespace polygon {
|
||||
template <>
|
||||
struct polygon_traits<Slic3r::ExPolygon> {
|
||||
typedef coord_t coordinate_type;
|
||||
typedef Slic3r::Points::const_iterator iterator_type;
|
||||
typedef Slic3r::Point point_type;
|
||||
|
||||
// Get the begin iterator
|
||||
static inline iterator_type begin_points(const Slic3r::ExPolygon& t) {
|
||||
return t.contour.points.begin();
|
||||
}
|
||||
|
||||
// Get the end iterator
|
||||
static inline iterator_type end_points(const Slic3r::ExPolygon& t) {
|
||||
return t.contour.points.end();
|
||||
}
|
||||
|
||||
// Get the number of sides of the polygon
|
||||
static inline std::size_t size(const Slic3r::ExPolygon& t) {
|
||||
return t.contour.points.size();
|
||||
}
|
||||
|
||||
// Get the winding direction of the polygon
|
||||
static inline winding_direction winding(const Slic3r::ExPolygon& /* t */) {
|
||||
return unknown_winding;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct polygon_mutable_traits<Slic3r::ExPolygon> {
|
||||
//expects stl style iterators
|
||||
template <typename iT>
|
||||
static inline Slic3r::ExPolygon& set_points(Slic3r::ExPolygon& expolygon, iT input_begin, iT input_end) {
|
||||
expolygon.contour.points.assign(input_begin, input_end);
|
||||
// skip last point since Boost will set last point = first point
|
||||
expolygon.contour.points.pop_back();
|
||||
return expolygon;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <>
|
||||
struct geometry_concept<Slic3r::ExPolygon> { typedef polygon_with_holes_concept type; };
|
||||
|
||||
template <>
|
||||
struct polygon_with_holes_traits<Slic3r::ExPolygon> {
|
||||
typedef Slic3r::Polygons::const_iterator iterator_holes_type;
|
||||
typedef Slic3r::Polygon hole_type;
|
||||
static inline iterator_holes_type begin_holes(const Slic3r::ExPolygon& t) {
|
||||
return t.holes.begin();
|
||||
}
|
||||
static inline iterator_holes_type end_holes(const Slic3r::ExPolygon& t) {
|
||||
return t.holes.end();
|
||||
}
|
||||
static inline unsigned int size_holes(const Slic3r::ExPolygon& t) {
|
||||
return (int)t.holes.size();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct polygon_with_holes_mutable_traits<Slic3r::ExPolygon> {
|
||||
template <typename iT>
|
||||
static inline Slic3r::ExPolygon& set_holes(Slic3r::ExPolygon& t, iT inputBegin, iT inputEnd) {
|
||||
t.holes.assign(inputBegin, inputEnd);
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
//first we register CPolygonSet as a polygon set
|
||||
template <>
|
||||
struct geometry_concept<Slic3r::ExPolygons> { typedef polygon_set_concept type; };
|
||||
|
||||
//next we map to the concept through traits
|
||||
template <>
|
||||
struct polygon_set_traits<Slic3r::ExPolygons> {
|
||||
typedef coord_t coordinate_type;
|
||||
typedef Slic3r::ExPolygons::const_iterator iterator_type;
|
||||
typedef Slic3r::ExPolygons operator_arg_type;
|
||||
|
||||
static inline iterator_type begin(const Slic3r::ExPolygons& polygon_set) {
|
||||
return polygon_set.begin();
|
||||
}
|
||||
|
||||
static inline iterator_type end(const Slic3r::ExPolygons& polygon_set) {
|
||||
return polygon_set.end();
|
||||
}
|
||||
|
||||
//don't worry about these, just return false from them
|
||||
static inline bool clean(const Slic3r::ExPolygons& /* polygon_set */) { return false; }
|
||||
static inline bool sorted(const Slic3r::ExPolygons& /* polygon_set */) { return false; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct polygon_set_mutable_traits<Slic3r::ExPolygons> {
|
||||
template <typename input_iterator_type>
|
||||
static inline void set(Slic3r::ExPolygons& expolygons, input_iterator_type input_begin, input_iterator_type input_end) {
|
||||
expolygons.assign(input_begin, input_end);
|
||||
}
|
||||
};
|
||||
} }
|
||||
// end Boost
|
||||
|
||||
#endif
|
||||
136
src/libslic3r/ExPolygonCollection.cpp
Normal file
136
src/libslic3r/ExPolygonCollection.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#include "ExPolygonCollection.hpp"
|
||||
#include "Geometry/ConvexHull.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon)
|
||||
{
|
||||
this->expolygons.push_back(expolygon);
|
||||
}
|
||||
|
||||
ExPolygonCollection::operator Points() const
|
||||
{
|
||||
Points points;
|
||||
Polygons pp = (Polygons)*this;
|
||||
for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) {
|
||||
for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point)
|
||||
points.push_back(*point);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
ExPolygonCollection::operator Polygons() const
|
||||
{
|
||||
Polygons polygons;
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
|
||||
polygons.push_back(it->contour);
|
||||
for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) {
|
||||
polygons.push_back(*ith);
|
||||
}
|
||||
}
|
||||
return polygons;
|
||||
}
|
||||
|
||||
ExPolygonCollection::operator ExPolygons&()
|
||||
{
|
||||
return this->expolygons;
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygonCollection::scale(double factor)
|
||||
{
|
||||
for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
|
||||
(*it).scale(factor);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygonCollection::translate(double x, double y)
|
||||
{
|
||||
for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
|
||||
(*it).translate(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygonCollection::rotate(double angle, const Point ¢er)
|
||||
{
|
||||
for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
|
||||
(*it).rotate(angle, center);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool ExPolygonCollection::contains(const T &item) const
|
||||
{
|
||||
for (const ExPolygon &poly : this->expolygons)
|
||||
if (poly.contains(item))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
template bool ExPolygonCollection::contains<Point>(const Point &item) const;
|
||||
template bool ExPolygonCollection::contains<Line>(const Line &item) const;
|
||||
template bool ExPolygonCollection::contains<Polyline>(const Polyline &item) const;
|
||||
|
||||
bool
|
||||
ExPolygonCollection::contains_b(const Point &point) const
|
||||
{
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
|
||||
if (it->contains_b(point)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygonCollection::simplify(double tolerance)
|
||||
{
|
||||
ExPolygons expp;
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
|
||||
it->simplify(tolerance, &expp);
|
||||
}
|
||||
this->expolygons = expp;
|
||||
}
|
||||
|
||||
Polygon
|
||||
ExPolygonCollection::convex_hull() const
|
||||
{
|
||||
Points pp;
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it)
|
||||
pp.insert(pp.end(), it->contour.points.begin(), it->contour.points.end());
|
||||
return Slic3r::Geometry::convex_hull(pp);
|
||||
}
|
||||
|
||||
Lines
|
||||
ExPolygonCollection::lines() const
|
||||
{
|
||||
Lines lines;
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
|
||||
Lines ex_lines = it->lines();
|
||||
lines.insert(lines.end(), ex_lines.begin(), ex_lines.end());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
Polygons
|
||||
ExPolygonCollection::contours() const
|
||||
{
|
||||
Polygons contours;
|
||||
contours.reserve(this->expolygons.size());
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it)
|
||||
contours.push_back(it->contour);
|
||||
return contours;
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygonCollection::append(const ExPolygons &expp)
|
||||
{
|
||||
this->expolygons.insert(this->expolygons.end(), expp.begin(), expp.end());
|
||||
}
|
||||
|
||||
BoundingBox get_extents(const ExPolygonCollection &expolygon)
|
||||
{
|
||||
return get_extents(expolygon.expolygons);
|
||||
}
|
||||
|
||||
}
|
||||
41
src/libslic3r/ExPolygonCollection.hpp
Normal file
41
src/libslic3r/ExPolygonCollection.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#ifndef slic3r_ExPolygonCollection_hpp_
|
||||
#define slic3r_ExPolygonCollection_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Polyline.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygonCollection;
|
||||
typedef std::vector<ExPolygonCollection> ExPolygonCollections;
|
||||
|
||||
class ExPolygonCollection
|
||||
{
|
||||
public:
|
||||
ExPolygons expolygons;
|
||||
|
||||
ExPolygonCollection() {}
|
||||
explicit ExPolygonCollection(const ExPolygon &expolygon);
|
||||
explicit ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {}
|
||||
explicit operator Points() const;
|
||||
explicit operator Polygons() const;
|
||||
explicit operator ExPolygons&();
|
||||
void scale(double factor);
|
||||
void translate(double x, double y);
|
||||
void rotate(double angle, const Point ¢er);
|
||||
template <class T> bool contains(const T &item) const;
|
||||
bool contains_b(const Point &point) const;
|
||||
void simplify(double tolerance);
|
||||
Polygon convex_hull() const;
|
||||
Lines lines() const;
|
||||
Polygons contours() const;
|
||||
void append(const ExPolygons &expolygons);
|
||||
};
|
||||
|
||||
extern BoundingBox get_extents(const ExPolygonCollection &expolygon);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
53
src/libslic3r/Exception.hpp
Normal file
53
src/libslic3r/Exception.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef _libslic3r_Exception_h_
|
||||
#define _libslic3r_Exception_h_
|
||||
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// PrusaSlicer's own exception hierarchy is derived from std::runtime_error.
|
||||
// Base for Slicer's own exceptions.
|
||||
class Exception : public std::runtime_error { using std::runtime_error::runtime_error; };
|
||||
#define SLIC3R_DERIVE_EXCEPTION(DERIVED_EXCEPTION, PARENT_EXCEPTION) \
|
||||
class DERIVED_EXCEPTION : public PARENT_EXCEPTION { using PARENT_EXCEPTION::PARENT_EXCEPTION; }
|
||||
// Critical exception produced by Slicer, such exception shall never propagate up to the UI thread.
|
||||
// If that happens, an ugly fat message box with an ugly fat exclamation mark is displayed.
|
||||
SLIC3R_DERIVE_EXCEPTION(CriticalException, Exception);
|
||||
SLIC3R_DERIVE_EXCEPTION(RuntimeError, CriticalException);
|
||||
SLIC3R_DERIVE_EXCEPTION(LogicError, CriticalException);
|
||||
SLIC3R_DERIVE_EXCEPTION(HardCrash, CriticalException);
|
||||
SLIC3R_DERIVE_EXCEPTION(InvalidArgument, LogicError);
|
||||
SLIC3R_DERIVE_EXCEPTION(OutOfRange, LogicError);
|
||||
SLIC3R_DERIVE_EXCEPTION(IOError, CriticalException);
|
||||
SLIC3R_DERIVE_EXCEPTION(FileIOError, IOError);
|
||||
SLIC3R_DERIVE_EXCEPTION(HostNetworkError, IOError);
|
||||
SLIC3R_DERIVE_EXCEPTION(ExportError, CriticalException);
|
||||
SLIC3R_DERIVE_EXCEPTION(PlaceholderParserError, RuntimeError);
|
||||
// Runtime exception produced by Slicer. Such exception cancels the slicing process and it shall be shown in notifications.
|
||||
//SLIC3R_DERIVE_EXCEPTION(SlicingError, Exception);
|
||||
class SlicingError : public Exception
|
||||
{
|
||||
public:
|
||||
using Exception::Exception;
|
||||
SlicingError(std::string const &msg, size_t objectId) : Exception(msg), objectId_(objectId) {}
|
||||
size_t objectId() const { return objectId_; }
|
||||
|
||||
private:
|
||||
size_t objectId_ = 0;
|
||||
};
|
||||
|
||||
class SlicingErrors : public Exception
|
||||
{
|
||||
public:
|
||||
using Exception::Exception;
|
||||
SlicingErrors(const std::vector<SlicingError> &errors) : Exception("Errors"), errors_(errors) {}
|
||||
|
||||
std::vector<SlicingError> errors_;
|
||||
};
|
||||
|
||||
#undef SLIC3R_DERIVE_EXCEPTION
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // _libslic3r_Exception_h_
|
||||
128
src/libslic3r/Execution/Execution.hpp
Normal file
128
src/libslic3r/Execution/Execution.hpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#ifndef EXECUTION_HPP
|
||||
#define EXECUTION_HPP
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Override for valid execution policies
|
||||
template<class EP> struct IsExecutionPolicy_ : public std::false_type {};
|
||||
|
||||
template<class EP> constexpr bool IsExecutionPolicy =
|
||||
IsExecutionPolicy_<remove_cvref_t<EP>>::value;
|
||||
|
||||
template<class EP, class T = void>
|
||||
using ExecutionPolicyOnly = std::enable_if_t<IsExecutionPolicy<EP>, T>;
|
||||
|
||||
namespace execution {
|
||||
|
||||
// This struct needs to be specialized for each execution policy.
|
||||
// See ExecutionSeq.hpp and ExecutionTBB.hpp for example.
|
||||
template<class EP, class En = void> struct Traits {};
|
||||
|
||||
template<class EP> using AsTraits = Traits<remove_cvref_t<EP>>;
|
||||
|
||||
// Each execution policy should declare two types of mutexes. A a spin lock and
|
||||
// a blocking mutex. These types should satisfy the BasicLockable concept.
|
||||
template<class EP> using SpinningMutex = typename Traits<EP>::SpinningMutex;
|
||||
template<class EP> using BlockingMutex = typename Traits<EP>::BlockingMutex;
|
||||
|
||||
// Query the available threads for concurrency.
|
||||
template<class EP, class = ExecutionPolicyOnly<EP> >
|
||||
size_t max_concurrency(const EP &ep)
|
||||
{
|
||||
return AsTraits<EP>::max_concurrency(ep);
|
||||
}
|
||||
|
||||
// foreach loop with the execution policy passed as argument. Granularity can
|
||||
// be specified explicitly. max_concurrency() can be used for optimal results.
|
||||
template<class EP, class It, class Fn, class = ExecutionPolicyOnly<EP>>
|
||||
void for_each(const EP &ep, It from, It to, Fn &&fn, size_t granularity = 1)
|
||||
{
|
||||
AsTraits<EP>::for_each(ep, from, to, std::forward<Fn>(fn), granularity);
|
||||
}
|
||||
|
||||
// A reduce operation with the execution policy passed as argument.
|
||||
// mergefn has T(const T&, const T&) signature
|
||||
// accessfn has T(I) signature if I is an integral type and
|
||||
// T(const I::value_type &) if I is an iterator type.
|
||||
template<class EP,
|
||||
class I,
|
||||
class MergeFn,
|
||||
class T,
|
||||
class AccessFn,
|
||||
class = ExecutionPolicyOnly<EP> >
|
||||
T reduce(const EP & ep,
|
||||
I from,
|
||||
I to,
|
||||
const T & init,
|
||||
MergeFn && mergefn,
|
||||
AccessFn &&accessfn,
|
||||
size_t granularity = 1)
|
||||
{
|
||||
return AsTraits<EP>::reduce(ep, from, to, init,
|
||||
std::forward<MergeFn>(mergefn),
|
||||
std::forward<AccessFn>(accessfn),
|
||||
granularity);
|
||||
}
|
||||
|
||||
// An overload of reduce method to be used with iterators as 'from' and 'to'
|
||||
// arguments. Access functor is omitted here.
|
||||
template<class EP,
|
||||
class I,
|
||||
class MergeFn,
|
||||
class T,
|
||||
class = ExecutionPolicyOnly<EP> >
|
||||
T reduce(const EP &ep,
|
||||
I from,
|
||||
I to,
|
||||
const T & init,
|
||||
MergeFn &&mergefn,
|
||||
size_t granularity = 1)
|
||||
{
|
||||
return reduce(
|
||||
ep, from, to, init, std::forward<MergeFn>(mergefn),
|
||||
[](const auto &i) { return i; }, granularity);
|
||||
}
|
||||
|
||||
template<class EP,
|
||||
class I,
|
||||
class T,
|
||||
class AccessFn,
|
||||
class = ExecutionPolicyOnly<EP>>
|
||||
T accumulate(const EP & ep,
|
||||
I from,
|
||||
I to,
|
||||
const T & init,
|
||||
AccessFn &&accessfn,
|
||||
size_t granularity = 1)
|
||||
{
|
||||
return reduce(ep, from, to, init, std::plus<T>{},
|
||||
std::forward<AccessFn>(accessfn), granularity);
|
||||
}
|
||||
|
||||
|
||||
template<class EP,
|
||||
class I,
|
||||
class T,
|
||||
class = ExecutionPolicyOnly<EP> >
|
||||
T accumulate(const EP &ep,
|
||||
I from,
|
||||
I to,
|
||||
const T & init,
|
||||
size_t granularity = 1)
|
||||
{
|
||||
return reduce(
|
||||
ep, from, to, init, std::plus<T>{}, [](const auto &i) { return i; },
|
||||
granularity);
|
||||
}
|
||||
|
||||
} // namespace execution_policy
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // EXECUTION_HPP
|
||||
84
src/libslic3r/Execution/ExecutionSeq.hpp
Normal file
84
src/libslic3r/Execution/ExecutionSeq.hpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#ifndef EXECUTIONSEQ_HPP
|
||||
#define EXECUTIONSEQ_HPP
|
||||
|
||||
#ifdef PRUSASLICER_USE_EXECUTION_STD // Conflicts with our version of TBB
|
||||
#include <execution>
|
||||
#endif
|
||||
|
||||
#include "Execution.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Execution policy implementing dummy sequential algorithms
|
||||
struct ExecutionSeq {};
|
||||
|
||||
template<> struct IsExecutionPolicy_<ExecutionSeq> : public std::true_type {};
|
||||
|
||||
static constexpr ExecutionSeq ex_seq = {};
|
||||
|
||||
template<class EP> struct IsSequentialEP_ { static constexpr bool value = false; };
|
||||
|
||||
template<> struct IsSequentialEP_<ExecutionSeq>: public std::true_type {};
|
||||
#ifdef PRUSASLICER_USE_EXECUTION_STD
|
||||
template<> struct IsExecutionPolicy_<std::execution::sequenced_policy>: public std::true_type {};
|
||||
template<> struct IsSequentialEP_<std::execution::sequenced_policy>: public std::true_type {};
|
||||
#endif
|
||||
|
||||
template<class EP>
|
||||
constexpr bool IsSequentialEP = IsSequentialEP_<remove_cvref_t<EP>>::value;
|
||||
|
||||
template<class EP, class R = EP>
|
||||
using SequentialEPOnly = std::enable_if_t<IsSequentialEP<EP>, R>;
|
||||
|
||||
template<class EP>
|
||||
struct execution::Traits<EP, SequentialEPOnly<EP, void>> {
|
||||
private:
|
||||
struct _Mtx { inline void lock() {} inline void unlock() {} };
|
||||
|
||||
template<class Fn, class It>
|
||||
static IteratorOnly<It, void> loop_(It from, It to, Fn &&fn)
|
||||
{
|
||||
for (auto it = from; it != to; ++it) fn(*it);
|
||||
}
|
||||
|
||||
template<class Fn, class I>
|
||||
static IntegerOnly<I, void> loop_(I from, I to, Fn &&fn)
|
||||
{
|
||||
for (I i = from; i < to; ++i) fn(i);
|
||||
}
|
||||
|
||||
public:
|
||||
using SpinningMutex = _Mtx;
|
||||
using BlockingMutex = _Mtx;
|
||||
|
||||
template<class It, class Fn>
|
||||
static void for_each(const EP &,
|
||||
It from,
|
||||
It to,
|
||||
Fn &&fn,
|
||||
size_t /* ignore granularity */ = 1)
|
||||
{
|
||||
loop_(from, to, std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
template<class I, class MergeFn, class T, class AccessFn>
|
||||
static T reduce(const EP &,
|
||||
I from,
|
||||
I to,
|
||||
const T & init,
|
||||
MergeFn &&mergefn,
|
||||
AccessFn &&access,
|
||||
size_t /*granularity*/ = 1
|
||||
)
|
||||
{
|
||||
T acc = init;
|
||||
loop_(from, to, [&](auto &i) { acc = mergefn(acc, access(i)); });
|
||||
return acc;
|
||||
}
|
||||
|
||||
static size_t max_concurrency(const EP &) { return 1; }
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // EXECUTIONSEQ_HPP
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user