This commit is contained in:
QIDI TECH
2024-09-03 09:34:33 +08:00
parent 27f34aa3e8
commit 585146181b
5147 changed files with 1734881 additions and 0 deletions

322
src/libslic3r/AABBMesh.cpp Normal file
View 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
View 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

View 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 RayBox 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_ */

View 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 &centroid() 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
View 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
View 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

File diff suppressed because it is too large Load Diff

297
src/libslic3r/AppConfig.hpp Normal file
View 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 &section, 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 &section, 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 &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_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 &section, 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 &section, 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 &section) const
{ return m_storage.find(section) != m_storage.end(); }
const std::map<std::string, std::string>& get_section(const std::string &section) const
{ return m_storage.find(section)->second; }
void set_section(const std::string &section, const std::map<std::string, std::string>& data)
{ m_storage[section] = data; }
void clear_section(const std::string &section)
{ 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_ */

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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);
}
}

View 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

View 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

View 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 &params)
: 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

View 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 &params);
/*!
* 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

View 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) {}
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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;
}

View 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

View 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
View 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;
}
}
}
}

View 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

File diff suppressed because it is too large Load Diff

217
src/libslic3r/Arrange.hpp Normal file
View 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 &params = {});
// 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 &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams &params);
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams &params);
inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams &params = {}) { arrange(items, {}, bed, params); }
}} // namespace Slic3r::arrangement
#endif // MODELARRANGE_HPP

View 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

View 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_

View 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 &center) 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);
}
}

View 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 &center) const;
void rotate(double angle) { (*this) = this->rotated(angle); }
void rotate(double angle, const Point &center) { (*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

View 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;
}
}

View 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

File diff suppressed because it is too large Load Diff

30
src/libslic3r/Brim.hpp Normal file
View 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_

View 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

View 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_

View 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 ()

View 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

View 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

View 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

View 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

View 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 &params,
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

View 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 &part;
}
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

View 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 &params = {})
{
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
View 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 &params, 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
View 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 &params, 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
View 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
View 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
View 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

View 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); }
}

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

2371
src/libslic3r/Config.hpp Normal file

File diff suppressed because it is too large Load Diff

View 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]);
}
}
}
}

View 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

View 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

View 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
View 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 dont 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

View 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

File diff suppressed because it is too large Load Diff

418
src/libslic3r/EdgeGrid.hpp Normal file
View 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_ */

View 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

View 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
View 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 &center)
{
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
View 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 &center);
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

View 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 &center)
{
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);
}
}

View 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 &center);
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

View 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_

View 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

View 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