mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-11-02 20:51:23 -07:00
Add the full source of BambuStudio
using version 1.0.10
This commit is contained in:
parent
30bcadab3e
commit
1555904bef
3771 changed files with 1251328 additions and 0 deletions
889
src/libslic3r/AABBTreeIndirect.hpp
Normal file
889
src/libslic3r/AABBTreeIndirect.hpp
Normal file
|
|
@ -0,0 +1,889 @@
|
|||
// 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 "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)
|
||||
{
|
||||
if (input.empty())
|
||||
clear();
|
||||
else {
|
||||
// Allocate enough memory for a full binary tree.
|
||||
m_nodes.assign(next_highest_power_of_2(input.size()) * 2 - 1, Node());
|
||||
build_recursive(input, 0, 0, input.size() - 1);
|
||||
}
|
||||
input.clear();
|
||||
}
|
||||
|
||||
const std::vector<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;
|
||||
{
|
||||
// Bubble sort the input[left], input[center], input[right], so that a median of the three values
|
||||
// will end up in input[center].
|
||||
CoordType left_value = input[left ].centroid()(dimension);
|
||||
CoordType center_value = input[center].centroid()(dimension);
|
||||
CoordType right_value = input[right ].centroid()(dimension);
|
||||
if (left_value > center_value) {
|
||||
std::swap(input[left], input[center]);
|
||||
std::swap(left_value, center_value);
|
||||
}
|
||||
if (left_value > right_value) {
|
||||
std::swap(input[left], input[right]);
|
||||
right_value = left_value;
|
||||
}
|
||||
if (center_value > right_value) {
|
||||
std::swap(input[center], input[right]);
|
||||
center_value = right_value;
|
||||
}
|
||||
pivot = center_value;
|
||||
}
|
||||
if (right <= left + 2)
|
||||
// The <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>;
|
||||
|
||||
namespace detail {
|
||||
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
|
||||
struct RayIntersector {
|
||||
using VertexType = AVertexType;
|
||||
using IndexedFaceType = AIndexedFaceType;
|
||||
using TreeType = ATreeType;
|
||||
using VectorType = AVectorType;
|
||||
|
||||
const std::vector<VertexType> &vertices;
|
||||
const std::vector<IndexedFaceType> &faces;
|
||||
const TreeType &tree;
|
||||
|
||||
const VectorType origin;
|
||||
const VectorType dir;
|
||||
const VectorType invdir;
|
||||
|
||||
// epsilon for ray-triangle intersection, see intersect_triangle1()
|
||||
const double eps;
|
||||
};
|
||||
|
||||
template<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
|
||||
struct RayIntersectorHits : RayIntersector<VertexType, IndexedFaceType, TreeType, VectorType> {
|
||||
std::vector<igl::Hit> hits;
|
||||
};
|
||||
|
||||
//FIXME implement SSE for float AABB trees with float ray queries.
|
||||
// SSE/SSE2 is supported by any Intel/AMD x64 processor.
|
||||
// SSE support requires 16 byte alignment of the AABB nodes, representing the bounding boxes with 4+4 floats,
|
||||
// storing the node index as the 4th element of the bounding box min value etc.
|
||||
// https://www.flipcode.com/archives/SSE_RayBox_Intersection_Test.shtml
|
||||
template <typename Derivedsource, typename Deriveddir, typename Scalar>
|
||||
inline bool ray_box_intersect_invdir(
|
||||
const Eigen::MatrixBase<Derivedsource> &origin,
|
||||
const Eigen::MatrixBase<Deriveddir> &inv_dir,
|
||||
Eigen::AlignedBox<Scalar,3> box,
|
||||
const Scalar &t0,
|
||||
const Scalar &t1) {
|
||||
// http://people.csail.mit.edu/amy/papers/box-jgt.pdf
|
||||
// "An Efficient and Robust Ray–Box Intersection Algorithm"
|
||||
if (inv_dir.x() < 0)
|
||||
std::swap(box.min().x(), box.max().x());
|
||||
if (inv_dir.y() < 0)
|
||||
std::swap(box.min().y(), box.max().y());
|
||||
Scalar tmin = (box.min().x() - origin.x()) * inv_dir.x();
|
||||
Scalar tymax = (box.max().y() - origin.y()) * inv_dir.y();
|
||||
if (tmin > tymax)
|
||||
return false;
|
||||
Scalar tmax = (box.max().x() - origin.x()) * inv_dir.x();
|
||||
Scalar tymin = (box.min().y() - origin.y()) * inv_dir.y();
|
||||
if (tymin > tmax)
|
||||
return false;
|
||||
if (tymin > tmin)
|
||||
tmin = tymin;
|
||||
if (tymax < tmax)
|
||||
tmax = tymax;
|
||||
if (inv_dir.z() < 0)
|
||||
std::swap(box.min().z(), box.max().z());
|
||||
Scalar tzmin = (box.min().z() - origin.z()) * inv_dir.z();
|
||||
if (tzmin > tmax)
|
||||
return false;
|
||||
Scalar tzmax = (box.max().z() - origin.z()) * inv_dir.z();
|
||||
if (tmin > tzmax)
|
||||
return false;
|
||||
if (tzmin > tmin)
|
||||
tmin = tzmin;
|
||||
if (tzmax < tmax)
|
||||
tmax = tzmax;
|
||||
return tmin < t1 && tmax > t0;
|
||||
}
|
||||
|
||||
// The following intersect_triangle() is derived from raytri.c routine intersect_triangle1()
|
||||
// Ray-Triangle Intersection Test Routines
|
||||
// Different optimizations of my and Ben Trumbore's
|
||||
// code from journals of graphics tools (JGT)
|
||||
// http://www.acm.org/jgt/
|
||||
// by Tomas Moller, May 2000
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<std::is_same<typename V::Scalar, double>::value&& std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &orig, const V &dir, const W &vert0, const W &vert1, const W &vert2, double &t, double &u, double &v, double eps)
|
||||
{
|
||||
// find vectors for two edges sharing vert0
|
||||
const V edge1 = vert1 - vert0;
|
||||
const V edge2 = vert2 - vert0;
|
||||
// begin calculating determinant - also used to calculate U parameter
|
||||
const V pvec = dir.cross(edge2);
|
||||
// if determinant is near zero, ray lies in plane of triangle
|
||||
const double det = edge1.dot(pvec);
|
||||
V qvec;
|
||||
|
||||
if (det > eps) {
|
||||
// calculate distance from vert0 to ray origin
|
||||
V tvec = orig - vert0;
|
||||
// calculate U parameter and test bounds
|
||||
u = tvec.dot(pvec);
|
||||
if (u < 0.0 || u > det)
|
||||
return false;
|
||||
// prepare to test V parameter
|
||||
qvec = tvec.cross(edge1);
|
||||
// calculate V parameter and test bounds
|
||||
v = dir.dot(qvec);
|
||||
if (v < 0.0 || u + v > det)
|
||||
return false;
|
||||
} else if (det < -eps) {
|
||||
// calculate distance from vert0 to ray origin
|
||||
V tvec = orig - vert0;
|
||||
// calculate U parameter and test bounds
|
||||
u = tvec.dot(pvec);
|
||||
if (u > 0.0 || u < det)
|
||||
return false;
|
||||
// prepare to test V parameter
|
||||
qvec = tvec.cross(edge1);
|
||||
// calculate V parameter and test bounds
|
||||
v = dir.dot(qvec);
|
||||
if (v > 0.0 || u + v < det)
|
||||
return false;
|
||||
} else
|
||||
// ray is parallel to the plane of the triangle
|
||||
return false;
|
||||
|
||||
double inv_det = 1.0 / det;
|
||||
// calculate t, ray intersects triangle
|
||||
t = edge2.dot(qvec) * inv_det;
|
||||
u *= inv_det;
|
||||
v *= inv_det;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<std::is_same<typename V::Scalar, double>::value && !std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) {
|
||||
return intersect_triangle(origin, dir, v0.template cast<double>(), v1.template cast<double>(), v2.template cast<double>(), t, u, v, eps);
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) {
|
||||
return intersect_triangle(origin.template cast<double>(), dir.template cast<double>(), v0, v1, v2, t, u, v, eps);
|
||||
}
|
||||
|
||||
template<typename V, typename W>
|
||||
std::enable_if_t<! std::is_same<typename V::Scalar, double>::value && ! std::is_same<typename W::Scalar, double>::value, bool>
|
||||
intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v, double eps) {
|
||||
return intersect_triangle(origin.template cast<double>(), dir.template cast<double>(), v0.template cast<double>(), v1.template cast<double>(), v2.template cast<double>(), t, u, v, eps);
|
||||
}
|
||||
|
||||
template<typename Tree>
|
||||
double intersect_triangle_epsilon(const Tree &tree) {
|
||||
double eps = 0.000001;
|
||||
if (! tree.empty()) {
|
||||
const typename Tree::BoundingBox &bbox = tree.nodes().front().bbox;
|
||||
double l = (bbox.max() - bbox.min()).cwiseMax();
|
||||
if (l > 0)
|
||||
eps /= (l * l);
|
||||
}
|
||||
return eps;
|
||||
}
|
||||
|
||||
template<typename RayIntersectorType, typename Scalar>
|
||||
static inline bool intersect_ray_recursive_first_hit(
|
||||
RayIntersectorType &ray_intersector,
|
||||
size_t node_idx,
|
||||
Scalar min_t,
|
||||
igl::Hit &hit)
|
||||
{
|
||||
const auto &node = ray_intersector.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
|
||||
if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast<Scalar>(), Scalar(0), min_t))
|
||||
return false;
|
||||
|
||||
if (node.is_leaf()) {
|
||||
// shoot ray, record hit
|
||||
auto face = ray_intersector.faces[node.idx];
|
||||
double t, u, v;
|
||||
if (intersect_triangle(
|
||||
ray_intersector.origin, ray_intersector.dir,
|
||||
ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)],
|
||||
t, u, v, ray_intersector.eps)
|
||||
&& t > 0.) {
|
||||
hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) };
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
} else {
|
||||
// Left / right child node index.
|
||||
size_t left = node_idx * 2 + 1;
|
||||
size_t right = left + 1;
|
||||
igl::Hit left_hit;
|
||||
igl::Hit right_hit;
|
||||
bool left_ret = intersect_ray_recursive_first_hit(ray_intersector, left, min_t, left_hit);
|
||||
if (left_ret && left_hit.t < min_t) {
|
||||
min_t = left_hit.t;
|
||||
hit = left_hit;
|
||||
} else
|
||||
left_ret = false;
|
||||
bool right_ret = intersect_ray_recursive_first_hit(ray_intersector, right, min_t, right_hit);
|
||||
if (right_ret && right_hit.t < min_t)
|
||||
hit = right_hit;
|
||||
else
|
||||
right_ret = false;
|
||||
return left_ret || right_ret;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename RayIntersectorType>
|
||||
static inline void intersect_ray_recursive_all_hits(RayIntersectorType &ray_intersector, size_t node_idx)
|
||||
{
|
||||
using Scalar = typename RayIntersectorType::VectorType::Scalar;
|
||||
|
||||
const auto &node = ray_intersector.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
|
||||
if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast<Scalar>(),
|
||||
Scalar(0), std::numeric_limits<Scalar>::infinity()))
|
||||
return;
|
||||
|
||||
if (node.is_leaf()) {
|
||||
auto face = ray_intersector.faces[node.idx];
|
||||
double t, u, v;
|
||||
if (intersect_triangle(
|
||||
ray_intersector.origin, ray_intersector.dir,
|
||||
ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)],
|
||||
t, u, v, ray_intersector.eps)
|
||||
&& t > 0.) {
|
||||
ray_intersector.hits.emplace_back(igl::Hit{ int(node.idx), -1, float(u), float(v), float(t) });
|
||||
}
|
||||
} else {
|
||||
// Left / right child node index.
|
||||
size_t left = node_idx * 2 + 1;
|
||||
size_t right = left + 1;
|
||||
intersect_ray_recursive_all_hits(ray_intersector, left);
|
||||
intersect_ray_recursive_all_hits(ray_intersector, right);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
const std::vector<VertexType> &vertices;
|
||||
const std::vector<IndexedFaceType> &faces;
|
||||
const TreeType &tree;
|
||||
|
||||
const VectorType origin;
|
||||
};
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
template<typename IndexedTriangleSetDistancerType, typename Scalar>
|
||||
static inline Scalar squared_distance_to_indexed_triangle_set_recursive(
|
||||
IndexedTriangleSetDistancerType &distancer,
|
||||
size_t node_idx,
|
||||
Scalar low_sqr_d,
|
||||
Scalar up_sqr_d,
|
||||
size_t &i,
|
||||
Eigen::PlainObjectBase<typename IndexedTriangleSetDistancerType::VectorType> &c)
|
||||
{
|
||||
using Vector = typename IndexedTriangleSetDistancerType::VectorType;
|
||||
|
||||
if (low_sqr_d > up_sqr_d)
|
||||
return low_sqr_d;
|
||||
|
||||
// Save the best achieved hit.
|
||||
auto set_min = [&i, &c, &up_sqr_d](const Scalar sqr_d_candidate, const size_t i_candidate, const Vector &c_candidate) {
|
||||
if (sqr_d_candidate < up_sqr_d) {
|
||||
i = i_candidate;
|
||||
c = c_candidate;
|
||||
up_sqr_d = sqr_d_candidate;
|
||||
}
|
||||
};
|
||||
|
||||
const auto &node = distancer.tree.node(node_idx);
|
||||
assert(node.is_valid());
|
||||
if (node.is_leaf())
|
||||
{
|
||||
const auto &triangle = distancer.faces[node.idx];
|
||||
Vector c_candidate = closest_point_to_triangle<Vector>(
|
||||
distancer.origin,
|
||||
distancer.vertices[triangle(0)].template cast<Scalar>(),
|
||||
distancer.vertices[triangle(1)].template cast<Scalar>(),
|
||||
distancer.vertices[triangle(2)].template cast<Scalar>());
|
||||
set_min((c_candidate - distancer.origin).squaredNorm(), node.idx, c_candidate);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t left_node_idx = node_idx * 2 + 1;
|
||||
size_t right_node_idx = left_node_idx + 1;
|
||||
const auto &node_left = distancer.tree.node(left_node_idx);
|
||||
const auto &node_right = distancer.tree.node(right_node_idx);
|
||||
assert(node_left.is_valid());
|
||||
assert(node_right.is_valid());
|
||||
|
||||
bool looked_left = false;
|
||||
bool looked_right = false;
|
||||
const auto &look_left = [&]()
|
||||
{
|
||||
size_t i_left;
|
||||
Vector c_left = c;
|
||||
Scalar sqr_d_left = squared_distance_to_indexed_triangle_set_recursive(distancer, left_node_idx, low_sqr_d, up_sqr_d, i_left, c_left);
|
||||
set_min(sqr_d_left, i_left, c_left);
|
||||
looked_left = true;
|
||||
};
|
||||
const auto &look_right = [&]()
|
||||
{
|
||||
size_t i_right;
|
||||
Vector c_right = c;
|
||||
Scalar sqr_d_right = squared_distance_to_indexed_triangle_set_recursive(distancer, right_node_idx, low_sqr_d, up_sqr_d, i_right, c_right);
|
||||
set_min(sqr_d_right, i_right, c_right);
|
||||
looked_right = true;
|
||||
};
|
||||
|
||||
// must look left or right if in box
|
||||
using BBoxScalar = typename IndexedTriangleSetDistancerType::TreeType::BoundingBox::Scalar;
|
||||
if (node_left.bbox.contains(distancer.origin.template cast<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;
|
||||
}
|
||||
|
||||
} // 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()) {
|
||||
ray_intersector.hits.reserve(8);
|
||||
detail::intersect_ray_recursive_all_hits(ray_intersector, 0);
|
||||
std::swap(hits, ray_intersector.hits);
|
||||
std::sort(hits.begin(), hits.end(), [](const auto &l, const auto &r) { return l.t < r.t; });
|
||||
}
|
||||
return ! hits.empty();
|
||||
}
|
||||
|
||||
// Finding a closest triangle, its closest point and squared distance to the closest point
|
||||
// on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree.
|
||||
// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar
|
||||
// even if the triangle mesh and the AABB Tree are built with floats.
|
||||
// Returns squared distance to the closest point or -1 if the input is empty.
|
||||
template<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_triangle_set_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,
|
||||
// Maximum distance in which triangle is search for
|
||||
typename VectorType::Scalar &max_distance)
|
||||
{
|
||||
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() * (std::nan(""));
|
||||
|
||||
if(tree.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), max_distance, hit_idx, hit_point);
|
||||
|
||||
return hit_point.allFinite();
|
||||
}
|
||||
|
||||
|
||||
// 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 Containing {};
|
||||
|
||||
// Intersection predicate specialization for box-box intersections
|
||||
template<class CoordType, int NumD>
|
||||
struct Containing<Eigen::AlignedBox<CoordType, NumD>> {
|
||||
Eigen::AlignedBox<CoordType, NumD> box;
|
||||
|
||||
Containing(const Eigen::AlignedBox<CoordType, NumD> &bb): box{bb} {}
|
||||
|
||||
bool operator() (const typename Tree<NumD, CoordType>::Node &node) const
|
||||
{
|
||||
return box.contains(node.bbox);
|
||||
}
|
||||
};
|
||||
|
||||
template<class G> auto containing(const G &g) { return Containing<G>{g}; }
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<int Dims, typename T, typename Pred, typename Fn>
|
||||
void 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))) return;
|
||||
|
||||
if (tree.node(idx).is_leaf()) {
|
||||
callback(tree.node(idx).idx);
|
||||
} else {
|
||||
|
||||
// call this with left and right node idx:
|
||||
auto trv = [&](size_t idx) {
|
||||
traverse_recurse(tree, idx, std::forward<Pred>(pred),
|
||||
std::forward<Fn>(callback));
|
||||
};
|
||||
|
||||
// Left / right child node index.
|
||||
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) {
|
||||
// /* ... */
|
||||
// });
|
||||
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_ */
|
||||
1079
src/libslic3r/AppConfig.cpp
Normal file
1079
src/libslic3r/AppConfig.cpp
Normal file
File diff suppressed because it is too large
Load diff
263
src/libslic3r/AppConfig.hpp
Normal file
263
src/libslic3r/AppConfig.hpp
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
#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"
|
||||
|
||||
using namespace nlohmann;
|
||||
|
||||
#define ENV_DEV_HOST "0"
|
||||
#define ENV_QAT_HOST "1"
|
||||
#define ENV_PRE_HOST "2"
|
||||
#define ENV_PRODUCT_HOST "3"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppConfig
|
||||
{
|
||||
public:
|
||||
enum class EAppMode : unsigned char
|
||||
{
|
||||
Editor,
|
||||
GCodeViewer
|
||||
};
|
||||
|
||||
//BBS: 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();
|
||||
}
|
||||
|
||||
// Clear and reset to defaults.
|
||||
void reset();
|
||||
// Override missing or keys with their defaults.
|
||||
void set_defaults();
|
||||
|
||||
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
|
||||
// return error string or empty strinf
|
||||
std::string load();
|
||||
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
|
||||
void save();
|
||||
|
||||
// Does this config need to be saved?
|
||||
bool dirty() const { return m_dirty; }
|
||||
|
||||
|
||||
void set_dirty() { m_dirty = true; }
|
||||
|
||||
// Const accessor, it will return false if a section or a key does not exist.
|
||||
bool get(const std::string §ion, const std::string &key, std::string &value) const
|
||||
{
|
||||
value.clear();
|
||||
auto it = m_storage.find(section);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
auto it2 = it->second.find(key);
|
||||
if (it2 == it->second.end())
|
||||
return false;
|
||||
value = it2->second;
|
||||
return true;
|
||||
}
|
||||
std::string get(const std::string §ion, const std::string &key) const
|
||||
{ std::string value; this->get(section, key, value); return value; }
|
||||
std::string get(const std::string &key) const
|
||||
{ std::string value; this->get("app", key, value); return value; }
|
||||
void set(const std::string §ion, const std::string &key, const std::string &value)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
std::string key_trimmed = key;
|
||||
boost::trim_all(key_trimmed);
|
||||
assert(key_trimmed == key);
|
||||
assert(! key_trimmed.empty());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
std::string &old = m_storage[section][key];
|
||||
if (old != value) {
|
||||
old = value;
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set_str(const std::string& section, const std::string& key, const std::string& value)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
std::string key_trimmed = key;
|
||||
boost::trim_all(key_trimmed);
|
||||
assert(key_trimmed == key);
|
||||
assert(!key_trimmed.empty());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
std::string& old = m_storage[section][key];
|
||||
if (old != value) {
|
||||
old = value;
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void set(const std::string& section, const std::string &key, bool value)
|
||||
{
|
||||
if (value){
|
||||
set(section, key, std::string("true"));
|
||||
} else {
|
||||
set(section, key, std::string("false"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void set(const std::string &key, const std::string &value)
|
||||
{ this->set("app", key, value); }
|
||||
|
||||
void set_bool(const std::string &key, const bool &value)
|
||||
{
|
||||
this->set("app", key, value);
|
||||
}
|
||||
|
||||
bool has(const std::string §ion, const std::string &key) const
|
||||
{
|
||||
auto it = m_storage.find(section);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
auto it2 = it->second.find(key);
|
||||
return it2 != it->second.end() && ! it2->second.empty();
|
||||
}
|
||||
bool has(const std::string &key) const
|
||||
{ return this->has("app", key); }
|
||||
|
||||
void erase(const std::string §ion, const std::string &key)
|
||||
{
|
||||
auto it = m_storage.find(section);
|
||||
if (it != m_storage.end()) {
|
||||
it->second.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
bool has_section(const std::string §ion) const
|
||||
{ return m_storage.find(section) != m_storage.end(); }
|
||||
const std::map<std::string, std::string>& get_section(const std::string §ion) const
|
||||
{ return m_storage.find(section)->second; }
|
||||
void set_section(const std::string §ion, const std::map<std::string, std::string>& data)
|
||||
{ m_storage[section] = data; }
|
||||
void clear_section(const std::string §ion)
|
||||
{ m_storage[section].clear(); }
|
||||
|
||||
typedef std::map<std::string, std::map<std::string, std::set<std::string>>> VendorMap;
|
||||
bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const;
|
||||
void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable);
|
||||
void set_vendors(const AppConfig &from);
|
||||
void set_vendors(const VendorMap &vendors) { m_vendors = vendors; m_dirty = true; }
|
||||
void set_vendors(VendorMap &&vendors) { m_vendors = std::move(vendors); m_dirty = true; }
|
||||
const VendorMap& vendors() const { return m_vendors; }
|
||||
|
||||
// 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);
|
||||
|
||||
// BBS: 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();
|
||||
|
||||
// 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;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_AppConfig_hpp_ */
|
||||
152
src/libslic3r/ArcFitter.cpp
Normal file
152
src/libslic3r/ArcFitter.cpp
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
#include "ArcFitter.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++) {
|
||||
//BBS: 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) {
|
||||
//BBS: 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) {
|
||||
//BBS: 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 {
|
||||
//BBS: 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]);
|
||||
}
|
||||
}
|
||||
//BBS: 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)
|
||||
{
|
||||
//BBS: 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() });
|
||||
|
||||
//BBS: 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) {
|
||||
//BBS: all are straight segment, directly use DP simplify
|
||||
points = MultiPoint::_douglas_peucker(points, tolerance);
|
||||
result[0].end_point_index = points.size() - 1;
|
||||
return;
|
||||
} else {
|
||||
//BBS: 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;
|
||||
//BBS: 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);
|
||||
//BBS: how many point has been reduced
|
||||
reduce_count[i] = end_index - start_index + 1 - straight_or_arc_part.size();
|
||||
//BBS: 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]);
|
||||
}
|
||||
}
|
||||
//BBS: save and will return the simplified_points
|
||||
points = simplified_points;
|
||||
//BBS: modify the index in result because the point index must be changed to match the simplified points
|
||||
for (size_t j = 1; j < reduce_count.size(); j++)
|
||||
reduce_count[j] += reduce_count[j - 1];
|
||||
for (size_t j = 0; j < result.size(); j++)
|
||||
{
|
||||
result[j].end_point_index -= reduce_count[j];
|
||||
if (j != result.size() - 1)
|
||||
result[j + 1].start_point_index = result[j].end_point_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
53
src/libslic3r/ArcFitter.hpp
Normal file
53
src/libslic3r/ArcFitter.hpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#ifndef slic3r_ArcFitter_hpp_
|
||||
#define slic3r_ArcFitter_hpp_
|
||||
|
||||
#include "Circle.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//BBS: 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
|
||||
};
|
||||
|
||||
//BBS
|
||||
struct PathFittingData{
|
||||
size_t start_point_index;
|
||||
size_t end_point_index;
|
||||
EMovePathType path_type;
|
||||
// BBS: 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:
|
||||
//BBS: 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);
|
||||
//BBS: 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
|
||||
875
src/libslic3r/Arrange.cpp
Normal file
875
src/libslic3r/Arrange.cpp
Normal file
|
|
@ -0,0 +1,875 @@
|
|||
#include "Arrange.hpp"
|
||||
|
||||
#include "BoundingBox.hpp"
|
||||
|
||||
#include <libnest2d/backends/libslic3r/geometries.hpp>
|
||||
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
||||
#include <libnest2d/placers/nfpplacer.hpp>
|
||||
#include <libnest2d/selections/firstfit.hpp>
|
||||
#include <libnest2d/utils/rotcalipers.hpp>
|
||||
|
||||
#include <numeric>
|
||||
#include <ClipperUtils.hpp>
|
||||
|
||||
#include <boost/geometry/index/rtree.hpp>
|
||||
|
||||
#if defined(_MSC_VER) && defined(__clang__)
|
||||
#define BOOST_NO_CXX17_HDR_STRING_VIEW
|
||||
#endif
|
||||
|
||||
#include <boost/multiprecision/integer.hpp>
|
||||
#include <boost/rational.hpp>
|
||||
|
||||
namespace libnest2d {
|
||||
#if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__)
|
||||
using LargeInt = __int128;
|
||||
#else
|
||||
using LargeInt = boost::multiprecision::int128_t;
|
||||
template<> struct _NumTag<LargeInt>
|
||||
{
|
||||
using Type = ScalarTag;
|
||||
};
|
||||
#endif
|
||||
|
||||
template<class T> struct _NumTag<boost::rational<T>>
|
||||
{
|
||||
using Type = RationalTag;
|
||||
};
|
||||
|
||||
namespace nfp {
|
||||
|
||||
template<class S> struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
|
||||
{
|
||||
NfpResult<S> operator()(const S &sh, const S &other)
|
||||
{
|
||||
return nfpConvexOnly<S, boost::rational<LargeInt>>(sh, other);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace nfp
|
||||
} // namespace libnest2d
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
template<class Tout = double, class = FloatingOnly<Tout>, int...EigenArgs>
|
||||
inline constexpr Eigen::Matrix<Tout, 2, EigenArgs...> unscaled(
|
||||
const Slic3r::ClipperLib::IntPoint &v) noexcept
|
||||
{
|
||||
return Eigen::Matrix<Tout, 2, EigenArgs...>{unscaled<Tout>(v.x()),
|
||||
unscaled<Tout>(v.y())};
|
||||
}
|
||||
|
||||
namespace arrangement {
|
||||
|
||||
using namespace libnest2d;
|
||||
|
||||
// Get the libnest2d types for clipper backend
|
||||
using Item = _Item<ExPolygon>;
|
||||
using Box = _Box<Point>;
|
||||
using Circle = _Circle<Point>;
|
||||
using Segment = _Segment<Point>;
|
||||
using MultiPolygon = ExPolygons;
|
||||
|
||||
// Summon the spatial indexing facilities from boost
|
||||
namespace bgi = boost::geometry::index;
|
||||
using SpatElement = std::pair<Box, unsigned>;
|
||||
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
|
||||
using ItemGroup = std::vector<std::reference_wrapper<Item>>;
|
||||
|
||||
// A coefficient used in separating bigger items and smaller items.
|
||||
const double BIG_ITEM_TRESHOLD = 0.02;
|
||||
|
||||
// Fill in the placer algorithm configuration with values carefully chosen for
|
||||
// Slic3r.
|
||||
template<class PConf>
|
||||
void fill_config(PConf& pcfg, const ArrangeParams ¶ms) {
|
||||
|
||||
if (params.is_seq_print) {
|
||||
// Align the arranged pile into the center of the bin
|
||||
pcfg.alignment = PConf::Alignment::CENTER;
|
||||
// Start placing the items from the center of the print bed
|
||||
pcfg.starting_point = PConf::Alignment::BOTTOM_LEFT;
|
||||
}
|
||||
else {
|
||||
// Align the arranged pile into the center of the bin
|
||||
pcfg.alignment = PConf::Alignment::CENTER;
|
||||
// Start placing the items from the center of the print bed
|
||||
pcfg.starting_point = PConf::Alignment::CENTER;
|
||||
}
|
||||
|
||||
// Try 4 angles (45 degree step) and find the one with min cost
|
||||
if (params.allow_rotations)
|
||||
pcfg.rotations = {0., PI / 4., PI/2, 3. * PI / 4. };
|
||||
else
|
||||
pcfg.rotations = {0.};
|
||||
|
||||
// The accuracy of optimization.
|
||||
// Goes from 0.0 to 1.0 and scales performance as well
|
||||
pcfg.accuracy = params.accuracy;
|
||||
|
||||
// Allow parallel execution.
|
||||
pcfg.parallel = params.parallel;
|
||||
|
||||
// BBS: excluded regions in BBS bed
|
||||
for (auto& poly : params.excluded_regions)
|
||||
process_arrangeable(poly, pcfg.m_excluded_regions);
|
||||
// BBS: nonprefered regions in BBS bed
|
||||
for (auto& poly : params.nonprefered_regions)
|
||||
process_arrangeable(poly, pcfg.m_nonprefered_regions);
|
||||
for (auto& itm : pcfg.m_excluded_regions) {
|
||||
itm.markAsFixedInBin(0);
|
||||
itm.inflate(scaled(-2. * EPSILON));
|
||||
}
|
||||
}
|
||||
|
||||
// Apply penalty to object function result. This is used only when alignment
|
||||
// after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN)
|
||||
// Also, this will only work well for Box shaped beds.
|
||||
static double fixed_overfit(const std::tuple<double, Box>& result, const Box &binbb)
|
||||
{
|
||||
double score = std::get<0>(result);
|
||||
Box pilebb = std::get<1>(result);
|
||||
Box fullbb = sl::boundingBox(pilebb, binbb);
|
||||
auto diff = double(fullbb.area()) - binbb.area();
|
||||
if(diff > 0) score += diff;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
// A class encapsulating the libnest2d Nester class and extending it with other
|
||||
// management and spatial index structures for acceleration.
|
||||
template<class TBin>
|
||||
class AutoArranger {
|
||||
public:
|
||||
// Useful type shortcuts...
|
||||
using Placer = typename placers::_NofitPolyPlacer<ExPolygon, TBin>;
|
||||
using Selector = selections::_FirstFitSelection<ExPolygon>;
|
||||
using Packer = _Nester<Placer, Selector>;
|
||||
using PConfig = typename Packer::PlacementConfig;
|
||||
using Distance = TCoord<PointImpl>;
|
||||
std::vector<Item> m_excluded_items_in_each_plate; // for V4 bed there are excluded regions at bottom left corner
|
||||
|
||||
protected:
|
||||
Packer m_pck;
|
||||
PConfig m_pconf; // Placement configuration
|
||||
TBin m_bin;
|
||||
double m_bin_area;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4244)
|
||||
#pragma warning(disable: 4267)
|
||||
#endif
|
||||
SpatIndex m_rtree; // spatial index for the normal (bigger) objects
|
||||
SpatIndex m_smallsrtree; // spatial index for only the smaller items
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
double m_norm; // A coefficient to scale distances
|
||||
MultiPolygon m_merged_pile; // The already merged pile (vector of items)
|
||||
Box m_pilebb; // The bounding box of the merged pile.
|
||||
ItemGroup m_remaining; // Remaining items
|
||||
ItemGroup m_items; // allready packed items
|
||||
size_t m_item_count = 0; // Number of all items to be packed
|
||||
ArrangeParams params;
|
||||
|
||||
template<class T> ArithmeticOnly<T, double> norm(T val)
|
||||
{
|
||||
return double(val) / m_norm;
|
||||
}
|
||||
|
||||
// dist function for sequential print (starting_point=BOTTOM_LEFT) which is composed of
|
||||
// 1) Y distance of item corner to bed corner. Must be put above bed corner. (high weight)
|
||||
// 2) X distance of item corner to bed corner (low weight)
|
||||
// 3) item row occupancy (useful when rotation is enabled)
|
||||
double dist_for_BOTTOM_LEFT(Box ibb, const ClipperLib::IntPoint& origin_pack)
|
||||
{
|
||||
double dist_corner_y = ibb.minCorner().y() - origin_pack.y();
|
||||
double dist_corner_x = ibb.minCorner().x() - origin_pack.x();
|
||||
if (dist_corner_y < 0 || dist_corner_x<0)
|
||||
return LARGE_COST_TO_REJECT;
|
||||
double bindist = norm(dist_corner_y + 0.1 * dist_corner_x
|
||||
+ 1 * double(ibb.maxCorner().y() - ibb.minCorner().y())); // occupy as few rows as possible
|
||||
return bindist;
|
||||
}
|
||||
|
||||
// This is "the" object function which is evaluated many times for each
|
||||
// vertex (decimated with the accuracy parameter) of each object.
|
||||
// Therefore it is upmost crucial for this function to be as efficient
|
||||
// as it possibly can be but at the same time, it has to provide
|
||||
// reasonable results.
|
||||
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
|
||||
objfunc(const Item &item, const ClipperLib::IntPoint &origin_pack)
|
||||
{
|
||||
const double bin_area = m_bin_area;
|
||||
const SpatIndex& spatindex = m_rtree;
|
||||
const SpatIndex& smalls_spatindex = m_smallsrtree;
|
||||
|
||||
// We will treat big items (compared to the print bed) differently
|
||||
auto isBig = [bin_area](double a) {
|
||||
return a/bin_area > BIG_ITEM_TRESHOLD ;
|
||||
};
|
||||
|
||||
// Candidate item bounding box
|
||||
auto ibb = item.boundingBox();
|
||||
|
||||
// Calculate the full bounding box of the pile with the candidate item
|
||||
auto fullbb = sl::boundingBox(m_pilebb, ibb);
|
||||
|
||||
// The bounding box of the big items (they will accumulate in the center
|
||||
// of the pile
|
||||
Box bigbb;
|
||||
if(spatindex.empty()) bigbb = fullbb;
|
||||
else {
|
||||
auto boostbb = spatindex.bounds();
|
||||
boost::geometry::convert(boostbb, bigbb);
|
||||
}
|
||||
|
||||
// Will hold the resulting score
|
||||
double score = 0;
|
||||
|
||||
// Density is the pack density: how big is the arranged pile
|
||||
double density = 0;
|
||||
|
||||
// Distinction of cases for the arrangement scene
|
||||
enum e_cases {
|
||||
// This branch is for big items in a mixed (big and small) scene
|
||||
// OR for all items in a small-only scene.
|
||||
BIG_ITEM,
|
||||
|
||||
// This branch is for the last big item in a mixed scene
|
||||
LAST_BIG_ITEM,
|
||||
|
||||
// For small items in a mixed scene.
|
||||
SMALL_ITEM
|
||||
} compute_case;
|
||||
|
||||
bool bigitems = isBig(item.area()) || spatindex.empty();
|
||||
if(!params.is_seq_print && bigitems && !m_remaining.empty()) compute_case = BIG_ITEM; // do not use so complicated logic for sequential printing
|
||||
else if (bigitems && m_remaining.empty()) compute_case = LAST_BIG_ITEM;
|
||||
else compute_case = SMALL_ITEM;
|
||||
|
||||
switch (compute_case) {
|
||||
case BIG_ITEM: {
|
||||
const Point& minc = ibb.minCorner(); // bottom left corner
|
||||
const Point& maxc = ibb.maxCorner(); // top right corner
|
||||
|
||||
// top left and bottom right corners
|
||||
Point top_left{getX(minc), getY(maxc)};
|
||||
Point bottom_right{getX(maxc), getY(minc)};
|
||||
|
||||
// Now the distance of the gravity center will be calculated to the
|
||||
// five anchor points and the smallest will be chosen.
|
||||
std::array<double, 5> dists;
|
||||
auto cc = fullbb.center(); // The gravity center
|
||||
dists[0] = pl::distance(minc, cc);
|
||||
dists[1] = pl::distance(maxc, cc);
|
||||
dists[2] = pl::distance(ibb.center(), cc);
|
||||
dists[3] = pl::distance(top_left, cc);
|
||||
dists[4] = pl::distance(bottom_right, cc);
|
||||
|
||||
// The smalles distance from the arranged pile center:
|
||||
double dist = norm(*(std::min_element(dists.begin(), dists.end())));
|
||||
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT) {
|
||||
double bindist = dist_for_BOTTOM_LEFT(ibb, origin_pack);
|
||||
score = 0.2 * dist + 0.8 * bindist;
|
||||
}
|
||||
else {
|
||||
double bindist = norm(pl::distance(ibb.center(), origin_pack));
|
||||
dist = 0.8 * dist + 0.2 * bindist;
|
||||
|
||||
|
||||
// Prepare a variable for the alignment score.
|
||||
// This will indicate: how well is the candidate item
|
||||
// aligned with its neighbors. We will check the alignment
|
||||
// with all neighbors and return the score for the best
|
||||
// alignment. So it is enough for the candidate to be
|
||||
// aligned with only one item.
|
||||
auto alignment_score = 1.0;
|
||||
|
||||
auto query = bgi::intersects(ibb);
|
||||
auto& index = isBig(item.area()) ? spatindex : smalls_spatindex;
|
||||
|
||||
// Query the spatial index for the neighbors
|
||||
std::vector<SpatElement> result;
|
||||
result.reserve(index.size());
|
||||
|
||||
index.query(query, std::back_inserter(result));
|
||||
|
||||
// now get the score for the best alignment
|
||||
for (auto& e : result) {
|
||||
auto idx = e.second;
|
||||
Item& p = m_items[idx];
|
||||
auto parea = p.area();
|
||||
if (std::abs(1.0 - parea / item.area()) < 1e-6) {
|
||||
auto bb = sl::boundingBox(p.boundingBox(), ibb);
|
||||
auto bbarea = bb.area();
|
||||
auto ascore = 1.0 - (item.area() + parea) / bbarea;
|
||||
|
||||
if (ascore < alignment_score) alignment_score = ascore;
|
||||
}
|
||||
}
|
||||
|
||||
density = std::sqrt(norm(fullbb.width()) * norm(fullbb.height()));
|
||||
double R = double(m_remaining.size()) / m_item_count;
|
||||
|
||||
// The final mix of the score is the balance between the
|
||||
// distance from the full pile center, the pack density and
|
||||
// the alignment with the neighbors
|
||||
if (result.empty())
|
||||
score = 0.50 * dist + 0.50 * density;
|
||||
else
|
||||
// Let the density matter more when fewer objects remain
|
||||
score = 0.50 * dist + (1.0 - R) * 0.20 * density +
|
||||
0.30 * alignment_score;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LAST_BIG_ITEM: {
|
||||
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT) {
|
||||
score = dist_for_BOTTOM_LEFT(ibb, origin_pack);
|
||||
}
|
||||
else {
|
||||
score = 0.5 * norm(pl::distance(ibb.center(), origin_pack));
|
||||
if (m_pilebb.defined)
|
||||
score += 0.5 * norm(pl::distance(ibb.center(), m_pilebb.center()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SMALL_ITEM: {
|
||||
// Here there are the small items that should be placed around the
|
||||
// already processed bigger items.
|
||||
// No need to play around with the anchor points, the center will be
|
||||
// just fine for small items
|
||||
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT)
|
||||
score = dist_for_BOTTOM_LEFT(ibb, origin_pack);
|
||||
else {
|
||||
// Align mainly around existing items
|
||||
score = 0.8 * norm(pl::distance(ibb.center(), bigbb.center()))+ 0.2*norm(pl::distance(ibb.center(), origin_pack));
|
||||
// Align to 135 degree line {calc distance to the line x+y-(xc+yc)=0}
|
||||
//auto ic = ibb.center(), bigbbc = origin_pack;// bigbb.center();
|
||||
//score = norm(std::abs(ic.x() + ic.y() - bigbbc.x() - bigbbc.y()));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (params.is_seq_print) {
|
||||
double clearance_height_to_lid = params.clearance_height_to_lid;
|
||||
double clearance_height_to_rod = params.clearance_height_to_rod;
|
||||
bool hasRowHeightConflict = false;
|
||||
bool hasLidHeightConflict = false;
|
||||
auto iy1 = item.boundingBox().minCorner().y();
|
||||
auto iy2 = item.boundingBox().maxCorner().y();
|
||||
auto ix1 = item.boundingBox().minCorner().x();
|
||||
|
||||
for (int i = 0; i < m_items.size(); i++) {
|
||||
Item& p = m_items[i];
|
||||
if (p.is_virt_object) continue;
|
||||
auto px1 = p.boundingBox().minCorner().x();
|
||||
auto py1 = p.boundingBox().minCorner().y();
|
||||
auto py2 = p.boundingBox().maxCorner().y();
|
||||
auto inter_min = std::max(iy1, py1); // min y of intersection
|
||||
auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists
|
||||
if (inter_max - inter_min > 0) {
|
||||
// if they inter, the one on the left will be printed first
|
||||
double h = ix1 < px1 ? item.height : p.height;
|
||||
hasRowHeightConflict |= (h > clearance_height_to_rod);
|
||||
}
|
||||
// only last item can be heigher than clearance_height_to_lid, so if the existing items are higher than clearance_height_to_lid, there is height conflict
|
||||
hasLidHeightConflict |= (p.height > clearance_height_to_lid);
|
||||
}
|
||||
|
||||
double lambda3 = LARGE_COST_TO_REJECT;
|
||||
double lambda4 = LARGE_COST_TO_REJECT;
|
||||
for (int i = 0; i < m_items.size(); i++) {
|
||||
Item& p = m_items[i];
|
||||
if (p.is_virt_object) continue;
|
||||
score += lambda3 * (item.bed_temp - p.vitrify_temp > 0);
|
||||
}
|
||||
score += lambda4 * hasRowHeightConflict + lambda4 * hasLidHeightConflict;
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < m_items.size(); i++) {
|
||||
Item& p = m_items[i];
|
||||
if (p.is_virt_object) {
|
||||
// Better not put items above wipe tower
|
||||
if (p.is_wipe_tower)
|
||||
score += ibb.maxCorner().y() > p.boundingBox().maxCorner().y();
|
||||
else
|
||||
continue;
|
||||
} else
|
||||
score += LARGE_COST_TO_REJECT * (item.bed_temp - p.bed_temp != 0);
|
||||
}
|
||||
}
|
||||
|
||||
std::set<int> extruder_ids;
|
||||
for (int i = 0; i < m_items.size(); i++) {
|
||||
Item& p = m_items[i];
|
||||
if (p.is_virt_object) continue;
|
||||
extruder_ids.insert(p.extrude_id);
|
||||
// add a large cost if not multi materials on same plate is not allowed
|
||||
if (!params.allow_multi_materials_on_same_plate)
|
||||
score += LARGE_COST_TO_REJECT * (item.extrude_id != p.extrude_id);
|
||||
}
|
||||
// for layered printing, we want extruder change as few as possible
|
||||
if (!params.is_seq_print) {
|
||||
extruder_ids.insert(item.extrude_id);
|
||||
score += 0.2 * LARGE_COST_TO_REJECT * std::max(0, ((int)extruder_ids.size() - 1));
|
||||
}
|
||||
|
||||
return std::make_tuple(score, fullbb);
|
||||
}
|
||||
|
||||
std::function<double(const Item&)> get_objfn();
|
||||
|
||||
public:
|
||||
AutoArranger(const TBin & bin,
|
||||
const ArrangeParams ¶ms,
|
||||
std::function<void(unsigned,std::string)> progressind,
|
||||
std::function<bool(void)> stopcond)
|
||||
: m_pck(bin, params.min_obj_distance)
|
||||
, m_bin(bin)
|
||||
{
|
||||
m_bin_area = abs(sl::area(bin)); // due to clockwise or anti-clockwise, the result of sl::area may be negative
|
||||
m_norm = std::sqrt(m_bin_area);
|
||||
fill_config(m_pconf, params);
|
||||
this->params = params;
|
||||
|
||||
// Set up a callback that is called just before arranging starts
|
||||
// This functionality is provided by the Nester class (m_pack).
|
||||
m_pconf.before_packing =
|
||||
[this](const MultiPolygon& merged_pile, // merged pile
|
||||
const ItemGroup& items, // packed items
|
||||
const ItemGroup& remaining) // future items to be packed
|
||||
{
|
||||
m_items = items;
|
||||
m_merged_pile = merged_pile;
|
||||
m_remaining = remaining;
|
||||
|
||||
m_pilebb.defined = false;
|
||||
if (!merged_pile.empty())
|
||||
{
|
||||
m_pilebb = sl::boundingBox(merged_pile);
|
||||
m_pilebb.defined = true;
|
||||
}
|
||||
|
||||
m_rtree.clear();
|
||||
m_smallsrtree.clear();
|
||||
|
||||
// We will treat big items (compared to the print bed) differently
|
||||
auto isBig = [this](double a) {
|
||||
return a / m_bin_area > BIG_ITEM_TRESHOLD ;
|
||||
};
|
||||
|
||||
for(unsigned idx = 0; idx < items.size(); ++idx) {
|
||||
Item& itm = items[idx];
|
||||
if (itm.is_virt_object) continue;
|
||||
if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx});
|
||||
m_smallsrtree.insert({itm.boundingBox(), idx});
|
||||
}
|
||||
};
|
||||
|
||||
m_pconf.object_function = get_objfn();
|
||||
|
||||
auto bbox2expoly = [](Box bb) {
|
||||
ExPolygon bin_poly;
|
||||
auto c0 = bb.minCorner();
|
||||
auto c1 = bb.maxCorner();
|
||||
bin_poly.contour.points.emplace_back(c0);
|
||||
bin_poly.contour.points.emplace_back(c1.x(), c0.y());
|
||||
bin_poly.contour.points.emplace_back(c1);
|
||||
bin_poly.contour.points.emplace_back(c0.x(), c1.y());
|
||||
return bin_poly;
|
||||
};
|
||||
|
||||
m_pconf.on_preload = [this](const ItemGroup &items, PConfig &cfg) {
|
||||
if (items.empty()) return;
|
||||
|
||||
auto bb = sl::boundingBox(m_bin);
|
||||
// BBS: excluded region (virtual object but not wipe tower) should not affect final alignment
|
||||
bool all_is_excluded_region = std::all_of(items.begin(), items.end(), [](Item &itm) { return itm.is_virt_object && !itm.is_wipe_tower; });
|
||||
if (!all_is_excluded_region)
|
||||
cfg.alignment = PConfig::Alignment::DONT_ALIGN;
|
||||
|
||||
auto starting_point = cfg.starting_point == PConfig::Alignment::BOTTOM_LEFT ? bb.minCorner() : bb.center();
|
||||
// if we have wipe tower, items should be arranged around wipe tower
|
||||
for (Item itm : items) {
|
||||
if (itm.is_wipe_tower) {
|
||||
starting_point = itm.boundingBox().center();
|
||||
break;
|
||||
}
|
||||
}
|
||||
cfg.object_function = [this, bb, starting_point](const Item& item) {
|
||||
return fixed_overfit(objfunc(item, starting_point), bb);
|
||||
};
|
||||
};
|
||||
|
||||
auto on_packed = params.on_packed;
|
||||
|
||||
if (progressind || on_packed)
|
||||
m_pck.progressIndicator(
|
||||
[this, progressind, on_packed](unsigned num_finished) {
|
||||
int last_bed = m_pck.lastPackedBinId();
|
||||
if (last_bed >= 0) {
|
||||
Item& last_packed = m_pck.lastResult()[last_bed].back();
|
||||
ArrangePolygon ap;
|
||||
ap.bed_idx = last_packed.binId();
|
||||
ap.priority = last_packed.priority();
|
||||
if (progressind) progressind(num_finished, last_packed.name);
|
||||
if (on_packed)
|
||||
on_packed(ap);
|
||||
BOOST_LOG_TRIVIAL(debug) << "arrange " + last_packed.name + " succeed!"
|
||||
<< ", plate id=" << ap.bed_idx;
|
||||
}
|
||||
});
|
||||
|
||||
//if (progressind) {
|
||||
// m_pck.unfitIndicator([this, progressind](std::string name) {
|
||||
// progressind(100, name+" not fit!");
|
||||
// BOOST_LOG_TRIVIAL(debug) << "arrange not fit: " + name;
|
||||
// });
|
||||
//}
|
||||
|
||||
if (stopcond) m_pck.stopCondition(stopcond);
|
||||
|
||||
m_pconf.sortfunc= [¶ms](Item& i1, Item& i2) {
|
||||
int p1 = i1.priority(), p2 = i2.priority();
|
||||
if (p1 != p2)
|
||||
return p1 > p2;
|
||||
if (params.is_seq_print) {
|
||||
return i1.bed_temp != i2.bed_temp ? (i1.bed_temp > i2.bed_temp) :
|
||||
(i1.height != i2.height ? (i1.height < i2.height) : (i1.area() > i2.area()));
|
||||
}
|
||||
else {
|
||||
return i1.bed_temp != i2.bed_temp ? (i1.bed_temp > i2.bed_temp) :
|
||||
(i1.extrude_id != i2.extrude_id ? (i1.extrude_id < i2.extrude_id) : (i1.area() > i2.area()));
|
||||
}
|
||||
};
|
||||
|
||||
m_pck.configure(m_pconf);
|
||||
}
|
||||
|
||||
template<class It> inline void operator()(It from, It to) {
|
||||
m_rtree.clear();
|
||||
m_item_count += size_t(to - from);
|
||||
m_pck.execute(from, to);
|
||||
m_item_count = 0;
|
||||
}
|
||||
|
||||
PConfig& config() { return m_pconf; }
|
||||
const PConfig& config() const { return m_pconf; }
|
||||
|
||||
inline void preload(std::vector<Item>& fixeditems) {
|
||||
for(unsigned idx = 0; idx < fixeditems.size(); ++idx) {
|
||||
Item& itm = fixeditems[idx];
|
||||
itm.markAsFixedInBin(itm.binId());
|
||||
}
|
||||
|
||||
m_item_count += fixeditems.size();
|
||||
}
|
||||
};
|
||||
|
||||
template<> std::function<double(const Item&)> AutoArranger<Box>::get_objfn()
|
||||
{
|
||||
auto origin_pack = m_pconf.starting_point == PConfig::Alignment::CENTER ? m_bin.center() : m_bin.minCorner();
|
||||
|
||||
return [this, origin_pack](const Item &itm) {
|
||||
auto result = objfunc(itm, origin_pack);
|
||||
|
||||
double score = std::get<0>(result);
|
||||
auto& fullbb = std::get<1>(result);
|
||||
|
||||
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT)
|
||||
{
|
||||
if (!sl::isInside(fullbb, m_bin))
|
||||
score += LARGE_COST_TO_REJECT;
|
||||
}
|
||||
else
|
||||
{
|
||||
double miss = Placer::overfit(fullbb, m_bin);
|
||||
miss = miss > 0 ? miss : 0;
|
||||
score += miss * miss;
|
||||
}
|
||||
|
||||
return score;
|
||||
};
|
||||
}
|
||||
|
||||
template<> std::function<double(const Item&)> AutoArranger<Circle>::get_objfn()
|
||||
{
|
||||
auto bb = sl::boundingBox(m_bin);
|
||||
auto origin_pack = m_pconf.starting_point == PConfig::Alignment::CENTER ? bb.center() : bb.minCorner();
|
||||
return [this, origin_pack](const Item &item) {
|
||||
|
||||
auto result = objfunc(item, origin_pack);
|
||||
|
||||
double score = std::get<0>(result);
|
||||
|
||||
auto isBig = [this](const Item& itm) {
|
||||
return itm.area() / m_bin_area > BIG_ITEM_TRESHOLD ;
|
||||
};
|
||||
|
||||
if(isBig(item)) {
|
||||
auto mp = m_merged_pile;
|
||||
mp.push_back(item.transformedShape());
|
||||
auto chull = sl::convexHull(mp);
|
||||
double miss = Placer::overfit(chull, m_bin);
|
||||
if(miss < 0) miss = 0;
|
||||
score += miss*miss;
|
||||
}
|
||||
|
||||
return score;
|
||||
};
|
||||
}
|
||||
|
||||
// Specialization for a generalized polygon.
|
||||
// Warning: this is much slower than with Box bed. Need further speedup.
|
||||
template<>
|
||||
std::function<double(const Item &)> AutoArranger<ExPolygon>::get_objfn()
|
||||
{
|
||||
auto bb = sl::boundingBox(m_bin);
|
||||
auto origin_pack = m_pconf.starting_point == PConfig::Alignment::CENTER ? bb.center() : bb.minCorner();
|
||||
return [this, origin_pack](const Item &itm) {
|
||||
auto result = objfunc(itm, origin_pack);
|
||||
|
||||
double score = std::get<0>(result);
|
||||
|
||||
auto mp = m_merged_pile;
|
||||
mp.emplace_back(itm.transformedShape());
|
||||
auto chull = sl::convexHull(mp);
|
||||
if (m_pconf.starting_point == PConfig::Alignment::BOTTOM_LEFT)
|
||||
{
|
||||
if (!sl::isInside(chull, m_bin))
|
||||
score += LARGE_COST_TO_REJECT;
|
||||
}
|
||||
else
|
||||
{
|
||||
double miss = Placer::overfit(chull, m_bin);
|
||||
miss = miss > 0 ? miss : 0;
|
||||
score += miss * miss;
|
||||
}
|
||||
|
||||
return score;
|
||||
};
|
||||
}
|
||||
|
||||
template<class Bin> void remove_large_items(std::vector<Item> &items, Bin &&bin)
|
||||
{
|
||||
auto it = items.begin();
|
||||
while (it != items.end())
|
||||
{
|
||||
//BBS: skip virtual object
|
||||
if (!it->is_virt_object && !sl::isInside(it->transformedShape(), bin))
|
||||
it = items.erase(it);
|
||||
else
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
template<class S> Radians min_area_boundingbox_rotation(const S &sh)
|
||||
{
|
||||
return minAreaBoundingBox<S, TCompute<S>, boost::rational<LargeInt>>(sh)
|
||||
.angleToX();
|
||||
}
|
||||
|
||||
template<class S>
|
||||
Radians fit_into_box_rotation(const S &sh, const _Box<TPoint<S>> &box)
|
||||
{
|
||||
return fitIntoBoxRotation<S, TCompute<S>, boost::rational<LargeInt>>(sh, box);
|
||||
}
|
||||
|
||||
template<class BinT> // Arrange for arbitrary bin type
|
||||
void _arrange(
|
||||
std::vector<Item> & shapes,
|
||||
std::vector<Item> & excludes,
|
||||
const BinT & bin,
|
||||
const ArrangeParams ¶ms,
|
||||
std::function<void(unsigned,std::string)> progressfn,
|
||||
std::function<bool()> stopfn)
|
||||
{
|
||||
// Integer ceiling the min distance from the bed perimeters
|
||||
coord_t md = params.min_obj_distance;
|
||||
md = md / 2;
|
||||
|
||||
auto corrected_bin = bin;
|
||||
//sl::offset(corrected_bin, md);
|
||||
ArrangeParams mod_params = params;
|
||||
mod_params.min_obj_distance = 0; // items are already inflated
|
||||
|
||||
AutoArranger<BinT> arranger{corrected_bin, mod_params, progressfn, stopfn};
|
||||
|
||||
remove_large_items(excludes, corrected_bin);
|
||||
|
||||
// If there is something on the plate
|
||||
if (!excludes.empty()) arranger.preload(excludes);
|
||||
|
||||
std::vector<std::reference_wrapper<Item>> inp;
|
||||
inp.reserve(shapes.size() + excludes.size());
|
||||
for (auto &itm : shapes ) inp.emplace_back(itm);
|
||||
for (auto &itm : excludes) inp.emplace_back(itm);
|
||||
|
||||
// Use the minimum bounding box rotation as a starting point.
|
||||
// TODO: This only works for convex hull. If we ever switch to concave
|
||||
// polygon nesting, a convex hull needs to be calculated.
|
||||
if (params.allow_rotations) {
|
||||
for (auto &itm : shapes) {
|
||||
itm.rotation(min_area_boundingbox_rotation(itm.rawShape()));
|
||||
|
||||
// If the item is too big, try to find a rotation that makes it fit
|
||||
if constexpr (std::is_same_v<BinT, Box>) {
|
||||
auto bb = itm.boundingBox();
|
||||
if (bb.width() >= bin.width() || bb.height() >= bin.height())
|
||||
itm.rotate(fit_into_box_rotation(itm.transformedShape(), bin));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arranger(inp.begin(), inp.end());
|
||||
for (Item &itm : inp) itm.inflation(0);
|
||||
}
|
||||
|
||||
inline Box to_nestbin(const BoundingBox &bb) { return Box{{bb.min(X), bb.min(Y)}, {bb.max(X), bb.max(Y)}};}
|
||||
inline Circle to_nestbin(const CircleBed &c) { return Circle({c.center()(0), c.center()(1)}, c.radius()); }
|
||||
inline ExPolygon to_nestbin(const Polygon &p) { return ExPolygon{p}; }
|
||||
inline Box to_nestbin(const InfiniteBed &bed) { return Box::infinite({bed.center.x(), bed.center.y()}); }
|
||||
|
||||
inline coord_t width(const BoundingBox& box) { return box.max.x() - box.min.x(); }
|
||||
inline coord_t height(const BoundingBox& box) { return box.max.y() - box.min.y(); }
|
||||
inline double area(const BoundingBox& box) { return double(width(box)) * height(box); }
|
||||
inline double poly_area(const Points &pts) { return std::abs(Polygon::area(pts)); }
|
||||
inline double distance_to(const Point& p1, const Point& p2)
|
||||
{
|
||||
double dx = p2.x() - p1.x();
|
||||
double dy = p2.y() - p1.y();
|
||||
return std::sqrt(dx*dx + dy*dy);
|
||||
}
|
||||
|
||||
static CircleBed to_circle(const Point ¢er, const Points& points) {
|
||||
std::vector<double> vertex_distances;
|
||||
double avg_dist = 0;
|
||||
|
||||
for (auto pt : points)
|
||||
{
|
||||
double distance = distance_to(center, pt);
|
||||
vertex_distances.push_back(distance);
|
||||
avg_dist += distance;
|
||||
}
|
||||
|
||||
avg_dist /= vertex_distances.size();
|
||||
|
||||
CircleBed ret(center, avg_dist);
|
||||
for(auto el : vertex_distances)
|
||||
{
|
||||
if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) {
|
||||
ret = {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Create Item from Arrangeable
|
||||
static void process_arrangeable(const ArrangePolygon &arrpoly,
|
||||
std::vector<Item> & outp)
|
||||
{
|
||||
Polygon p = arrpoly.poly.contour;
|
||||
const Vec2crd &offs = arrpoly.translation;
|
||||
double rotation = arrpoly.rotation;
|
||||
|
||||
if (p.is_counter_clockwise()) p.reverse();
|
||||
|
||||
if (p.size() < 3)
|
||||
return;
|
||||
|
||||
outp.emplace_back(std::move(p));
|
||||
Item& item = outp.back();
|
||||
item.rotation(rotation);
|
||||
item.translation({offs.x(), offs.y()});
|
||||
item.binId(arrpoly.bed_idx);
|
||||
item.priority(arrpoly.priority);
|
||||
item.itemId(arrpoly.itemid);
|
||||
item.extrude_id = arrpoly.extrude_ids.back();
|
||||
item.height = arrpoly.height;
|
||||
item.name = arrpoly.name;
|
||||
//BBS: add virtual object logic
|
||||
item.is_virt_object = arrpoly.is_virt_object;
|
||||
item.is_wipe_tower = arrpoly.is_wipe_tower;
|
||||
item.bed_temp = arrpoly.first_bed_temp;
|
||||
item.print_temp = arrpoly.print_temp;
|
||||
item.vitrify_temp = arrpoly.vitrify_temp;
|
||||
item.inflation(arrpoly.inflation);
|
||||
}
|
||||
|
||||
template<class Fn> auto call_with_bed(const Points &bed, Fn &&fn)
|
||||
{
|
||||
if (bed.empty())
|
||||
return fn(InfiniteBed{});
|
||||
else if (bed.size() == 1)
|
||||
return fn(InfiniteBed{bed.front()});
|
||||
else {
|
||||
auto bb = BoundingBox(bed);
|
||||
CircleBed circ = to_circle(bb.center(), bed);
|
||||
auto parea = poly_area(bed);
|
||||
|
||||
if ((1.0 - parea / area(bb)) < 1e-3)
|
||||
return fn(bb);
|
||||
else if (!std::isnan(circ.radius()))
|
||||
return fn(circ);
|
||||
else
|
||||
return fn(Polygon(bed));
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void arrange(ArrangePolygons & items,
|
||||
const ArrangePolygons &excludes,
|
||||
const Points & bed,
|
||||
const ArrangeParams & params)
|
||||
{
|
||||
call_with_bed(bed, [&](const auto &bin) {
|
||||
arrange(items, excludes, bin, params);
|
||||
});
|
||||
}
|
||||
|
||||
template<class BedT>
|
||||
void arrange(ArrangePolygons & arrangables,
|
||||
const ArrangePolygons &excludes,
|
||||
const BedT & bed,
|
||||
const ArrangeParams & params)
|
||||
{
|
||||
namespace clppr = Slic3r::ClipperLib;
|
||||
|
||||
std::vector<Item> items, fixeditems;
|
||||
items.reserve(arrangables.size());
|
||||
|
||||
for (ArrangePolygon &arrangeable : arrangables)
|
||||
process_arrangeable(arrangeable, items);
|
||||
|
||||
for (const ArrangePolygon &fixed: excludes)
|
||||
process_arrangeable(fixed, fixeditems);
|
||||
|
||||
for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON));
|
||||
|
||||
_arrange(items, fixeditems, to_nestbin(bed), params, params.progressind, params.stopcondition);
|
||||
|
||||
for(size_t i = 0; i < items.size(); ++i) {
|
||||
Point tr = items[i].translation();
|
||||
arrangables[i].translation = {coord_t(tr.x()), coord_t(tr.y())};
|
||||
arrangables[i].rotation = items[i].rotation();
|
||||
arrangables[i].bed_idx = items[i].binId();
|
||||
arrangables[i].itemid = items[i].itemId(); // arrange order is useful for sequential printing
|
||||
}
|
||||
}
|
||||
|
||||
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms);
|
||||
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms);
|
||||
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms);
|
||||
template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms);
|
||||
|
||||
} // namespace arr
|
||||
} // namespace Slic3r
|
||||
175
src/libslic3r/Arrange.hpp
Normal file
175
src/libslic3r/Arrange.hpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
#ifndef ARRANGE_HPP
|
||||
#define ARRANGE_HPP
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
|
||||
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};
|
||||
//BBS: add locked_plate to indicate whether it is in the locked plate
|
||||
int locked_plate{ -1 };
|
||||
bool is_virt_object{ false };
|
||||
bool is_wipe_tower{false};
|
||||
//BBS: add row/col for sudoku-style layout
|
||||
int row{0};
|
||||
int col{0};
|
||||
std::vector<int> extrude_ids{1}; ///extruder_id for least extruder switch
|
||||
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 auto_brim_width{ 0 }; // auto brim width
|
||||
double user_brim_width{ 0 }; // user defined 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;
|
||||
|
||||
//BBS: add specific arrange params
|
||||
bool allow_multi_materials_on_same_plate = true;
|
||||
bool avoid_extrusion_cali_region = true;
|
||||
bool is_seq_print = false;
|
||||
float bed_shrink_x = 0;
|
||||
float bed_shrink_y = 0;
|
||||
float brim_skirt_distance = 0;
|
||||
float clearance_height_to_rod = 0;
|
||||
float clearance_height_to_lid = 0;
|
||||
float cleareance_radius = 0;
|
||||
|
||||
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) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Arranges the input polygons.
|
||||
*
|
||||
* WARNING: Currently, only convex polygons are supported by the libnest2d
|
||||
* library which is used to do the arrangement. This might change in the future
|
||||
* this is why the interface contains a general polygon capable to have holes.
|
||||
*
|
||||
* \param items Input vector of ArrangePolygons. The transformation, rotation
|
||||
* and bin_idx fields will be changed after the call finished and can be used
|
||||
* to apply the result on the input polygon.
|
||||
*/
|
||||
template<class TBed> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const TBed &bed, const ArrangeParams ¶ms = {});
|
||||
|
||||
// A dispatch function that determines the bed shape from a set of points.
|
||||
template<> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Points &bed, const ArrangeParams ¶ms);
|
||||
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms);
|
||||
extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms);
|
||||
|
||||
inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); }
|
||||
|
||||
}} // namespace Slic3r::arrangement
|
||||
|
||||
#endif // MODELARRANGE_HPP
|
||||
84
src/libslic3r/BlacklistedLibraryCheck.cpp
Normal file
84
src/libslic3r/BlacklistedLibraryCheck.cpp
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#include "BlacklistedLibraryCheck.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <psapi.h>
|
||||
# endif //WIN32
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
//only dll name with .dll suffix - currently case sensitive
|
||||
const std::vector<std::wstring> BlacklistedLibraryCheck::blacklist({ L"NahimicOSD.dll", L"SS2OSD.dll", L"amhook.dll", L"AMHook.dll" });
|
||||
|
||||
bool BlacklistedLibraryCheck::get_blacklisted(std::vector<std::wstring>& names)
|
||||
{
|
||||
if (m_found.empty())
|
||||
return false;
|
||||
for (const auto& lib : m_found)
|
||||
names.emplace_back(lib);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::wstring BlacklistedLibraryCheck::get_blacklisted_string()
|
||||
{
|
||||
std::wstring ret;
|
||||
for (const auto& lib : m_found)
|
||||
ret += lib + L"\n";
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool BlacklistedLibraryCheck::perform_check()
|
||||
{
|
||||
// Get the pseudo-handle for the current process.
|
||||
HANDLE hCurrentProcess = GetCurrentProcess();
|
||||
|
||||
// Get a list of all the modules in this process.
|
||||
HMODULE hMods[1024];
|
||||
DWORD cbNeeded;
|
||||
if (EnumProcessModulesEx(hCurrentProcess, hMods, sizeof(hMods), &cbNeeded, LIST_MODULES_ALL))
|
||||
{
|
||||
//printf("Total Dlls: %d\n", cbNeeded / sizeof(HMODULE));
|
||||
for (unsigned int i = 0; i < cbNeeded / sizeof(HMODULE); ++ i)
|
||||
{
|
||||
wchar_t szModName[MAX_PATH];
|
||||
// Get the full path to the module's file.
|
||||
if (GetModuleFileNameExW(hCurrentProcess, hMods[i], szModName, MAX_PATH))
|
||||
{
|
||||
// Add to list if blacklisted
|
||||
if (BlacklistedLibraryCheck::is_blacklisted(szModName)) {
|
||||
//wprintf(L"Contains library: %s\n", szModName);
|
||||
if (std::find(m_found.begin(), m_found.end(), szModName) == m_found.end())
|
||||
m_found.emplace_back(szModName);
|
||||
}
|
||||
//wprintf(L"%s\n", szModName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//printf("\n");
|
||||
return !m_found.empty();
|
||||
}
|
||||
|
||||
bool BlacklistedLibraryCheck::is_blacklisted(const std::wstring &dllpath)
|
||||
{
|
||||
std::wstring dllname = boost::filesystem::path(dllpath).filename().wstring();
|
||||
//std::transform(dllname.begin(), dllname.end(), dllname.begin(), std::tolower);
|
||||
if (std::find(BlacklistedLibraryCheck::blacklist.begin(), BlacklistedLibraryCheck::blacklist.end(), dllname) != BlacklistedLibraryCheck::blacklist.end()) {
|
||||
//std::wprintf(L"%s is blacklisted\n", dllname.c_str());
|
||||
return true;
|
||||
}
|
||||
//std::wprintf(L"%s is NOT blacklisted\n", dllname.c_str());
|
||||
return false;
|
||||
}
|
||||
bool BlacklistedLibraryCheck::is_blacklisted(const std::string &dllpath)
|
||||
{
|
||||
return BlacklistedLibraryCheck::is_blacklisted(boost::nowide::widen(dllpath));
|
||||
}
|
||||
|
||||
#endif //WIN32
|
||||
|
||||
} // namespace Slic3r
|
||||
46
src/libslic3r/BlacklistedLibraryCheck.hpp
Normal file
46
src/libslic3r/BlacklistedLibraryCheck.hpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#ifndef slic3r_BlacklistedLibraryCheck_hpp_
|
||||
#define slic3r_BlacklistedLibraryCheck_hpp_
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#endif //WIN32
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef WIN32
|
||||
class BlacklistedLibraryCheck
|
||||
{
|
||||
public:
|
||||
static BlacklistedLibraryCheck& get_instance()
|
||||
{
|
||||
static BlacklistedLibraryCheck instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
private:
|
||||
BlacklistedLibraryCheck() = default;
|
||||
|
||||
std::vector<std::wstring> m_found;
|
||||
public:
|
||||
BlacklistedLibraryCheck(BlacklistedLibraryCheck const&) = delete;
|
||||
void operator=(BlacklistedLibraryCheck const&) = delete;
|
||||
// returns all found blacklisted dlls
|
||||
bool get_blacklisted(std::vector<std::wstring>& names);
|
||||
std::wstring get_blacklisted_string();
|
||||
// returns true if enumerating found blacklisted dll
|
||||
bool perform_check();
|
||||
|
||||
// UTF-8 encoded path
|
||||
static bool is_blacklisted(const std::string &dllpath);
|
||||
static bool is_blacklisted(const std::wstring &dllpath);
|
||||
private:
|
||||
static const std::vector<std::wstring> blacklist;
|
||||
};
|
||||
|
||||
#endif //WIN32
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif //slic3r_BlacklistedLibraryCheck_hpp_
|
||||
289
src/libslic3r/BoundingBox.cpp
Normal file
289
src/libslic3r/BoundingBox.cpp
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
#include "BoundingBox.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
template BoundingBoxBase<Point>::BoundingBoxBase(const std::vector<Point> &points);
|
||||
template BoundingBoxBase<Vec2d>::BoundingBoxBase(const std::vector<Vec2d> &points);
|
||||
|
||||
template BoundingBox3Base<Vec3d>::BoundingBox3Base(const std::vector<Vec3d> &points);
|
||||
|
||||
void BoundingBox::polygon(Polygon* polygon) const
|
||||
{
|
||||
polygon->points.clear();
|
||||
polygon->points.resize(4);
|
||||
polygon->points[0](0) = this->min(0);
|
||||
polygon->points[0](1) = this->min(1);
|
||||
polygon->points[1](0) = this->max(0);
|
||||
polygon->points[1](1) = this->min(1);
|
||||
polygon->points[2](0) = this->max(0);
|
||||
polygon->points[2](1) = this->max(1);
|
||||
polygon->points[3](0) = this->min(0);
|
||||
polygon->points[3](1) = this->max(1);
|
||||
}
|
||||
|
||||
Polygon BoundingBox::polygon() const
|
||||
{
|
||||
Polygon p;
|
||||
this->polygon(&p);
|
||||
return p;
|
||||
}
|
||||
|
||||
BoundingBox BoundingBox::rotated(double angle) const
|
||||
{
|
||||
BoundingBox out;
|
||||
out.merge(this->min.rotated(angle));
|
||||
out.merge(this->max.rotated(angle));
|
||||
out.merge(Point(this->min(0), this->max(1)).rotated(angle));
|
||||
out.merge(Point(this->max(0), this->min(1)).rotated(angle));
|
||||
return out;
|
||||
}
|
||||
|
||||
BoundingBox BoundingBox::rotated(double angle, const Point ¢er) const
|
||||
{
|
||||
BoundingBox out;
|
||||
out.merge(this->min.rotated(angle, center));
|
||||
out.merge(this->max.rotated(angle, center));
|
||||
out.merge(Point(this->min(0), this->max(1)).rotated(angle, center));
|
||||
out.merge(Point(this->max(0), this->min(1)).rotated(angle, center));
|
||||
return out;
|
||||
}
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBoxBase<PointClass>::scale(double factor)
|
||||
{
|
||||
this->min *= factor;
|
||||
this->max *= factor;
|
||||
}
|
||||
template void BoundingBoxBase<Point>::scale(double factor);
|
||||
template void BoundingBoxBase<Vec2d>::scale(double factor);
|
||||
template void BoundingBoxBase<Vec3d>::scale(double factor);
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBoxBase<PointClass>::merge(const PointClass &point)
|
||||
{
|
||||
if (this->defined) {
|
||||
this->min = this->min.cwiseMin(point);
|
||||
this->max = this->max.cwiseMax(point);
|
||||
} else {
|
||||
this->min = point;
|
||||
this->max = point;
|
||||
this->defined = true;
|
||||
}
|
||||
}
|
||||
template void BoundingBoxBase<Point>::merge(const Point &point);
|
||||
template void BoundingBoxBase<Vec2f>::merge(const Vec2f &point);
|
||||
template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBoxBase<PointClass>::merge(const std::vector<PointClass> &points)
|
||||
{
|
||||
this->merge(BoundingBoxBase(points));
|
||||
}
|
||||
template void BoundingBoxBase<Point>::merge(const Points &points);
|
||||
template void BoundingBoxBase<Vec2d>::merge(const Pointfs &points);
|
||||
|
||||
template <class PointClass> void
|
||||
BoundingBoxBase<PointClass>::merge(const BoundingBoxBase<PointClass> &bb)
|
||||
{
|
||||
assert(bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1));
|
||||
if (bb.defined) {
|
||||
if (this->defined) {
|
||||
this->min = this->min.cwiseMin(bb.min);
|
||||
this->max = this->max.cwiseMax(bb.max);
|
||||
} else {
|
||||
this->min = bb.min;
|
||||
this->max = bb.max;
|
||||
this->defined = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
template void BoundingBoxBase<Point>::merge(const BoundingBoxBase<Point> &bb);
|
||||
template void BoundingBoxBase<Vec2f>::merge(const BoundingBoxBase<Vec2f> &bb);
|
||||
template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
|
||||
|
||||
//BBS
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
261
src/libslic3r/BoundingBox.hpp
Normal file
261
src/libslic3r/BoundingBox.hpp
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
#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) : min(PointClass::Zero()), max(PointClass::Zero())
|
||||
{
|
||||
if (from == to) {
|
||||
this->defined = false;
|
||||
// throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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)); } // BBS
|
||||
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));
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
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;//BBS: 2D footprint polygon
|
||||
void merge(const PointClass &point);
|
||||
void merge(const std::vector<PointClass> &points);
|
||||
void merge(const BoundingBox3Base<PointClass> &bb);
|
||||
PointClass size() const;
|
||||
double radius() const;
|
||||
void translate(coordf_t x, coordf_t y, coordf_t z) { assert(this->defined); PointClass v(x, y, z); this->min += v; this->max += v; }
|
||||
void translate(const Vec3d &v) { this->min += v; this->max += v; }
|
||||
void offset(coordf_t delta);
|
||||
BoundingBox3Base<PointClass> inflated(coordf_t delta) const throw() { BoundingBox3Base<PointClass> out(*this); out.offset(delta); return out; }
|
||||
PointClass center() const;
|
||||
coordf_t max_size() const;
|
||||
|
||||
bool contains(const PointClass &point) const {
|
||||
return BoundingBoxBase<PointClass>::contains(point) && point(2) >= this->min(2) && point(2) <= this->max(2);
|
||||
}
|
||||
|
||||
bool contains(const BoundingBox3Base<PointClass>& other) const {
|
||||
return contains(other.min) && contains(other.max);
|
||||
}
|
||||
|
||||
bool intersects(const BoundingBox3Base<PointClass>& other) const {
|
||||
return (this->min(0) < other.max(0)) && (this->max(0) > other.min(0)) && (this->min(1) < other.max(1)) && (this->max(1) > other.min(1)) && (this->min(2) < other.max(2)) && (this->max(2) > other.min(2));
|
||||
}
|
||||
};
|
||||
|
||||
// Will prevent warnings caused by non existing definition of template in hpp
|
||||
extern template void BoundingBoxBase<Point>::scale(double factor);
|
||||
extern template void BoundingBoxBase<Vec2d>::scale(double factor);
|
||||
extern template void BoundingBoxBase<Vec3d>::scale(double factor);
|
||||
extern template void BoundingBoxBase<Point>::offset(coordf_t delta);
|
||||
extern template void BoundingBoxBase<Vec2d>::offset(coordf_t delta);
|
||||
extern template void BoundingBoxBase<Point>::merge(const Point &point);
|
||||
extern template void BoundingBoxBase<Vec2f>::merge(const Vec2f &point);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const Vec2d &point);
|
||||
extern template void BoundingBoxBase<Point>::merge(const Points &points);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const Pointfs &points);
|
||||
extern template void BoundingBoxBase<Point>::merge(const BoundingBoxBase<Point> &bb);
|
||||
extern template void BoundingBoxBase<Vec2f>::merge(const BoundingBoxBase<Vec2f> &bb);
|
||||
extern template void BoundingBoxBase<Vec2d>::merge(const BoundingBoxBase<Vec2d> &bb);
|
||||
extern template Point BoundingBoxBase<Point>::size() const;
|
||||
extern template Vec2f BoundingBoxBase<Vec2f>::size() const;
|
||||
extern template Vec2d BoundingBoxBase<Vec2d>::size() const;
|
||||
extern template double BoundingBoxBase<Point>::radius() const;
|
||||
extern template double BoundingBoxBase<Vec2d>::radius() const;
|
||||
extern template Point BoundingBoxBase<Point>::center() const;
|
||||
extern template Vec2f BoundingBoxBase<Vec2f>::center() const;
|
||||
extern template Vec2d BoundingBoxBase<Vec2d>::center() const;
|
||||
extern template void BoundingBox3Base<Vec3f>::merge(const Vec3f &point);
|
||||
extern template void BoundingBox3Base<Vec3d>::merge(const Vec3d &point);
|
||||
extern template void BoundingBox3Base<Vec3d>::merge(const Pointf3s &points);
|
||||
extern template void BoundingBox3Base<Vec3d>::merge(const BoundingBox3Base<Vec3d> &bb);
|
||||
extern template Vec3f BoundingBox3Base<Vec3f>::size() const;
|
||||
extern template Vec3d BoundingBox3Base<Vec3d>::size() const;
|
||||
extern template double BoundingBox3Base<Vec3d>::radius() const;
|
||||
extern template void BoundingBox3Base<Vec3d>::offset(coordf_t delta);
|
||||
extern template Vec3f BoundingBox3Base<Vec3f>::center() const;
|
||||
extern template Vec3d BoundingBox3Base<Vec3d>::center() const;
|
||||
extern template coordf_t BoundingBox3Base<Vec3f>::max_size() const;
|
||||
extern template coordf_t BoundingBox3Base<Vec3d>::max_size() const;
|
||||
|
||||
class BoundingBox : public BoundingBoxBase<Point>
|
||||
{
|
||||
public:
|
||||
void polygon(Polygon* polygon) const;
|
||||
Polygon polygon() const;
|
||||
BoundingBox rotated(double angle) const;
|
||||
BoundingBox rotated(double angle, const Point ¢er) const;
|
||||
void rotate(double angle) { (*this) = this->rotated(angle); }
|
||||
void rotate(double angle, const Point ¢er) { (*this) = this->rotated(angle, center); }
|
||||
// Align the min corner to a grid of cell_size x cell_size cells,
|
||||
// to encompass the original bounding box.
|
||||
void align_to_grid(const coord_t cell_size);
|
||||
|
||||
BoundingBox() : BoundingBoxBase<Point>() {}
|
||||
BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point>(pmin, pmax) {}
|
||||
BoundingBox(const Points &points) : BoundingBoxBase<Point>(points) {}
|
||||
|
||||
BoundingBox inflated(coordf_t delta) const throw() { BoundingBox out(*this); out.offset(delta); return out; }
|
||||
|
||||
friend BoundingBox get_extents_rotated(const Points &points, double angle);
|
||||
};
|
||||
|
||||
class BoundingBox3 : public BoundingBox3Base<Vec3crd>
|
||||
{
|
||||
public:
|
||||
BoundingBox3() : BoundingBox3Base<Vec3crd>() {}
|
||||
BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base<Vec3crd>(pmin, pmax) {}
|
||||
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>(points) {}
|
||||
};
|
||||
|
||||
class BoundingBoxf : public BoundingBoxBase<Vec2d>
|
||||
{
|
||||
public:
|
||||
BoundingBoxf() : BoundingBoxBase<Vec2d>() {}
|
||||
BoundingBoxf(const Vec2d &pmin, const Vec2d &pmax) : BoundingBoxBase<Vec2d>(pmin, pmax) {}
|
||||
BoundingBoxf(const std::vector<Vec2d> &points) : BoundingBoxBase<Vec2d>(points) {}
|
||||
};
|
||||
|
||||
class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
|
||||
{
|
||||
public:
|
||||
using BoundingBox3Base::BoundingBox3Base;
|
||||
|
||||
BoundingBoxf3 transformed(const Transform3d& matrix) const;
|
||||
};
|
||||
|
||||
template<typename VT>
|
||||
inline bool empty(const BoundingBoxBase<VT> &bb)
|
||||
{
|
||||
return ! bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1);
|
||||
}
|
||||
|
||||
template<typename VT>
|
||||
inline bool empty(const BoundingBox3Base<VT> &bb)
|
||||
{
|
||||
return ! bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1) || bb.min(2) >= bb.max(2);
|
||||
}
|
||||
|
||||
inline BoundingBox scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; }
|
||||
inline BoundingBox3 scaled(const BoundingBoxf3 &bb) { return {scaled(bb.min), scaled(bb.max)}; }
|
||||
inline BoundingBoxf unscaled(const BoundingBox &bb) { return {unscaled(bb.min), unscaled(bb.max)}; }
|
||||
inline BoundingBoxf3 unscaled(const BoundingBox3 &bb) { return {unscaled(bb.min), unscaled(bb.max)}; }
|
||||
|
||||
template<class Tout, class Tin>
|
||||
auto cast(const BoundingBoxBase<Tin> &b)
|
||||
{
|
||||
return BoundingBoxBase<Vec<3, Tout>>{b.min.template cast<Tout>(),
|
||||
b.max.template cast<Tout>()};
|
||||
}
|
||||
|
||||
template<class Tout, class Tin>
|
||||
auto cast(const BoundingBox3Base<Tin> &b)
|
||||
{
|
||||
return BoundingBox3Base<Vec<3, Tout>>{b.min.template cast<Tout>(),
|
||||
b.max.template cast<Tout>()};
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
// Serialization through the Cereal library
|
||||
namespace cereal {
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBox &bb) { archive(bb.min, bb.max, bb.defined); }
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBox3 &bb) { archive(bb.min, bb.max, bb.defined); }
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBoxf &bb) { archive(bb.min, bb.max, bb.defined); }
|
||||
template<class Archive> void serialize(Archive& archive, Slic3r::BoundingBoxf3 &bb) { archive(bb.min, bb.max, bb.defined); }
|
||||
}
|
||||
|
||||
#endif
|
||||
381
src/libslic3r/BridgeDetector.cpp
Normal file
381
src/libslic3r/BridgeDetector.cpp
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
#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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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() && 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;
|
||||
}
|
||||
|
||||
}
|
||||
69
src/libslic3r/BridgeDetector.hpp
Normal file
69
src/libslic3r/BridgeDetector.hpp
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef slic3r_BridgeDetector_hpp_
|
||||
#define slic3r_BridgeDetector_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.) {}
|
||||
// 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;
|
||||
};
|
||||
double angle;
|
||||
double coverage;
|
||||
double max_length;
|
||||
};
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1927
src/libslic3r/Brim.cpp
Normal file
1927
src/libslic3r/Brim.cpp
Normal file
File diff suppressed because it is too large
Load diff
30
src/libslic3r/Brim.hpp
Normal file
30
src/libslic3r/Brim.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef slic3r_Brim_hpp_
|
||||
#define slic3r_Brim_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
|
||||
#include<map>
|
||||
#include<vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Print;
|
||||
class ExtrusionEntityCollection;
|
||||
class PrintTryCancel;
|
||||
class ObjectID;
|
||||
|
||||
// Produce brim lines around those objects, that have the brim enabled.
|
||||
// Collect islands_area to be merged into the final 1st layer convex hull.
|
||||
ExtrusionEntityCollection make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_area);
|
||||
void make_brim(const Print& print, PrintTryCancel try_cancel,
|
||||
Polygons& islands_area, std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
|
||||
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
|
||||
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
|
||||
std::vector<unsigned int>& printExtruders);
|
||||
|
||||
// BBS: automatically make brim
|
||||
ExtrusionEntityCollection make_brim_auto(const Print &print, PrintTryCancel try_cancel, Polygons &islands_area);
|
||||
|
||||
} // Slic3r
|
||||
|
||||
#endif // slic3r_Brim_hpp_
|
||||
422
src/libslic3r/BuildVolume.cpp
Normal file
422
src/libslic3r/BuildVolume.cpp
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
#include "BuildVolume.hpp"
|
||||
#include "ClipperUtils.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 {};
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
126
src/libslic3r/BuildVolume.hpp
Normal file
126
src/libslic3r/BuildVolume.hpp
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#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) }; }
|
||||
|
||||
// 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_
|
||||
436
src/libslic3r/CMakeLists.txt
Normal file
436
src/libslic3r/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
project(libslic3r)
|
||||
|
||||
include(PrecompiledHeader)
|
||||
|
||||
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
|
||||
BoundingBox.cpp
|
||||
BoundingBox.hpp
|
||||
BridgeDetector.cpp
|
||||
BridgeDetector.hpp
|
||||
FaceDetector.cpp
|
||||
FaceDetector.hpp
|
||||
Brim.cpp
|
||||
Brim.hpp
|
||||
BuildVolume.cpp
|
||||
BuildVolume.hpp
|
||||
Circle.cpp
|
||||
Circle.hpp
|
||||
clipper.cpp
|
||||
clipper.hpp
|
||||
ClipperUtils.cpp
|
||||
ClipperUtils.hpp
|
||||
Config.cpp
|
||||
Config.hpp
|
||||
CurveAnalyzer.cpp
|
||||
CurveAnalyzer.hpp
|
||||
EdgeGrid.cpp
|
||||
EdgeGrid.hpp
|
||||
ElephantFootCompensation.cpp
|
||||
ElephantFootCompensation.hpp
|
||||
enum_bitmask.hpp
|
||||
ExPolygon.cpp
|
||||
ExPolygon.hpp
|
||||
ExPolygonCollection.cpp
|
||||
ExPolygonCollection.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/FillConcentricWGapFill.cpp
|
||||
Fill/FillConcentricWGapFill.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
|
||||
format.hpp
|
||||
Format/3mf.cpp
|
||||
Format/3mf.hpp
|
||||
Format/bbs_3mf.cpp
|
||||
Format/bbs_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
|
||||
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/SpiralVase.cpp
|
||||
GCode/SpiralVase.hpp
|
||||
GCode/SeamPlacer.cpp
|
||||
GCode/SeamPlacer.hpp
|
||||
GCode/SpeedGenerator.cpp
|
||||
GCode/SpeedGenerator.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.cpp
|
||||
GCode.hpp
|
||||
GCodeReader.cpp
|
||||
GCodeReader.hpp
|
||||
# GCodeSender.cpp
|
||||
# GCodeSender.hpp
|
||||
GCodeWriter.cpp
|
||||
GCodeWriter.hpp
|
||||
Geometry.cpp
|
||||
Geometry.hpp
|
||||
Geometry/Circle.cpp
|
||||
Geometry/Circle.hpp
|
||||
Geometry/ConvexHull.cpp
|
||||
Geometry/ConvexHull.hpp
|
||||
Geometry/MedialAxis.cpp
|
||||
Geometry/MedialAxis.hpp
|
||||
Geometry/Voronoi.hpp
|
||||
Geometry/VoronoiOffset.cpp
|
||||
Geometry/VoronoiOffset.hpp
|
||||
Geometry/VoronoiVisualUtils.hpp
|
||||
Int128.hpp
|
||||
InternalBridgeDetector.cpp
|
||||
InternalBridgeDetector.hpp
|
||||
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
|
||||
CustomGCode.cpp
|
||||
CustomGCode.hpp
|
||||
Arrange.hpp
|
||||
Arrange.cpp
|
||||
Orient.hpp
|
||||
Orient.cpp
|
||||
MultiPoint.cpp
|
||||
MultiPoint.hpp
|
||||
MutablePriorityQueue.hpp
|
||||
ObjectID.cpp
|
||||
ObjectID.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
|
||||
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
|
||||
ShortestPath.cpp
|
||||
ShortestPath.hpp
|
||||
SLAPrint.cpp
|
||||
SLAPrintSteps.cpp
|
||||
SLAPrintSteps.hpp
|
||||
SLAPrint.hpp
|
||||
Slicing.cpp
|
||||
Slicing.hpp
|
||||
SlicesToTriangleMesh.hpp
|
||||
SlicesToTriangleMesh.cpp
|
||||
SlicingAdaptive.cpp
|
||||
SlicingAdaptive.hpp
|
||||
SupportMaterial.cpp
|
||||
SupportMaterial.hpp
|
||||
TreeSupport.hpp
|
||||
TreeSupport.cpp
|
||||
MinimumSpanningTree.hpp
|
||||
MinimumSpanningTree.cpp
|
||||
Surface.cpp
|
||||
Surface.hpp
|
||||
SurfaceCollection.cpp
|
||||
SurfaceCollection.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
|
||||
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
|
||||
)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
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
|
||||
libslic3r_cgal
|
||||
${CMAKE_DL_LIBS}
|
||||
PNG::PNG
|
||||
ZLIB::ZLIB
|
||||
${OCCT_LIBS}
|
||||
)
|
||||
|
||||
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 ()
|
||||
101
src/libslic3r/Channel.hpp
Normal file
101
src/libslic3r/Channel.hpp
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#ifndef slic3r_Channel_hpp_
|
||||
#define slic3r_Channel_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
template<class T> class Channel
|
||||
{
|
||||
public:
|
||||
using UniqueLock = std::unique_lock<std::mutex>;
|
||||
|
||||
template<class Ptr> class Unlocker
|
||||
{
|
||||
public:
|
||||
Unlocker(UniqueLock lock) : m_lock(std::move(lock)) {}
|
||||
Unlocker(const Unlocker &other) noexcept : m_lock(std::move(other.m_lock)) {} // XXX: done beacuse of MSVC 2013 not supporting init of deleter by move
|
||||
Unlocker(Unlocker &&other) noexcept : m_lock(std::move(other.m_lock)) {}
|
||||
Unlocker& operator=(const Unlocker &other) = delete;
|
||||
Unlocker& operator=(Unlocker &&other) { m_lock = std::move(other.m_lock); }
|
||||
|
||||
void operator()(Ptr*) { m_lock.unlock(); }
|
||||
private:
|
||||
mutable UniqueLock m_lock; // XXX: mutable: see above
|
||||
};
|
||||
|
||||
using Queue = std::deque<T>;
|
||||
using LockedConstPtr = std::unique_ptr<const Queue, Unlocker<const Queue>>;
|
||||
using LockedPtr = std::unique_ptr<Queue, Unlocker<Queue>>;
|
||||
|
||||
Channel() {}
|
||||
~Channel() {}
|
||||
|
||||
void push(const T& item, bool silent = false)
|
||||
{
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
m_queue.push_back(item);
|
||||
}
|
||||
if (! silent) { m_condition.notify_one(); }
|
||||
}
|
||||
|
||||
void push(T &&item, bool silent = false)
|
||||
{
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
m_queue.push_back(std::forward<T>(item));
|
||||
}
|
||||
if (! silent) { m_condition.notify_one(); }
|
||||
}
|
||||
|
||||
T pop()
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
m_condition.wait(lock, [this]() { return !m_queue.empty(); });
|
||||
auto item = std::move(m_queue.front());
|
||||
m_queue.pop_front();
|
||||
return item;
|
||||
}
|
||||
|
||||
boost::optional<T> try_pop()
|
||||
{
|
||||
UniqueLock lock(m_mutex);
|
||||
if (m_queue.empty()) {
|
||||
return boost::none;
|
||||
} else {
|
||||
auto item = std::move(m_queue.front());
|
||||
m_queue.pop();
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
// Unlocked observer/hint. Thread unsafe! Keep in mind you need to re-verify the result after locking.
|
||||
size_t size_hint() const noexcept { return m_queue.size(); }
|
||||
|
||||
LockedConstPtr lock_read() const
|
||||
{
|
||||
return LockedConstPtr(&m_queue, Unlocker<const Queue>(UniqueLock(m_mutex)));
|
||||
}
|
||||
|
||||
LockedPtr lock_rw()
|
||||
{
|
||||
return LockedPtr(&m_queue, Unlocker<Queue>(UniqueLock(m_mutex)));
|
||||
}
|
||||
private:
|
||||
Queue m_queue;
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_Channel_hpp_
|
||||
513
src/libslic3r/Circle.cpp
Normal file
513
src/libslic3r/Circle.cpp
Normal file
|
|
@ -0,0 +1,513 @@
|
|||
#include "Circle.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
#include "Geometry.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//BBS: 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();
|
||||
|
||||
//BBS: 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;
|
||||
//BBS: 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;
|
||||
// BBS: 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;
|
||||
}
|
||||
|
||||
// BBS: 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)
|
||||
// BBS: 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;
|
||||
// BBS: skip the first and last points since they has fit perfectly.
|
||||
for (size_t index = 0; index < points.size() - 1; index++)
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
//BBS: 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;
|
||||
}
|
||||
|
||||
//BBS: 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;
|
||||
//BBS: [(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;
|
||||
|
||||
//BBS: 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;
|
||||
// BBS: skip the first and last points since they are on the circle
|
||||
for (int index = 1; index < points.size() - 1; index++)
|
||||
{
|
||||
//BBS: 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;
|
||||
//BBS: 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;
|
||||
}
|
||||
|
||||
//BBS: 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;
|
||||
//BBS: 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;
|
||||
}
|
||||
}
|
||||
|
||||
// BBS: 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;
|
||||
|
||||
// BBS: 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)
|
||||
{
|
||||
// BBS: 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.
|
||||
|
||||
//BBS: 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;
|
||||
//BBS: 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)
|
||||
{
|
||||
//BBS: 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;
|
||||
|
||||
//BBS: 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;
|
||||
|
||||
//BBS: First ensure the test point is within the arc
|
||||
if (test_arc.direction == ArcDirection::Arc_Dir_CCW)
|
||||
{
|
||||
//BBS: 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;
|
||||
}
|
||||
//BBS: check the angles are increasing
|
||||
if (previous_polar > polar_test) {
|
||||
if (!will_cross_zero)
|
||||
return false;
|
||||
|
||||
//BBS: 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;
|
||||
}
|
||||
//BBS: Now make sure the angles are decreasing
|
||||
if (previous_polar < polar_test)
|
||||
{
|
||||
if (!will_cross_zero)
|
||||
return false;
|
||||
//BBS: Allow the angle to cross zero once
|
||||
if (crossed_zero)
|
||||
return false;
|
||||
crossed_zero = true;
|
||||
}
|
||||
}
|
||||
|
||||
// BBS: 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;
|
||||
}
|
||||
//BBS: Ensure that all arcs that cross zero
|
||||
if (will_cross_zero != crossed_zero)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// BBS: 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;
|
||||
}
|
||||
|
||||
// BBS: 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();
|
||||
}
|
||||
|
||||
// BBS: new function to calculate arc length in X-Y plane
|
||||
float ArcSegment::calc_arc_length(Vec3f start_pos, Vec3f end_pos, Vec3f center_pos, bool is_ccw)
|
||||
{
|
||||
return calc_arc_radius(start_pos, center_pos) * calc_arc_radian(start_pos, end_pos, center_pos, is_ccw);
|
||||
}
|
||||
|
||||
}
|
||||
135
src/libslic3r/Circle.hpp
Normal file
135
src/libslic3r/Circle.hpp
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
#ifndef slic3r_Circle_hpp_
|
||||
#define slic3r_Circle_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "Line.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
constexpr double ZERO_TOLERANCE = 0.000005;
|
||||
|
||||
class Circle {
|
||||
public:
|
||||
Circle() {
|
||||
center = Point(0,0);
|
||||
radius = 0;
|
||||
}
|
||||
Circle(Point &p, double r) {
|
||||
center = p;
|
||||
radius = r;
|
||||
}
|
||||
Point center;
|
||||
double radius;
|
||||
|
||||
Point get_closest_point(const Point& input) {
|
||||
Vec2d v = (input - center).cast<double>().normalized();
|
||||
return (center + (v * radius).cast<coord_t>());
|
||||
}
|
||||
|
||||
static bool try_create_circle(const Point &p1, const Point &p2, const Point &p3, const double max_radius, Circle& new_circle);
|
||||
static bool try_create_circle(const Points& points, const double max_radius, const double tolerance, Circle& new_circle);
|
||||
double get_polar_radians(const Point& p1) const;
|
||||
bool is_over_deviation(const Points& points, const double tolerance);
|
||||
bool get_deviation_sum_squared(const Points& points, const double tolerance, double& sum_deviation);
|
||||
|
||||
//BBS: 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);
|
||||
// BBS: 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);
|
||||
// BBS: 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
|
||||
1262
src/libslic3r/ClipperUtils.cpp
Normal file
1262
src/libslic3r/ClipperUtils.cpp
Normal file
File diff suppressed because it is too large
Load diff
620
src/libslic3r/ClipperUtils.hpp
Normal file
620
src/libslic3r/ClipperUtils.hpp
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
#ifndef slic3r_ClipperUtils_hpp_
|
||||
#define slic3r_ClipperUtils_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "clipper.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Polygon.hpp"
|
||||
#include "Surface.hpp"
|
||||
|
||||
// import these wherever we're included
|
||||
using Slic3r::ClipperLib::jtMiter;
|
||||
using Slic3r::ClipperLib::jtRound;
|
||||
using Slic3r::ClipperLib::jtSquare;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
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.;
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
// 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);
|
||||
// BBS
|
||||
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); }
|
||||
|
||||
// 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);
|
||||
|
||||
// BBS
|
||||
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);
|
||||
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::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::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);
|
||||
|
||||
// BBS
|
||||
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::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);
|
||||
// BBS
|
||||
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::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::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::Polygons &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);
|
||||
|
||||
// BBS
|
||||
inline Slic3r::ExPolygons intersection_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 intersection_ex(subject_temp, clip, do_safety_offset);
|
||||
}
|
||||
|
||||
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 Slic3r::Polygons &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
|
||||
1634
src/libslic3r/Config.cpp
Normal file
1634
src/libslic3r/Config.cpp
Normal file
File diff suppressed because it is too large
Load diff
2346
src/libslic3r/Config.hpp
Normal file
2346
src/libslic3r/Config.hpp
Normal file
File diff suppressed because it is too large
Load diff
203
src/libslic3r/CurveAnalyzer.cpp
Normal file
203
src/libslic3r/CurveAnalyzer.cpp
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
#include "CurveAnalyzer.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
||||
static const int curvatures_sampling_number = 6;
|
||||
static const double curvatures_densify_width = 1; // mm
|
||||
static const double curvatures_sampling_width = 6; // mm
|
||||
static const double curvatures_angle_best = PI/6;
|
||||
static const double curvatures_angle_worst = 5*PI/6;
|
||||
|
||||
static const double curvatures_best = (curvatures_angle_best * 1000 / curvatures_sampling_width);
|
||||
static const double curvatures_worst = (curvatures_angle_worst * 1000 / curvatures_sampling_width);
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// This function is used to calculate curvature for paths.
|
||||
// Paths must be generated from a closed polygon.
|
||||
// Data in paths may be modify, and paths will be spilited and regenerated
|
||||
// arrording to different curve degree.
|
||||
void CurveAnalyzer::calculate_curvatures(ExtrusionPaths& paths, ECurveAnalyseMode mode)
|
||||
{
|
||||
Polygon polygon;
|
||||
std::vector<float> paths_length(paths.size(), 0.0);
|
||||
for (size_t i = 0; i < paths.size(); i++) {
|
||||
if (i == 0) {
|
||||
paths_length[i] = paths[i].polyline.length();
|
||||
}
|
||||
else {
|
||||
paths_length[i] = paths_length[i - 1] + paths[i].polyline.length();
|
||||
}
|
||||
polygon.points.insert(polygon.points.end(), paths[i].polyline.points.begin(), paths[i].polyline.points.end() - 1);
|
||||
}
|
||||
// 1 generate point series which is on the line of polygon, point distance along the polygon is smaller than 1mm
|
||||
polygon.densify(scale_(curvatures_densify_width));
|
||||
std::vector<float> polygon_length = polygon.parameter_by_length();
|
||||
|
||||
// 2 calculate angle of every segment
|
||||
size_t point_num = polygon.points.size();
|
||||
std::vector<float> angles(point_num, 0.f);
|
||||
for (size_t i = 0; i < point_num; i++) {
|
||||
size_t curr = i;
|
||||
size_t prev = (curr == 0) ? point_num - 1 : curr - 1;
|
||||
size_t next = (curr == point_num - 1) ? 0 : curr + 1;
|
||||
const Point v1 = polygon.points[curr] - polygon.points[prev];
|
||||
const Point v2 = polygon.points[next] - polygon.points[curr];
|
||||
int64_t dot = int64_t(v1(0)) * int64_t(v2(0)) + int64_t(v1(1)) * int64_t(v2(1));
|
||||
int64_t cross = int64_t(v1(0)) * int64_t(v2(1)) - int64_t(v1(1)) * int64_t(v2(0));
|
||||
if (mode == ECurveAnalyseMode::RelativeMode)
|
||||
cross = abs(cross);
|
||||
angles[curr] = float(atan2(double(cross), double(dot)));
|
||||
}
|
||||
|
||||
// 3 generate sum of angle and length of the adjacent segment for eveny point, range is approximately curvatures_sampling_width.
|
||||
// And then calculate the curvature
|
||||
std::vector<float> sum_angles(point_num, 0.f);
|
||||
std::vector<double> average_curvatures(point_num, 0.f);
|
||||
if (paths_length.back() < scale_(curvatures_sampling_width)) {
|
||||
// loop is too short, so the curvatures is max
|
||||
double temp = 1000.0 * 2.0 * PI / ((double)(paths_length.back()) * SCALING_FACTOR);
|
||||
for (size_t i = 0; i < point_num; i++) {
|
||||
average_curvatures[i] = temp;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (size_t i = 0; i < point_num; i++) {
|
||||
// right segment
|
||||
size_t j = i;
|
||||
float right_length = 0;
|
||||
while (right_length < scale_(curvatures_sampling_width / 2)) {
|
||||
int next_j = (j + 1 >= point_num) ? 0 : j + 1;
|
||||
sum_angles[i] += angles[j];
|
||||
right_length += (polygon.points[next_j] - polygon.points[j]).cast<float>().norm();
|
||||
j = next_j;
|
||||
}
|
||||
// left segment
|
||||
size_t k = i;
|
||||
float left_length = 0;
|
||||
while (left_length < scale_(curvatures_sampling_width / 2)) {
|
||||
size_t next_k = (k < 1) ? point_num - 1 : k - 1;
|
||||
sum_angles[i] += angles[k];
|
||||
left_length += (polygon.points[k] - polygon.points[next_k]).cast<float>().norm();
|
||||
k = next_k;
|
||||
}
|
||||
sum_angles[i] = sum_angles[i] - angles[i];
|
||||
average_curvatures[i] = (1000.0 * (double)abs(sum_angles[i]) / (double)curvatures_sampling_width);
|
||||
}
|
||||
}
|
||||
|
||||
// 4 calculate the degree of curve
|
||||
// For angle >= curvatures_angle_worst, we think it's enough to be worst. Should make the speed to be slowest.
|
||||
// For angle <= curvatures_angle_best, we thins it's enough to be best. Should make the speed to be fastest.
|
||||
// Use several steps [0 1 2...curvatures_sampling_number - 1] to describe the degree of curve. 0 is the flatest. curvatures_sampling_number - 1 is the sharpest
|
||||
std::vector<int> curvatures_norm(point_num, 0.f);
|
||||
std::vector<int> sampling_step(curvatures_sampling_number - 1, 0);
|
||||
for (size_t i = 0; i < curvatures_sampling_number - 1; i++) {
|
||||
sampling_step[i] = (2 * i + 1) * 50 / (curvatures_sampling_number - 1);
|
||||
}
|
||||
sampling_step[0] = 0;
|
||||
sampling_step[curvatures_sampling_number - 2] = 100;
|
||||
for (size_t i = 0; i < point_num; i++) {
|
||||
curvatures_norm[i] = (int)(100 * (average_curvatures[i] - curvatures_best) / (curvatures_worst - curvatures_best));
|
||||
if (curvatures_norm[i] >= 100)
|
||||
curvatures_norm[i] = curvatures_sampling_number - 1;
|
||||
else
|
||||
for (size_t j = 0; j < curvatures_sampling_number - 1; j++) {
|
||||
if (curvatures_norm[i] < sampling_step[j]) {
|
||||
curvatures_norm[i] = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<std::pair<std::pair<Point, int>, int>> curvature_list; // point, index, curve_degree
|
||||
int last_curvature_norm = -1;
|
||||
for (int i = 0; i < point_num; i++) {
|
||||
if (curvatures_norm[i] != last_curvature_norm) {
|
||||
last_curvature_norm = curvatures_norm[i];
|
||||
curvature_list.push_back(std::pair<std::pair<Point, int>, int>(std::pair<Point, int>(polygon.points[i], i), last_curvature_norm));
|
||||
}
|
||||
}
|
||||
curvature_list.push_back(std::pair<std::pair<Point, int>, int>(std::pair<Point, int>(polygon.points[0], point_num), curvatures_norm[0])); // the last point should be the first point
|
||||
|
||||
//5 split and modify the path according to the degree of curve
|
||||
if (curvature_list.size() == 2) { // all paths has same curva_degree
|
||||
for (size_t i = 0; i < paths.size(); i++) {
|
||||
paths[i].set_curve_degree(curvature_list[0].second);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ExtrusionPaths out;
|
||||
out.reserve(paths.size() + curvature_list.size() - 1);
|
||||
size_t j = 1;
|
||||
int current_curva_norm = curvature_list[0].second;
|
||||
for (size_t i = 0; i < paths.size() && j < curvature_list.size(); i++) {
|
||||
if (paths[i].last_point() == curvature_list[j].first.first) {
|
||||
paths[i].set_curve_degree(current_curva_norm);
|
||||
out.push_back(paths[i]);
|
||||
current_curva_norm = curvature_list[j].second;
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
else if (paths[i].first_point() == curvature_list[j].first.first) {
|
||||
if (paths[i].polyline.points.front() == paths[i].polyline.points.back()) {
|
||||
paths[i].set_curve_degree(current_curva_norm);
|
||||
out.push_back(paths[i]);
|
||||
current_curva_norm = curvature_list[j].second;
|
||||
j++;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
// should never happen
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (paths_length[i] <= polygon_length[curvature_list[j].first.second] ||
|
||||
paths[i].last_point() == curvature_list[j].first.first) {
|
||||
// save paths[i] directly
|
||||
paths[i].set_curve_degree(current_curva_norm);
|
||||
out.push_back(paths[i]);
|
||||
if (paths[i].last_point() == curvature_list[j].first.first) {
|
||||
current_curva_norm = curvature_list[j].second;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
//split paths[i]
|
||||
ExtrusionPath current_path = paths[i];
|
||||
while (j < curvature_list.size()) {
|
||||
Polyline left, right;
|
||||
current_path.polyline.split_at(curvature_list[j].first.first, &left, &right);
|
||||
ExtrusionPath left_path(left, current_path);
|
||||
left_path.set_curve_degree(current_curva_norm);
|
||||
out.push_back(left_path);
|
||||
ExtrusionPath right_path(right, current_path);
|
||||
current_path = right_path;
|
||||
|
||||
current_curva_norm = curvature_list[j].second;
|
||||
j++;
|
||||
if (j < curvature_list.size() &&
|
||||
(paths_length[i] <= polygon_length[curvature_list[j].first.second] ||
|
||||
paths[i].last_point() == curvature_list[j].first.first)) {
|
||||
current_path.set_curve_degree(current_curva_norm);
|
||||
out.push_back(current_path);
|
||||
if (current_path.last_point() == curvature_list[j].first.first) {
|
||||
current_curva_norm = curvature_list[j].second;
|
||||
j++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paths.clear();
|
||||
paths.reserve(out.size());
|
||||
for (int i = 0; i < out.size(); i++) {
|
||||
paths.push_back(out[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
27
src/libslic3r/CurveAnalyzer.hpp
Normal file
27
src/libslic3r/CurveAnalyzer.hpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef slic3r_CurvaAnalyzer_hpp_
|
||||
#define slic3r_CurvaAnalyzer_hpp_
|
||||
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class ECurveAnalyseMode : unsigned char
|
||||
{
|
||||
RelativeMode,
|
||||
AbsoluteMode,
|
||||
Count
|
||||
};
|
||||
|
||||
//BBS: CurvaAnalyzer, ansolutely new file
|
||||
class CurveAnalyzer {
|
||||
public:
|
||||
// This function is used to calculate curvature for paths.
|
||||
// Paths must be generated from a closed polygon.
|
||||
// Data in paths may be modify, and paths will be spilited and regenerated
|
||||
// arrording to different curve degree.
|
||||
void calculate_curvatures(ExtrusionPaths& paths, ECurveAnalyseMode mode = ECurveAnalyseMode::RelativeMode);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
76
src/libslic3r/CustomGCode.cpp
Normal file
76
src/libslic3r/CustomGCode.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#include "CustomGCode.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include "GCodeWriter.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace CustomGCode {
|
||||
|
||||
//BBS: 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
|
||||
96
src/libslic3r/CustomGCode.hpp
Normal file
96
src/libslic3r/CustomGCode.hpp
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#ifndef slic3r_CustomGCode_hpp_
|
||||
#define slic3r_CustomGCode_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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); }
|
||||
};
|
||||
|
||||
// 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.
|
||||
//BBS
|
||||
//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_ */
|
||||
1637
src/libslic3r/EdgeGrid.cpp
Normal file
1637
src/libslic3r/EdgeGrid.cpp
Normal file
File diff suppressed because it is too large
Load diff
421
src/libslic3r/EdgeGrid.hpp
Normal file
421
src/libslic3r/EdgeGrid.hpp
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
#ifndef slic3r_EdgeGrid_hpp_
|
||||
#define slic3r_EdgeGrid_hpp_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ExPolygonCollection.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);
|
||||
void create(const ExPolygonCollection &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; }
|
||||
bool intersect(const ExPolygonCollection &expolygons) { return intersect(expolygons.expolygons); }
|
||||
|
||||
// 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, ExPolygonCollection etc).
|
||||
std::vector<Contour> m_contours;
|
||||
|
||||
// Referencing a contour and a line segment of m_contours.
|
||||
std::vector<std::pair<size_t, size_t> > m_cell_data;
|
||||
|
||||
// Full grid of cells.
|
||||
std::vector<Cell> m_cells;
|
||||
|
||||
// Distance field derived from the edge grid, seed filled by the Danielsson chamfer metric.
|
||||
// May be empty.
|
||||
std::vector<float> m_signed_distance_field;
|
||||
};
|
||||
|
||||
// Debugging utility. Save the signed distance field.
|
||||
extern void save_png(const Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path, size_t scale = 1);
|
||||
|
||||
} // namespace EdgeGrid
|
||||
|
||||
// Find all pairs of intersectiong edges from the set of polygons.
|
||||
extern std::vector<std::pair<EdgeGrid::Grid::ContourEdge, EdgeGrid::Grid::ContourEdge>> intersecting_edges(const Polygons &polygons);
|
||||
|
||||
// Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG.
|
||||
extern void export_intersections_to_svg(const std::string &filename, const Polygons &polygons);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_EdgeGrid_hpp_ */
|
||||
646
src/libslic3r/ElephantFootCompensation.cpp
Normal file
646
src/libslic3r/ElephantFootCompensation.cpp
Normal file
|
|
@ -0,0 +1,646 @@
|
|||
#include "clipper/clipper_z.hpp"
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "ElephantFootCompensation.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
||||
// #define CONTOUR_DISTANCE_DEBUG_SVG
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct ResampledPoint {
|
||||
ResampledPoint(size_t idx_src, bool interpolated, double curve_parameter) : idx_src(idx_src), interpolated(interpolated), curve_parameter(curve_parameter) {}
|
||||
|
||||
size_t idx_src;
|
||||
// Is this point interpolated or initial?
|
||||
bool interpolated;
|
||||
// Euclidean distance along the curve from the 0th point.
|
||||
double curve_parameter;
|
||||
};
|
||||
|
||||
// Distance calculated using SDF (Shape Diameter Function).
|
||||
// The distance is calculated by casting a fan of rays and measuring the intersection distance.
|
||||
// Thus the calculation is relatively slow. For the Elephant foot compensation purpose, this distance metric does not avoid
|
||||
// pinching off small pieces of a contour, thus this function has been superseded by contour_distance2().
|
||||
std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx_contour, const Slic3r::Points &contour, const std::vector<ResampledPoint> &resampled_point_parameters, double search_radius)
|
||||
{
|
||||
assert(! contour.empty());
|
||||
assert(contour.size() >= 2);
|
||||
|
||||
std::vector<float> out;
|
||||
|
||||
if (contour.size() > 2)
|
||||
{
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
static int iRun = 0;
|
||||
++ iRun;
|
||||
BoundingBox bbox = get_extents(contour);
|
||||
bbox.merge(grid.bbox());
|
||||
ExPolygon expoly_grid;
|
||||
expoly_grid.contour = Polygon(*grid.contours().front());
|
||||
for (size_t i = 1; i < grid.contours().size(); ++ i)
|
||||
expoly_grid.holes.emplace_back(Polygon(*grid.contours()[i]));
|
||||
#endif
|
||||
struct Visitor {
|
||||
Visitor(const EdgeGrid::Grid &grid, const size_t idx_contour, const std::vector<ResampledPoint> &resampled_point_parameters, double dist_same_contour_reject) :
|
||||
grid(grid), idx_contour(idx_contour), resampled_point_parameters(resampled_point_parameters), dist_same_contour_reject(dist_same_contour_reject) {}
|
||||
|
||||
void init(const size_t aidx_point_start, const Point &apt_start, Vec2d dir, const double radius) {
|
||||
this->idx_point_start = aidx_point_start;
|
||||
this->pt = apt_start.cast<double>() + SCALED_EPSILON * dir;
|
||||
dir *= radius;
|
||||
this->pt_start = this->pt.cast<coord_t>();
|
||||
// Trim the vector by the grid's bounding box.
|
||||
const BoundingBox &bbox = this->grid.bbox();
|
||||
double t = 1.;
|
||||
for (size_t axis = 0; axis < 2; ++ axis) {
|
||||
double dx = std::abs(dir(axis));
|
||||
if (dx >= EPSILON) {
|
||||
double tedge = (dir(axis) > 0) ? (double(bbox.max(axis)) - SCALED_EPSILON - this->pt(axis)) : (this->pt(axis) - double(bbox.min(axis)) - SCALED_EPSILON);
|
||||
if (tedge < dx)
|
||||
t = std::min(t, tedge / dx);
|
||||
}
|
||||
}
|
||||
this->dir = dir;
|
||||
if (t < 1.)
|
||||
dir *= t;
|
||||
this->pt_end = (this->pt + dir).cast<coord_t>();
|
||||
this->t_min = 1.;
|
||||
assert(this->grid.bbox().contains(this->pt_start) && this->grid.bbox().contains(this->pt_end));
|
||||
}
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix) {
|
||||
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||
auto cell_data_range = this->grid.cell_data_range(iy, ix);
|
||||
bool valid = true;
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
|
||||
// End points of the line segment and their vector.
|
||||
auto segment = this->grid.segment(*it_contour_and_segment);
|
||||
if (Geometry::segments_intersect(segment.first, segment.second, this->pt_start, this->pt_end)) {
|
||||
// The two segments intersect. Calculate the intersection.
|
||||
Vec2d pt2 = segment.first.cast<double>();
|
||||
Vec2d dir2 = segment.second.cast<double>() - pt2;
|
||||
Vec2d vptpt2 = pt - pt2;
|
||||
double denom = dir(0) * dir2(1) - dir2(0) * dir(1);
|
||||
|
||||
if (std::abs(denom) >= EPSILON) {
|
||||
double t = cross2(dir2, vptpt2) / denom;
|
||||
assert(t > - EPSILON && t < 1. + EPSILON);
|
||||
bool this_valid = true;
|
||||
if (it_contour_and_segment->first == idx_contour) {
|
||||
// The intersected segment originates from the same contour as the starting point.
|
||||
// Reject the intersection if it is close to the starting point.
|
||||
// Find the start and end points of this segment
|
||||
double param_lo = resampled_point_parameters[idx_point_start].curve_parameter;
|
||||
double param_hi;
|
||||
double param_end = resampled_point_parameters.back().curve_parameter;
|
||||
{
|
||||
const EdgeGrid::Contour &contour = grid.contours()[it_contour_and_segment->first];
|
||||
size_t ipt = it_contour_and_segment->second;
|
||||
ResampledPoint key(ipt, false, 0.);
|
||||
auto lower = [](const ResampledPoint& l, const ResampledPoint r) { return l.idx_src < r.idx_src || (l.idx_src == r.idx_src && int(l.interpolated) > int(r.interpolated)); };
|
||||
auto it = std::lower_bound(resampled_point_parameters.begin(), resampled_point_parameters.end(), key, lower);
|
||||
assert(it != resampled_point_parameters.end() && it->idx_src == ipt && ! it->interpolated);
|
||||
double t2 = cross2(dir, vptpt2) / denom;
|
||||
assert(t2 > - EPSILON && t2 < 1. + EPSILON);
|
||||
if (contour.begin() + (++ ipt) == contour.end())
|
||||
param_hi = t2 * dir2.norm();
|
||||
else
|
||||
param_hi = it->curve_parameter + t2 * dir2.norm();
|
||||
}
|
||||
if (param_lo > param_hi)
|
||||
std::swap(param_lo, param_hi);
|
||||
assert(param_lo >= 0. && param_lo <= param_end);
|
||||
assert(param_hi >= 0. && param_hi <= param_end);
|
||||
this_valid = param_hi > param_lo + dist_same_contour_reject && param_hi - param_end < param_lo - dist_same_contour_reject;
|
||||
}
|
||||
if (t < this->t_min) {
|
||||
this->t_min = t;
|
||||
valid = this_valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! valid)
|
||||
this->t_min = 1.;
|
||||
}
|
||||
// Continue traversing the grid along the edge.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid &grid;
|
||||
const size_t idx_contour;
|
||||
const std::vector<ResampledPoint> &resampled_point_parameters;
|
||||
const double dist_same_contour_reject;
|
||||
|
||||
size_t idx_point_start;
|
||||
Point pt_start;
|
||||
Point pt_end;
|
||||
Vec2d pt;
|
||||
Vec2d dir;
|
||||
// Minium parameter along the vector (pt_end - pt_start).
|
||||
double t_min;
|
||||
} visitor(grid, idx_contour, resampled_point_parameters, search_radius);
|
||||
|
||||
const Point *pt_this = &contour.back();
|
||||
size_t idx_pt_this = contour.size() - 1;
|
||||
const Point *pt_prev = pt_this - 1;
|
||||
// perpenduclar vector
|
||||
auto perp = [](const Vec2d& v) -> Vec2d { return Vec2d(v.y(), -v.x()); };
|
||||
Vec2d vprev = (*pt_this - *pt_prev).cast<double>().normalized();
|
||||
out.reserve(contour.size() + 1);
|
||||
for (const Point &pt_next : contour) {
|
||||
Vec2d vnext = (pt_next - *pt_this).cast<double>().normalized();
|
||||
Vec2d dir = - (perp(vprev) + perp(vnext)).normalized();
|
||||
Vec2d dir_perp = perp(dir);
|
||||
double cross = cross2(vprev, vnext);
|
||||
double dot = vprev.dot(vnext);
|
||||
double a = (cross < 0 || dot > 0.5) ? (M_PI / 3.) : (0.48 * acos(std::min(1., - dot)));
|
||||
// Throw rays, collect distances.
|
||||
std::vector<double> distances;
|
||||
int num_rays = 15;
|
||||
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
SVG svg(debug_out_path("contour_distance_raycasted-%d-%d.svg", iRun, &pt_next - contour.data()).c_str(), bbox);
|
||||
svg.draw(expoly_grid);
|
||||
svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
|
||||
svg.draw(*pt_this, "red", coord_t(scale_(0.1)));
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
|
||||
for (int i = - num_rays + 1; i < num_rays; ++ i) {
|
||||
double angle = a * i / (int)num_rays;
|
||||
double c = cos(angle);
|
||||
double s = sin(angle);
|
||||
Vec2d v = c * dir + s * dir_perp;
|
||||
visitor.init(idx_pt_this, *pt_this, v, search_radius);
|
||||
grid.visit_cells_intersecting_line(visitor.pt_start, visitor.pt_end, visitor);
|
||||
distances.emplace_back(visitor.t_min);
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
svg.draw(Line(visitor.pt_start, visitor.pt_end), "yellow", scale_(0.01));
|
||||
if (visitor.t_min < 1.) {
|
||||
Vec2d pt = visitor.pt + visitor.dir * visitor.t_min;
|
||||
svg.draw(Point(pt), "red", coord_t(scale_(0.1)));
|
||||
}
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
}
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
svg.Close();
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
std::sort(distances.begin(), distances.end());
|
||||
#if 0
|
||||
double median = distances[distances.size() / 2];
|
||||
double standard_deviation = 0;
|
||||
for (double d : distances)
|
||||
standard_deviation += (d - median) * (d - median);
|
||||
standard_deviation = sqrt(standard_deviation / (distances.size() - 1));
|
||||
double avg = 0;
|
||||
size_t cnt = 0;
|
||||
for (double d : distances)
|
||||
if (d > median - standard_deviation - EPSILON && d < median + standard_deviation + EPSILON) {
|
||||
avg += d;
|
||||
++ cnt;
|
||||
}
|
||||
avg /= double(cnt);
|
||||
out.emplace_back(float(avg * search_radius));
|
||||
#else
|
||||
out.emplace_back(float(distances.front() * search_radius));
|
||||
#endif
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
printf("contour_distance_raycasted-%d-%d.svg - distance %lf\n", iRun, int(&pt_next - contour.data()), unscale<double>(out.back()));
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
pt_this = &pt_next;
|
||||
idx_pt_this = &pt_next - contour.data();
|
||||
vprev = vnext;
|
||||
}
|
||||
// Rotate the vector by one item.
|
||||
out.emplace_back(out.front());
|
||||
out.erase(out.begin());
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Contour distance by measuring the closest point of an ExPolygon stored inside the EdgeGrid, while filtering out points of the same contour
|
||||
// at concave regions, or convex regions with low curvature (curvature is estimated as a ratio between contour length and chordal distance crossing the contour ends).
|
||||
std::vector<float> contour_distance2(const EdgeGrid::Grid &grid, const size_t idx_contour, const Slic3r::Points &contour, const std::vector<ResampledPoint> &resampled_point_parameters, double compensation, double search_radius)
|
||||
{
|
||||
assert(! contour.empty());
|
||||
assert(contour.size() >= 2);
|
||||
|
||||
std::vector<float> out;
|
||||
|
||||
if (contour.size() > 2)
|
||||
{
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
static int iRun = 0;
|
||||
++ iRun;
|
||||
BoundingBox bbox = get_extents(contour);
|
||||
bbox.merge(grid.bbox());
|
||||
ExPolygon expoly_grid;
|
||||
expoly_grid.contour = Polygon(*grid.contours().front());
|
||||
for (size_t i = 1; i < grid.contours().size(); ++ i)
|
||||
expoly_grid.holes.emplace_back(Polygon(*grid.contours()[i]));
|
||||
#endif
|
||||
struct Visitor {
|
||||
Visitor(const EdgeGrid::Grid &grid, const size_t idx_contour, const std::vector<ResampledPoint> &resampled_point_parameters, double dist_same_contour_accept, double dist_same_contour_reject) :
|
||||
grid(grid), idx_contour(idx_contour), contour(grid.contours()[idx_contour]), resampled_point_parameters(resampled_point_parameters), dist_same_contour_accept(dist_same_contour_accept), dist_same_contour_reject(dist_same_contour_reject) {}
|
||||
|
||||
void init(const Points &contour, const Point &apoint) {
|
||||
this->idx_point = &apoint - contour.data();
|
||||
this->point = apoint;
|
||||
this->found = false;
|
||||
this->dir_inside = this->dir_inside_at_point(contour, this->idx_point);
|
||||
this->distance = std::numeric_limits<double>::max();
|
||||
}
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix) {
|
||||
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||
auto cell_data_range = this->grid.cell_data_range(iy, ix);
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
|
||||
// End points of the line segment and their vector.
|
||||
std::pair<const Point&, const Point&> segment = this->grid.segment(*it_contour_and_segment);
|
||||
const Vec2d v = (segment.second - segment.first).cast<double>();
|
||||
const Vec2d va = (this->point - segment.first).cast<double>();
|
||||
const double l2 = v.squaredNorm(); // avoid a sqrt
|
||||
const double t = (l2 == 0.0) ? 0. : std::clamp(va.dot(v) / l2, 0., 1.);
|
||||
// Closest point from this->point to the segment.
|
||||
const Vec2d foot = segment.first.cast<double>() + t * v;
|
||||
const Vec2d bisector = foot - this->point.cast<double>();
|
||||
const double dist = bisector.norm();
|
||||
if ((! this->found || dist < this->distance) && this->dir_inside.dot(bisector) > 0) {
|
||||
bool accept = true;
|
||||
if (it_contour_and_segment->first == idx_contour) {
|
||||
// Complex case: The closest segment originates from the same contour as the starting point.
|
||||
// Reject the closest point if its distance along the contour is reasonable compared to the current contour bisector (this->pt, foot).
|
||||
double param_lo = resampled_point_parameters[this->idx_point].curve_parameter;
|
||||
double param_hi;
|
||||
double param_end = resampled_point_parameters.back().curve_parameter;
|
||||
const EdgeGrid::Contour &contour = grid.contours()[it_contour_and_segment->first];
|
||||
const size_t ipt = it_contour_and_segment->second;
|
||||
{
|
||||
ResampledPoint key(ipt, false, 0.);
|
||||
auto lower = [](const ResampledPoint& l, const ResampledPoint r) { return l.idx_src < r.idx_src || (l.idx_src == r.idx_src && int(l.interpolated) > int(r.interpolated)); };
|
||||
auto it = std::lower_bound(resampled_point_parameters.begin(), resampled_point_parameters.end(), key, lower);
|
||||
assert(it != resampled_point_parameters.end() && it->idx_src == ipt && ! it->interpolated);
|
||||
param_hi = t * sqrt(l2);
|
||||
if (contour.begin() + ipt + 1 < contour.end())
|
||||
param_hi += it->curve_parameter;
|
||||
}
|
||||
if (param_lo > param_hi)
|
||||
std::swap(param_lo, param_hi);
|
||||
assert(param_lo > - SCALED_EPSILON && param_lo <= param_end + SCALED_EPSILON);
|
||||
assert(param_hi > - SCALED_EPSILON && param_hi <= param_end + SCALED_EPSILON);
|
||||
double dist_along_contour = std::min(param_hi - param_lo, param_lo + param_end - param_hi);
|
||||
if (dist_along_contour < dist_same_contour_accept)
|
||||
accept = false;
|
||||
else if (dist < dist_same_contour_reject + SCALED_EPSILON) {
|
||||
// this->point is close to foot. This point will only be accepted if the path along the contour is significantly
|
||||
// longer than the bisector. That is, the path shall not bulge away from the bisector too much.
|
||||
// Bulge is estimated by 0.6 of the circle circumference drawn around the bisector.
|
||||
// Test whether the contour is convex or concave.
|
||||
bool inside =
|
||||
(t == 0.) ? this->inside_corner(contour, ipt, this->point) :
|
||||
(t == 1.) ? this->inside_corner(contour, contour.segment_idx_next(ipt), this->point) :
|
||||
this->left_of_segment(contour, ipt, this->point);
|
||||
accept = inside && dist_along_contour > 0.6 * M_PI * dist;
|
||||
}
|
||||
}
|
||||
if (accept && (! this->found || dist < this->distance)) {
|
||||
// Simple case: Just measure the shortest distance.
|
||||
this->distance = dist;
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
this->closest_point = foot.cast<coord_t>();
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
this->found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Continue traversing the grid.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid &grid;
|
||||
const size_t idx_contour;
|
||||
const EdgeGrid::Contour &contour;
|
||||
const std::vector<ResampledPoint> &resampled_point_parameters;
|
||||
const double dist_same_contour_accept;
|
||||
const double dist_same_contour_reject;
|
||||
|
||||
size_t idx_point;
|
||||
Point point;
|
||||
// Direction inside the contour from idx_point, not normalized.
|
||||
Vec2d dir_inside;
|
||||
bool found;
|
||||
double distance;
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
Point closest_point;
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
|
||||
private:
|
||||
static Vec2d dir_inside_at_point(const Points &contour, size_t i) {
|
||||
size_t iprev = prev_idx_modulo(i, contour);
|
||||
size_t inext = next_idx_modulo(i, contour);
|
||||
Vec2d v1 = (contour[i] - contour[iprev]).cast<double>();
|
||||
Vec2d v2 = (contour[inext] - contour[i]).cast<double>();
|
||||
return Vec2d(- v1.y() - v2.y(), v1.x() + v2.x());
|
||||
}
|
||||
static Vec2d dir_inside_at_segment(const Points& contour, size_t i) {
|
||||
size_t inext = next_idx_modulo(i, contour);
|
||||
Vec2d v = (contour[inext] - contour[i]).cast<double>();
|
||||
return Vec2d(- v.y(), v.x());
|
||||
}
|
||||
|
||||
static bool inside_corner(const EdgeGrid::Contour &contour, size_t i, const Point &pt_oposite)
|
||||
{
|
||||
const Vec2d pt = pt_oposite.cast<double>();
|
||||
const Point &pt_prev = contour.segment_prev(i);
|
||||
const Point &pt_this = contour.segment_start(i);
|
||||
const Point &pt_next = contour.segment_end(i);
|
||||
Vec2d v1 = (pt_this - pt_prev).cast<double>();
|
||||
Vec2d v2 = (pt_next - pt_this).cast<double>();
|
||||
bool left_of_v1 = cross2(v1, pt - pt_prev.cast<double>()) > 0.;
|
||||
bool left_of_v2 = cross2(v2, pt - pt_this.cast<double>()) > 0.;
|
||||
return cross2(v1, v2) > 0 ? left_of_v1 && left_of_v2 : // convex corner
|
||||
left_of_v1 || left_of_v2; // concave corner
|
||||
}
|
||||
|
||||
static bool left_of_segment(const EdgeGrid::Contour &contour, size_t i, const Point &pt_oposite)
|
||||
{
|
||||
const Vec2d pt = pt_oposite.cast<double>();
|
||||
const Point &pt_this = contour.segment_start(i);
|
||||
const Point &pt_next = contour.segment_end(i);
|
||||
Vec2d v = (pt_next - pt_this).cast<double>();
|
||||
return cross2(v, pt - pt_this.cast<double>()) > 0.;
|
||||
}
|
||||
} visitor(grid, idx_contour, resampled_point_parameters, 0.5 * compensation * M_PI, search_radius);
|
||||
|
||||
out.reserve(contour.size());
|
||||
Point radius_vector(search_radius, search_radius);
|
||||
for (const Point &pt : contour) {
|
||||
visitor.init(contour, pt);
|
||||
grid.visit_cells_intersecting_box(BoundingBox(pt - radius_vector, pt + radius_vector), visitor);
|
||||
out.emplace_back(float(visitor.found ? std::min(visitor.distance, search_radius) : search_radius));
|
||||
|
||||
#if 0
|
||||
//#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
if (out.back() < search_radius) {
|
||||
SVG svg(debug_out_path("contour_distance_filtered-%d-%d.svg", iRun, int(&pt - contour.data())).c_str(), bbox);
|
||||
svg.draw(expoly_grid);
|
||||
svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
|
||||
svg.draw(pt, "green", coord_t(scale_(0.1)));
|
||||
svg.draw(visitor.closest_point, "red", coord_t(scale_(0.1)));
|
||||
printf("contour_distance_filtered-%d-%d.svg - distance %lf\n", iRun, int(&pt - contour.data()), unscale<double>(out.back()));
|
||||
}
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
}
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
if (out.back() < search_radius) {
|
||||
SVG svg(debug_out_path("contour_distance_filtered-final-%d.svg", iRun).c_str(), bbox);
|
||||
svg.draw(expoly_grid);
|
||||
svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
|
||||
for (size_t i = 0; i < contour.size(); ++ i)
|
||||
svg.draw(contour[i], out[i] < float(search_radius - SCALED_EPSILON) ? "red" : "green", coord_t(scale_(0.1)));
|
||||
}
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Points resample_polygon(const Points &contour, double dist, std::vector<ResampledPoint> &resampled_point_parameters)
|
||||
{
|
||||
Points out;
|
||||
out.reserve(contour.size());
|
||||
resampled_point_parameters.reserve(contour.size());
|
||||
if (contour.size() > 2) {
|
||||
Vec2d pt_prev = contour.back().cast<double>();
|
||||
for (const Point &pt : contour) {
|
||||
size_t idx_this = &pt - contour.data();
|
||||
const Vec2d pt_this = pt.cast<double>();
|
||||
const Vec2d v = pt_this - pt_prev;
|
||||
const double l = v.norm();
|
||||
const size_t n = size_t(ceil(l / dist));
|
||||
const double l_step = l / n;
|
||||
for (size_t i = 1; i < n; ++ i) {
|
||||
double interpolation_parameter = double(i) / n;
|
||||
Vec2d new_pt = pt_prev + v * interpolation_parameter;
|
||||
out.emplace_back(new_pt.cast<coord_t>());
|
||||
resampled_point_parameters.emplace_back(idx_this, true, l_step);
|
||||
}
|
||||
out.emplace_back(pt);
|
||||
resampled_point_parameters.emplace_back(idx_this, false, l_step);
|
||||
pt_prev = pt_this;
|
||||
}
|
||||
for (size_t i = 1; i < resampled_point_parameters.size(); ++i)
|
||||
resampled_point_parameters[i].curve_parameter += resampled_point_parameters[i - 1].curve_parameter;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static inline void smooth_compensation(std::vector<float> &compensation, float strength, size_t num_iterations)
|
||||
{
|
||||
std::vector<float> out(compensation);
|
||||
for (size_t iter = 0; iter < num_iterations; ++ iter) {
|
||||
for (size_t i = 0; i < compensation.size(); ++ i) {
|
||||
float prev = prev_value_modulo(i, compensation);
|
||||
float next = next_value_modulo(i, compensation);
|
||||
float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next);
|
||||
// Compensations are negative. Only apply the laplacian if it leads to lower compensation.
|
||||
out[i] = std::max(laplacian, compensation[i]);
|
||||
}
|
||||
out.swap(compensation);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void smooth_compensation_banded(const Points &contour, float band, std::vector<float> &compensation, float strength, size_t num_iterations)
|
||||
{
|
||||
assert(contour.size() == compensation.size());
|
||||
assert(contour.size() > 2);
|
||||
std::vector<float> out(compensation);
|
||||
float dist_min2 = band * band;
|
||||
static constexpr bool use_min = false;
|
||||
for (size_t iter = 0; iter < num_iterations; ++ iter) {
|
||||
for (int i = 0; i < int(compensation.size()); ++ i) {
|
||||
const Vec2f pthis = contour[i].cast<float>();
|
||||
|
||||
int j = prev_idx_modulo(i, contour);
|
||||
Vec2f pprev = contour[j].cast<float>();
|
||||
float prev = compensation[j];
|
||||
float l2 = (pthis - pprev).squaredNorm();
|
||||
if (l2 < dist_min2) {
|
||||
float l = sqrt(l2);
|
||||
int jprev = std::exchange(j, prev_idx_modulo(j, contour));
|
||||
while (j != i) {
|
||||
const Vec2f pp = contour[j].cast<float>();
|
||||
const float lthis = (pp - pprev).norm();
|
||||
const float lnext = l + lthis;
|
||||
if (lnext > band) {
|
||||
// Interpolate the compensation value.
|
||||
prev = use_min ?
|
||||
std::min(prev, lerp(compensation[jprev], compensation[j], (band - l) / lthis)) :
|
||||
lerp(compensation[jprev], compensation[j], (band - l) / lthis);
|
||||
break;
|
||||
}
|
||||
prev = use_min ? std::min(prev, compensation[j]) : compensation[j];
|
||||
pprev = pp;
|
||||
l = lnext;
|
||||
jprev = std::exchange(j, prev_idx_modulo(j, contour));
|
||||
}
|
||||
}
|
||||
|
||||
j = next_idx_modulo(i, contour);
|
||||
pprev = contour[j].cast<float>();
|
||||
float next = compensation[j];
|
||||
l2 = (pprev - pthis).squaredNorm();
|
||||
if (l2 < dist_min2) {
|
||||
float l = sqrt(l2);
|
||||
int jprev = std::exchange(j, next_idx_modulo(j, contour));
|
||||
while (j != i) {
|
||||
const Vec2f pp = contour[j].cast<float>();
|
||||
const float lthis = (pp - pprev).norm();
|
||||
const float lnext = l + lthis;
|
||||
if (lnext > band) {
|
||||
// Interpolate the compensation value.
|
||||
next = use_min ?
|
||||
std::min(next, lerp(compensation[jprev], compensation[j], (band - l) / lthis)) :
|
||||
lerp(compensation[jprev], compensation[j], (band - l) / lthis);
|
||||
break;
|
||||
}
|
||||
next = use_min ? std::min(next, compensation[j]) : compensation[j];
|
||||
pprev = pp;
|
||||
l = lnext;
|
||||
jprev = std::exchange(j, next_idx_modulo(j, contour));
|
||||
}
|
||||
}
|
||||
|
||||
float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next);
|
||||
// Compensations are negative. Only apply the laplacian if it leads to lower compensation.
|
||||
out[i] = std::max(laplacian, compensation[i]);
|
||||
}
|
||||
out.swap(compensation);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
static bool validate_expoly_orientation(const ExPolygon &expoly)
|
||||
{
|
||||
bool valid = expoly.contour.is_counter_clockwise();
|
||||
for (auto &h : expoly.holes)
|
||||
valid &= h.is_clockwise();
|
||||
return valid;
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, double min_contour_width, const double compensation)
|
||||
{
|
||||
assert(validate_expoly_orientation(input_expoly));
|
||||
|
||||
double scaled_compensation = scale_(compensation);
|
||||
min_contour_width = scale_(min_contour_width);
|
||||
double min_contour_width_compensated = min_contour_width + 2. * scaled_compensation;
|
||||
// Make the search radius a bit larger for the averaging in contour_distance over a fan of rays to work.
|
||||
double search_radius = min_contour_width_compensated + min_contour_width * 0.5;
|
||||
|
||||
BoundingBox bbox = get_extents(input_expoly.contour);
|
||||
Point bbox_size = bbox.size();
|
||||
ExPolygon out;
|
||||
if (bbox_size.x() < min_contour_width_compensated + SCALED_EPSILON ||
|
||||
bbox_size.y() < min_contour_width_compensated + SCALED_EPSILON ||
|
||||
input_expoly.area() < min_contour_width_compensated * min_contour_width_compensated * 5.)
|
||||
{
|
||||
// The contour is tiny. Don't correct it.
|
||||
out = input_expoly;
|
||||
}
|
||||
else
|
||||
{
|
||||
EdgeGrid::Grid grid;
|
||||
ExPolygon simplified = input_expoly.simplify(SCALED_EPSILON).front();
|
||||
assert(validate_expoly_orientation(simplified));
|
||||
BoundingBox bbox = get_extents(simplified.contour);
|
||||
bbox.offset(SCALED_EPSILON);
|
||||
grid.set_bbox(bbox);
|
||||
grid.create(simplified, coord_t(0.7 * search_radius));
|
||||
std::vector<std::vector<float>> deltas;
|
||||
deltas.reserve(simplified.holes.size() + 1);
|
||||
ExPolygon resampled(simplified);
|
||||
double resample_interval = scale_(0.5);
|
||||
for (size_t idx_contour = 0; idx_contour <= simplified.holes.size(); ++ idx_contour) {
|
||||
Polygon &poly = (idx_contour == 0) ? resampled.contour : resampled.holes[idx_contour - 1];
|
||||
std::vector<ResampledPoint> resampled_point_parameters;
|
||||
poly.points = resample_polygon(poly.points, resample_interval, resampled_point_parameters);
|
||||
assert(poly.is_counter_clockwise() == (idx_contour == 0));
|
||||
std::vector<float> dists = contour_distance2(grid, idx_contour, poly.points, resampled_point_parameters, scaled_compensation, search_radius);
|
||||
for (float &d : dists) {
|
||||
// printf("Point %d, Distance: %lf\n", int(&d - dists.data()), unscale<double>(d));
|
||||
// Convert contour width to available compensation distance.
|
||||
if (d < min_contour_width)
|
||||
d = 0.f;
|
||||
else if (d > min_contour_width_compensated)
|
||||
d = - float(scaled_compensation);
|
||||
else
|
||||
d = - (d - float(min_contour_width)) / 2.f;
|
||||
assert(d >= - float(scaled_compensation) && d <= 0.f);
|
||||
}
|
||||
// smooth_compensation(dists, 0.4f, 10);
|
||||
smooth_compensation_banded(poly.points, float(0.8 * resample_interval), dists, 0.3f, 3);
|
||||
deltas.emplace_back(dists);
|
||||
}
|
||||
|
||||
ExPolygons out_vec = variable_offset_inner_ex(resampled, deltas, 2.);
|
||||
if (out_vec.size() == 1)
|
||||
out = std::move(out_vec.front());
|
||||
else {
|
||||
// Something went wrong, don't compensate.
|
||||
out = input_expoly;
|
||||
#ifdef TESTS_EXPORT_SVGS
|
||||
if (out_vec.size() > 1) {
|
||||
static int iRun = 0;
|
||||
SVG::export_expolygons(debug_out_path("elephant_foot_compensation-many_contours-%d.svg", iRun ++).c_str(),
|
||||
{ { { input_expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
|
||||
{ { out_vec }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
|
||||
}
|
||||
#endif /* TESTS_EXPORT_SVGS */
|
||||
assert(out_vec.size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
assert(validate_expoly_orientation(out));
|
||||
return out;
|
||||
}
|
||||
|
||||
ExPolygon elephant_foot_compensation(const ExPolygon &input, const Flow &external_perimeter_flow, const double compensation)
|
||||
{
|
||||
// The contour shall be wide enough to apply the external perimeter plus compensation on both sides.
|
||||
double min_contour_width = double(external_perimeter_flow.width() + external_perimeter_flow.spacing());
|
||||
return elephant_foot_compensation(input, min_contour_width, compensation);
|
||||
}
|
||||
|
||||
ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation)
|
||||
{
|
||||
ExPolygons out;
|
||||
out.reserve(input.size());
|
||||
for (const ExPolygon &expoly : input)
|
||||
out.emplace_back(elephant_foot_compensation(expoly, external_perimeter_flow, compensation));
|
||||
return out;
|
||||
}
|
||||
|
||||
ExPolygons elephant_foot_compensation(const ExPolygons &input, double min_contour_width, const double compensation)
|
||||
{
|
||||
ExPolygons out;
|
||||
out.reserve(input.size());
|
||||
for (const ExPolygon &expoly : input)
|
||||
out.emplace_back(elephant_foot_compensation(expoly, min_contour_width, compensation));
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
19
src/libslic3r/ElephantFootCompensation.hpp
Normal file
19
src/libslic3r/ElephantFootCompensation.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef slic3r_ElephantFootCompensation_hpp_
|
||||
#define slic3r_ElephantFootCompensation_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ExPolygon.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Flow;
|
||||
|
||||
ExPolygon elephant_foot_compensation(const ExPolygon &input, double min_countour_width, const double compensation);
|
||||
ExPolygons elephant_foot_compensation(const ExPolygons &input, double min_countour_width, const double compensation);
|
||||
ExPolygon elephant_foot_compensation(const ExPolygon &input, const Flow &external_perimeter_flow, const double compensation);
|
||||
ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation);
|
||||
|
||||
} // Slic3r
|
||||
|
||||
#endif /* slic3r_ElephantFootCompensation_hpp_ */
|
||||
468
src/libslic3r/ExPolygon.cpp
Normal file
468
src/libslic3r/ExPolygon.cpp
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
#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 {
|
||||
|
||||
ExPolygon::operator Points() const
|
||||
{
|
||||
Points points;
|
||||
Polygons pp = *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;
|
||||
}
|
||||
|
||||
ExPolygon::operator Polygons() const
|
||||
{
|
||||
return to_polygons(*this);
|
||||
}
|
||||
|
||||
ExPolygon::operator Polylines() const
|
||||
{
|
||||
return to_polylines(*this);
|
||||
}
|
||||
|
||||
void ExPolygon::scale(double factor)
|
||||
{
|
||||
contour.scale(factor);
|
||||
for (Polygon &hole : holes)
|
||||
hole.scale(factor);
|
||||
}
|
||||
|
||||
void ExPolygon::translate(const Point &p)
|
||||
{
|
||||
contour.translate(p);
|
||||
for (Polygon &hole : holes)
|
||||
hole.translate(p);
|
||||
}
|
||||
|
||||
void ExPolygon::rotate(double angle)
|
||||
{
|
||||
contour.rotate(angle);
|
||||
for (Polygon &hole : holes)
|
||||
hole.rotate(angle);
|
||||
}
|
||||
|
||||
void ExPolygon::rotate(double angle, const Point ¢er)
|
||||
{
|
||||
contour.rotate(angle, center);
|
||||
for (Polygon &hole : holes)
|
||||
hole.rotate(angle, center);
|
||||
}
|
||||
|
||||
double ExPolygon::area() const
|
||||
{
|
||||
double a = this->contour.area();
|
||||
for (const Polygon &hole : holes)
|
||||
a -= - hole.area(); // holes have negative area
|
||||
return a;
|
||||
}
|
||||
|
||||
bool ExPolygon::is_valid() const
|
||||
{
|
||||
if (!this->contour.is_valid() || !this->contour.is_counter_clockwise()) return false;
|
||||
for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) {
|
||||
if (!(*it).is_valid() || (*it).is_counter_clockwise()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExPolygon::douglas_peucker(double tolerance)
|
||||
{
|
||||
this->contour.douglas_peucker(tolerance);
|
||||
for (Polygon &poly : this->holes)
|
||||
poly.douglas_peucker(tolerance);
|
||||
}
|
||||
|
||||
bool ExPolygon::contains(const Line &line) const
|
||||
{
|
||||
return this->contains(Polyline(line.a, line.b));
|
||||
}
|
||||
|
||||
bool ExPolygon::contains(const Polyline &polyline) const
|
||||
{
|
||||
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) const
|
||||
{
|
||||
if (! this->contour.contains(point))
|
||||
return false;
|
||||
for (const Polygon &hole : this->holes)
|
||||
if (hole.contains(point))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// inclusive version of contains() that also checks whether point is on boundaries
|
||||
bool ExPolygon::contains_b(const Point &point) const
|
||||
{
|
||||
return this->contains(point) || this->has_boundary_point(point);
|
||||
}
|
||||
|
||||
bool
|
||||
ExPolygon::has_boundary_point(const Point &point) const
|
||||
{
|
||||
if (this->contour.has_boundary_point(point)) return true;
|
||||
for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) {
|
||||
if (h->has_boundary_point(point)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ExPolygon::overlaps(const ExPolygon &other) const
|
||||
{
|
||||
#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((Polylines)other, *this);
|
||||
#if 0
|
||||
svg.draw(pl_out, "red");
|
||||
#endif
|
||||
if (! pl_out.empty())
|
||||
return true;
|
||||
//FIXME ExPolygon::overlaps() shall be commutative, it is not!
|
||||
return ! other.contour.points.empty() && this->contains_b(other.contour.points.front());
|
||||
}
|
||||
|
||||
void ExPolygon::simplify_p(double tolerance, Polygons* polygons, SimplifyMethod method) const
|
||||
{
|
||||
Polygons pp = this->simplify_p(tolerance, method);
|
||||
polygons->insert(polygons->end(), pp.begin(), pp.end());
|
||||
}
|
||||
|
||||
Polygons ExPolygon::simplify_p(double tolerance, SimplifyMethod method) const
|
||||
{
|
||||
Polygons pp;
|
||||
pp.reserve(this->holes.size() + 1);
|
||||
std::map<int, std::function<Points(const Points&, const double)>> method_list = { {SimplifyMethodDP, MultiPoint::_douglas_peucker}, {SimplifyMethodVisvalingam, MultiPoint::visivalingam},{SimplifyMethodConcave, MultiPoint::concave_hull_2d} };
|
||||
// contour
|
||||
{
|
||||
Polygon p = this->contour;
|
||||
p.points.push_back(p.points.front());
|
||||
p.points = method_list[method](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 = method_list[method](p.points, tolerance);
|
||||
p.points.pop_back();
|
||||
pp.emplace_back(std::move(p));
|
||||
}
|
||||
return simplify_polygons(pp);
|
||||
}
|
||||
|
||||
ExPolygons ExPolygon::simplify(double tolerance, SimplifyMethod method) const
|
||||
{
|
||||
return union_ex(this->simplify_p(tolerance, method));
|
||||
}
|
||||
|
||||
void ExPolygon::simplify(double tolerance, ExPolygons* expolygons, SimplifyMethod method) const
|
||||
{
|
||||
append(*expolygons, this->simplify(tolerance, method));
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygon::medial_axis(double max_width, double min_width, ThickPolylines* polylines) const
|
||||
{
|
||||
// init helper object
|
||||
Slic3r::Geometry::MedialAxis ma(max_width, min_width, this);
|
||||
ma.lines = this->lines();
|
||||
|
||||
// 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->has_boundary_point(new_front)) {
|
||||
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->has_boundary_point(new_back)) {
|
||||
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 max_width, double min_width, Polylines* polylines) const
|
||||
{
|
||||
ThickPolylines tp;
|
||||
this->medial_axis(max_width, min_width, &tp);
|
||||
polylines->insert(polylines->end(), tp.begin(), tp.end());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
485
src/libslic3r/ExPolygon.hpp
Normal file
485
src/libslic3r/ExPolygon.hpp
Normal file
|
|
@ -0,0 +1,485 @@
|
|||
#ifndef slic3r_ExPolygon_hpp_
|
||||
#define slic3r_ExPolygon_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygon;
|
||||
typedef std::vector<ExPolygon> ExPolygons;
|
||||
|
||||
typedef enum SimplifyMethod_ {
|
||||
SimplifyMethodDP=0,
|
||||
SimplifyMethodVisvalingam,
|
||||
SimplifyMethodConcave
|
||||
}SimplifyMethod;
|
||||
|
||||
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;
|
||||
Polygons holes;
|
||||
|
||||
operator Points() const;
|
||||
operator Polygons() const;
|
||||
operator Polylines() const;
|
||||
void clear() { contour.points.clear(); holes.clear(); }
|
||||
void scale(double factor);
|
||||
void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); }
|
||||
void translate(const Point &vector);
|
||||
void rotate(double angle);
|
||||
void rotate(double angle, const Point ¢er);
|
||||
double area() const;
|
||||
bool empty() const { return contour.points.empty(); }
|
||||
bool is_valid() const;
|
||||
void douglas_peucker(double tolerance);
|
||||
|
||||
// Contains the line / polyline / polylines etc COMPLETELY.
|
||||
bool contains(const Line &line) const;
|
||||
bool contains(const Polyline &polyline) const;
|
||||
bool contains(const Polylines &polylines) const;
|
||||
bool contains(const Point &point) const;
|
||||
bool contains_b(const Point &point) const;
|
||||
bool has_boundary_point(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.
|
||||
bool overlaps(const ExPolygon &other) const;
|
||||
|
||||
void simplify_p(double tolerance, Polygons* polygons, SimplifyMethod method = SimplifyMethodDP) const;
|
||||
Polygons simplify_p(double tolerance, SimplifyMethod method = SimplifyMethodDP) const;
|
||||
ExPolygons simplify(double tolerance, SimplifyMethod method = SimplifyMethodDP) const;
|
||||
void simplify(double tolerance, ExPolygons* expolygons, SimplifyMethod method = SimplifyMethodDP) const;
|
||||
void medial_axis(double max_width, double min_width, ThickPolylines* polylines) const;
|
||||
void medial_axis(double max_width, double min_width, Polylines* polylines) const;
|
||||
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; }
|
||||
|
||||
// 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)
|
||||
{
|
||||
size_t n_lines = src.contour.points.size();
|
||||
for (size_t i = 0; i < src.holes.size(); ++ i)
|
||||
n_lines += src.holes[i].points.size();
|
||||
Lines lines;
|
||||
lines.reserve(n_lines);
|
||||
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)
|
||||
{
|
||||
size_t n_lines = 0;
|
||||
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++ it_expoly) {
|
||||
n_lines += it_expoly->contour.points.size();
|
||||
for (size_t i = 0; i < it_expoly->holes.size(); ++ i)
|
||||
n_lines += it_expoly->holes[i].points.size();
|
||||
}
|
||||
Lines lines;
|
||||
lines.reserve(n_lines);
|
||||
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;
|
||||
}
|
||||
|
||||
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 (Polygons::const_iterator ith = src.holes.begin(); ith != src.holes.end(); ++ith) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = std::move(ith->points);
|
||||
pl.points.push_back(ith->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 (ExPolygons::const_iterator 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 (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) {
|
||||
Polyline &pl = polylines[idx ++];
|
||||
pl.points = std::move(ith->points);
|
||||
pl.points.push_back(ith->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));
|
||||
std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(polygons));
|
||||
src.holes.clear();
|
||||
return polygons;
|
||||
}
|
||||
|
||||
inline Polygons to_polygons(ExPolygons &&src)
|
||||
{
|
||||
Polygons polygons;
|
||||
polygons.reserve(number_polygons(src));
|
||||
for (ExPolygons::iterator it = src.begin(); it != src.end(); ++it) {
|
||||
polygons.push_back(std::move(it->contour));
|
||||
std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(polygons));
|
||||
it->holes.clear();
|
||||
}
|
||||
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 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));
|
||||
std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(dst));
|
||||
src.holes.clear();
|
||||
}
|
||||
|
||||
inline void polygons_append(Polygons &dst, ExPolygons &&src)
|
||||
{
|
||||
dst.reserve(dst.size() + number_polygons(src));
|
||||
for (ExPolygons::iterator it = src.begin(); it != src.end(); ++ it) {
|
||||
dst.push_back(std::move(it->contour));
|
||||
std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(dst));
|
||||
it->holes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
std::move(std::begin(src), std::end(src), std::back_inserter(dst));
|
||||
src.clear();
|
||||
}
|
||||
}
|
||||
|
||||
inline void expolygons_rotate(ExPolygons &expolys, double angle)
|
||||
{
|
||||
for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p)
|
||||
p->rotate(angle);
|
||||
}
|
||||
|
||||
inline bool expolygons_contain(ExPolygons &expolys, const Point &pt)
|
||||
{
|
||||
for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p)
|
||||
if (p->contains(pt))
|
||||
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;
|
||||
}
|
||||
|
||||
BoundingBox get_extents(const ExPolygon &expolygon);
|
||||
BoundingBox get_extents(const ExPolygons &expolygons);
|
||||
BoundingBox get_extents_rotated(const ExPolygon &poly, double angle);
|
||||
BoundingBox get_extents_rotated(const ExPolygons &polygons, double angle);
|
||||
std::vector<BoundingBox> get_extents_vector(const ExPolygons &polygons);
|
||||
|
||||
// Test for duplicate points. The points are copied, sorted and checked for duplicates globally.
|
||||
bool has_duplicate_points(const ExPolygon &expoly);
|
||||
bool has_duplicate_points(const ExPolygons &expolys);
|
||||
|
||||
bool remove_sticks(ExPolygon &poly);
|
||||
void keep_largest_contour_only(ExPolygons &polygons);
|
||||
|
||||
inline double area(const ExPolygon &poly) { return poly.area(); }
|
||||
inline double area(const ExPolygons &polys) { double s = 0.; for (auto &p : polys) s += p.area(); return s; }
|
||||
|
||||
// Removes all expolygons smaller than min_area and also removes all holes smaller than min_area
|
||||
bool remove_small_and_small_holes(ExPolygons &expolygons, double min_area);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
// start Boost
|
||||
#include <boost/polygon/polygon.hpp>
|
||||
namespace boost { namespace polygon {
|
||||
template <>
|
||||
struct polygon_traits<Slic3r::ExPolygon> {
|
||||
typedef coord_t coordinate_type;
|
||||
typedef Slic3r::Points::const_iterator iterator_type;
|
||||
typedef Slic3r::Point point_type;
|
||||
|
||||
// Get the begin iterator
|
||||
static inline iterator_type begin_points(const Slic3r::ExPolygon& t) {
|
||||
return t.contour.points.begin();
|
||||
}
|
||||
|
||||
// Get the end iterator
|
||||
static inline iterator_type end_points(const Slic3r::ExPolygon& t) {
|
||||
return t.contour.points.end();
|
||||
}
|
||||
|
||||
// Get the number of sides of the polygon
|
||||
static inline std::size_t size(const Slic3r::ExPolygon& t) {
|
||||
return t.contour.points.size();
|
||||
}
|
||||
|
||||
// Get the winding direction of the polygon
|
||||
static inline winding_direction winding(const Slic3r::ExPolygon& /* t */) {
|
||||
return unknown_winding;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct polygon_mutable_traits<Slic3r::ExPolygon> {
|
||||
//expects stl style iterators
|
||||
template <typename iT>
|
||||
static inline Slic3r::ExPolygon& set_points(Slic3r::ExPolygon& expolygon, iT input_begin, iT input_end) {
|
||||
expolygon.contour.points.assign(input_begin, input_end);
|
||||
// skip last point since Boost will set last point = first point
|
||||
expolygon.contour.points.pop_back();
|
||||
return expolygon;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <>
|
||||
struct geometry_concept<Slic3r::ExPolygon> { typedef polygon_with_holes_concept type; };
|
||||
|
||||
template <>
|
||||
struct polygon_with_holes_traits<Slic3r::ExPolygon> {
|
||||
typedef Slic3r::Polygons::const_iterator iterator_holes_type;
|
||||
typedef Slic3r::Polygon hole_type;
|
||||
static inline iterator_holes_type begin_holes(const Slic3r::ExPolygon& t) {
|
||||
return t.holes.begin();
|
||||
}
|
||||
static inline iterator_holes_type end_holes(const Slic3r::ExPolygon& t) {
|
||||
return t.holes.end();
|
||||
}
|
||||
static inline unsigned int size_holes(const Slic3r::ExPolygon& t) {
|
||||
return (int)t.holes.size();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct polygon_with_holes_mutable_traits<Slic3r::ExPolygon> {
|
||||
template <typename iT>
|
||||
static inline Slic3r::ExPolygon& set_holes(Slic3r::ExPolygon& t, iT inputBegin, iT inputEnd) {
|
||||
t.holes.assign(inputBegin, inputEnd);
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
//first we register CPolygonSet as a polygon set
|
||||
template <>
|
||||
struct geometry_concept<Slic3r::ExPolygons> { typedef polygon_set_concept type; };
|
||||
|
||||
//next we map to the concept through traits
|
||||
template <>
|
||||
struct polygon_set_traits<Slic3r::ExPolygons> {
|
||||
typedef coord_t coordinate_type;
|
||||
typedef Slic3r::ExPolygons::const_iterator iterator_type;
|
||||
typedef Slic3r::ExPolygons operator_arg_type;
|
||||
|
||||
static inline iterator_type begin(const Slic3r::ExPolygons& polygon_set) {
|
||||
return polygon_set.begin();
|
||||
}
|
||||
|
||||
static inline iterator_type end(const Slic3r::ExPolygons& polygon_set) {
|
||||
return polygon_set.end();
|
||||
}
|
||||
|
||||
//don't worry about these, just return false from them
|
||||
static inline bool clean(const Slic3r::ExPolygons& /* polygon_set */) { return false; }
|
||||
static inline bool sorted(const Slic3r::ExPolygons& /* polygon_set */) { return false; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct polygon_set_mutable_traits<Slic3r::ExPolygons> {
|
||||
template <typename input_iterator_type>
|
||||
static inline void set(Slic3r::ExPolygons& expolygons, input_iterator_type input_begin, input_iterator_type input_end) {
|
||||
expolygons.assign(input_begin, input_end);
|
||||
}
|
||||
};
|
||||
} }
|
||||
// end Boost
|
||||
|
||||
#endif
|
||||
136
src/libslic3r/ExPolygonCollection.cpp
Normal file
136
src/libslic3r/ExPolygonCollection.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#include "ExPolygonCollection.hpp"
|
||||
#include "Geometry/ConvexHull.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
ExPolygonCollection::ExPolygonCollection(const ExPolygon &expolygon)
|
||||
{
|
||||
this->expolygons.push_back(expolygon);
|
||||
}
|
||||
|
||||
ExPolygonCollection::operator Points() const
|
||||
{
|
||||
Points points;
|
||||
Polygons pp = (Polygons)*this;
|
||||
for (Polygons::const_iterator poly = pp.begin(); poly != pp.end(); ++poly) {
|
||||
for (Points::const_iterator point = poly->points.begin(); point != poly->points.end(); ++point)
|
||||
points.push_back(*point);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
ExPolygonCollection::operator Polygons() const
|
||||
{
|
||||
Polygons polygons;
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
|
||||
polygons.push_back(it->contour);
|
||||
for (Polygons::const_iterator ith = it->holes.begin(); ith != it->holes.end(); ++ith) {
|
||||
polygons.push_back(*ith);
|
||||
}
|
||||
}
|
||||
return polygons;
|
||||
}
|
||||
|
||||
ExPolygonCollection::operator ExPolygons&()
|
||||
{
|
||||
return this->expolygons;
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygonCollection::scale(double factor)
|
||||
{
|
||||
for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
|
||||
(*it).scale(factor);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygonCollection::translate(double x, double y)
|
||||
{
|
||||
for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
|
||||
(*it).translate(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygonCollection::rotate(double angle, const Point ¢er)
|
||||
{
|
||||
for (ExPolygons::iterator it = expolygons.begin(); it != expolygons.end(); ++it) {
|
||||
(*it).rotate(angle, center);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool ExPolygonCollection::contains(const T &item) const
|
||||
{
|
||||
for (const ExPolygon &poly : this->expolygons)
|
||||
if (poly.contains(item))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
template bool ExPolygonCollection::contains<Point>(const Point &item) const;
|
||||
template bool ExPolygonCollection::contains<Line>(const Line &item) const;
|
||||
template bool ExPolygonCollection::contains<Polyline>(const Polyline &item) const;
|
||||
|
||||
bool
|
||||
ExPolygonCollection::contains_b(const Point &point) const
|
||||
{
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
|
||||
if (it->contains_b(point)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygonCollection::simplify(double tolerance)
|
||||
{
|
||||
ExPolygons expp;
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
|
||||
it->simplify(tolerance, &expp);
|
||||
}
|
||||
this->expolygons = expp;
|
||||
}
|
||||
|
||||
Polygon
|
||||
ExPolygonCollection::convex_hull() const
|
||||
{
|
||||
Points pp;
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it)
|
||||
pp.insert(pp.end(), it->contour.points.begin(), it->contour.points.end());
|
||||
return Slic3r::Geometry::convex_hull(pp);
|
||||
}
|
||||
|
||||
Lines
|
||||
ExPolygonCollection::lines() const
|
||||
{
|
||||
Lines lines;
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it) {
|
||||
Lines ex_lines = it->lines();
|
||||
lines.insert(lines.end(), ex_lines.begin(), ex_lines.end());
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
Polygons
|
||||
ExPolygonCollection::contours() const
|
||||
{
|
||||
Polygons contours;
|
||||
contours.reserve(this->expolygons.size());
|
||||
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it)
|
||||
contours.push_back(it->contour);
|
||||
return contours;
|
||||
}
|
||||
|
||||
void
|
||||
ExPolygonCollection::append(const ExPolygons &expp)
|
||||
{
|
||||
this->expolygons.insert(this->expolygons.end(), expp.begin(), expp.end());
|
||||
}
|
||||
|
||||
BoundingBox get_extents(const ExPolygonCollection &expolygon)
|
||||
{
|
||||
return get_extents(expolygon.expolygons);
|
||||
}
|
||||
|
||||
}
|
||||
41
src/libslic3r/ExPolygonCollection.hpp
Normal file
41
src/libslic3r/ExPolygonCollection.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef slic3r_ExPolygonCollection_hpp_
|
||||
#define slic3r_ExPolygonCollection_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "Line.hpp"
|
||||
#include "Polyline.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygonCollection;
|
||||
typedef std::vector<ExPolygonCollection> ExPolygonCollections;
|
||||
|
||||
class ExPolygonCollection
|
||||
{
|
||||
public:
|
||||
ExPolygons expolygons;
|
||||
|
||||
ExPolygonCollection() {}
|
||||
explicit ExPolygonCollection(const ExPolygon &expolygon);
|
||||
explicit ExPolygonCollection(const ExPolygons &expolygons) : expolygons(expolygons) {}
|
||||
explicit operator Points() const;
|
||||
explicit operator Polygons() const;
|
||||
explicit operator ExPolygons&();
|
||||
void scale(double factor);
|
||||
void translate(double x, double y);
|
||||
void rotate(double angle, const Point ¢er);
|
||||
template <class T> bool contains(const T &item) const;
|
||||
bool contains_b(const Point &point) const;
|
||||
void simplify(double tolerance);
|
||||
Polygon convex_hull() const;
|
||||
Lines lines() const;
|
||||
Polygons contours() const;
|
||||
void append(const ExPolygons &expolygons);
|
||||
};
|
||||
|
||||
extern BoundingBox get_extents(const ExPolygonCollection &expolygon);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
32
src/libslic3r/Exception.hpp
Normal file
32
src/libslic3r/Exception.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef _libslic3r_Exception_h_
|
||||
#define _libslic3r_Exception_h_
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
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);
|
||||
#undef SLIC3R_DERIVE_EXCEPTION
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // _libslic3r_Exception_h_
|
||||
128
src/libslic3r/Execution/Execution.hpp
Normal file
128
src/libslic3r/Execution/Execution.hpp
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#ifndef EXECUTION_HPP
|
||||
#define EXECUTION_HPP
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Override for valid execution policies
|
||||
template<class EP> struct IsExecutionPolicy_ : public std::false_type {};
|
||||
|
||||
template<class EP> constexpr bool IsExecutionPolicy =
|
||||
IsExecutionPolicy_<remove_cvref_t<EP>>::value;
|
||||
|
||||
template<class EP, class T = void>
|
||||
using ExecutionPolicyOnly = std::enable_if_t<IsExecutionPolicy<EP>, T>;
|
||||
|
||||
namespace execution {
|
||||
|
||||
// This struct needs to be specialized for each execution policy.
|
||||
// See ExecutionSeq.hpp and ExecutionTBB.hpp for example.
|
||||
template<class EP, class En = void> struct Traits {};
|
||||
|
||||
template<class EP> using AsTraits = Traits<remove_cvref_t<EP>>;
|
||||
|
||||
// Each execution policy should declare two types of mutexes. A a spin lock and
|
||||
// a blocking mutex. These types should satisfy the BasicLockable concept.
|
||||
template<class EP> using SpinningMutex = typename Traits<EP>::SpinningMutex;
|
||||
template<class EP> using BlockingMutex = typename Traits<EP>::BlockingMutex;
|
||||
|
||||
// Query the available threads for concurrency.
|
||||
template<class EP, class = ExecutionPolicyOnly<EP> >
|
||||
size_t max_concurrency(const EP &ep)
|
||||
{
|
||||
return AsTraits<EP>::max_concurrency(ep);
|
||||
}
|
||||
|
||||
// foreach loop with the execution policy passed as argument. Granularity can
|
||||
// be specified explicitly. max_concurrency() can be used for optimal results.
|
||||
template<class EP, class It, class Fn, class = ExecutionPolicyOnly<EP>>
|
||||
void for_each(const EP &ep, It from, It to, Fn &&fn, size_t granularity = 1)
|
||||
{
|
||||
AsTraits<EP>::for_each(ep, from, to, std::forward<Fn>(fn), granularity);
|
||||
}
|
||||
|
||||
// A reduce operation with the execution policy passed as argument.
|
||||
// mergefn has T(const T&, const T&) signature
|
||||
// accessfn has T(I) signature if I is an integral type and
|
||||
// T(const I::value_type &) if I is an iterator type.
|
||||
template<class EP,
|
||||
class I,
|
||||
class MergeFn,
|
||||
class T,
|
||||
class AccessFn,
|
||||
class = ExecutionPolicyOnly<EP> >
|
||||
T reduce(const EP & ep,
|
||||
I from,
|
||||
I to,
|
||||
const T & init,
|
||||
MergeFn && mergefn,
|
||||
AccessFn &&accessfn,
|
||||
size_t granularity = 1)
|
||||
{
|
||||
return AsTraits<EP>::reduce(ep, from, to, init,
|
||||
std::forward<MergeFn>(mergefn),
|
||||
std::forward<AccessFn>(accessfn),
|
||||
granularity);
|
||||
}
|
||||
|
||||
// An overload of reduce method to be used with iterators as 'from' and 'to'
|
||||
// arguments. Access functor is omitted here.
|
||||
template<class EP,
|
||||
class I,
|
||||
class MergeFn,
|
||||
class T,
|
||||
class = ExecutionPolicyOnly<EP> >
|
||||
T reduce(const EP &ep,
|
||||
I from,
|
||||
I to,
|
||||
const T & init,
|
||||
MergeFn &&mergefn,
|
||||
size_t granularity = 1)
|
||||
{
|
||||
return reduce(
|
||||
ep, from, to, init, std::forward<MergeFn>(mergefn),
|
||||
[](const auto &i) { return i; }, granularity);
|
||||
}
|
||||
|
||||
template<class EP,
|
||||
class I,
|
||||
class T,
|
||||
class AccessFn,
|
||||
class = ExecutionPolicyOnly<EP>>
|
||||
T accumulate(const EP & ep,
|
||||
I from,
|
||||
I to,
|
||||
const T & init,
|
||||
AccessFn &&accessfn,
|
||||
size_t granularity = 1)
|
||||
{
|
||||
return reduce(ep, from, to, init, std::plus<T>{},
|
||||
std::forward<AccessFn>(accessfn), granularity);
|
||||
}
|
||||
|
||||
|
||||
template<class EP,
|
||||
class I,
|
||||
class T,
|
||||
class = ExecutionPolicyOnly<EP> >
|
||||
T accumulate(const EP &ep,
|
||||
I from,
|
||||
I to,
|
||||
const T & init,
|
||||
size_t granularity = 1)
|
||||
{
|
||||
return reduce(
|
||||
ep, from, to, init, std::plus<T>{}, [](const auto &i) { return i; },
|
||||
granularity);
|
||||
}
|
||||
|
||||
} // namespace execution_policy
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // EXECUTION_HPP
|
||||
84
src/libslic3r/Execution/ExecutionSeq.hpp
Normal file
84
src/libslic3r/Execution/ExecutionSeq.hpp
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#ifndef EXECUTIONSEQ_HPP
|
||||
#define EXECUTIONSEQ_HPP
|
||||
|
||||
#ifdef PRUSASLICER_USE_EXECUTION_STD // Conflicts with our version of TBB
|
||||
#include <execution>
|
||||
#endif
|
||||
|
||||
#include "Execution.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Execution policy implementing dummy sequential algorithms
|
||||
struct ExecutionSeq {};
|
||||
|
||||
template<> struct IsExecutionPolicy_<ExecutionSeq> : public std::true_type {};
|
||||
|
||||
static constexpr ExecutionSeq ex_seq = {};
|
||||
|
||||
template<class EP> struct IsSequentialEP_ { static constexpr bool value = false; };
|
||||
|
||||
template<> struct IsSequentialEP_<ExecutionSeq>: public std::true_type {};
|
||||
#ifdef PRUSASLICER_USE_EXECUTION_STD
|
||||
template<> struct IsExecutionPolicy_<std::execution::sequenced_policy>: public std::true_type {};
|
||||
template<> struct IsSequentialEP_<std::execution::sequenced_policy>: public std::true_type {};
|
||||
#endif
|
||||
|
||||
template<class EP>
|
||||
constexpr bool IsSequentialEP = IsSequentialEP_<remove_cvref_t<EP>>::value;
|
||||
|
||||
template<class EP, class R = EP>
|
||||
using SequentialEPOnly = std::enable_if_t<IsSequentialEP<EP>, R>;
|
||||
|
||||
template<class EP>
|
||||
struct execution::Traits<EP, SequentialEPOnly<EP, void>> {
|
||||
private:
|
||||
struct _Mtx { inline void lock() {} inline void unlock() {} };
|
||||
|
||||
template<class Fn, class It>
|
||||
static IteratorOnly<It, void> loop_(It from, It to, Fn &&fn)
|
||||
{
|
||||
for (auto it = from; it != to; ++it) fn(*it);
|
||||
}
|
||||
|
||||
template<class Fn, class I>
|
||||
static IntegerOnly<I, void> loop_(I from, I to, Fn &&fn)
|
||||
{
|
||||
for (I i = from; i < to; ++i) fn(i);
|
||||
}
|
||||
|
||||
public:
|
||||
using SpinningMutex = _Mtx;
|
||||
using BlockingMutex = _Mtx;
|
||||
|
||||
template<class It, class Fn>
|
||||
static void for_each(const EP &,
|
||||
It from,
|
||||
It to,
|
||||
Fn &&fn,
|
||||
size_t /* ignore granularity */ = 1)
|
||||
{
|
||||
loop_(from, to, std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
template<class I, class MergeFn, class T, class AccessFn>
|
||||
static T reduce(const EP &,
|
||||
I from,
|
||||
I to,
|
||||
const T & init,
|
||||
MergeFn &&mergefn,
|
||||
AccessFn &&access,
|
||||
size_t /*granularity*/ = 1
|
||||
)
|
||||
{
|
||||
T acc = init;
|
||||
loop_(from, to, [&](auto &i) { acc = mergefn(acc, access(i)); });
|
||||
return acc;
|
||||
}
|
||||
|
||||
static size_t max_concurrency(const EP &) { return 1; }
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // EXECUTIONSEQ_HPP
|
||||
78
src/libslic3r/Execution/ExecutionTBB.hpp
Normal file
78
src/libslic3r/Execution/ExecutionTBB.hpp
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#ifndef EXECUTIONTBB_HPP
|
||||
#define EXECUTIONTBB_HPP
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <tbb/spin_mutex.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_reduce.h>
|
||||
#include <tbb/task_arena.h>
|
||||
|
||||
#include "Execution.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct ExecutionTBB {};
|
||||
template<> struct IsExecutionPolicy_<ExecutionTBB> : public std::true_type {};
|
||||
|
||||
// Execution policy using Intel TBB library under the hood.
|
||||
static constexpr ExecutionTBB ex_tbb = {};
|
||||
|
||||
template<> struct execution::Traits<ExecutionTBB> {
|
||||
private:
|
||||
|
||||
template<class Fn, class It>
|
||||
static IteratorOnly<It, void> loop_(const tbb::blocked_range<It> &range, Fn &&fn)
|
||||
{
|
||||
for (auto &el : range) fn(el);
|
||||
}
|
||||
|
||||
template<class Fn, class I>
|
||||
static IntegerOnly<I, void> loop_(const tbb::blocked_range<I> &range, Fn &&fn)
|
||||
{
|
||||
for (I i = range.begin(); i < range.end(); ++i) fn(i);
|
||||
}
|
||||
|
||||
public:
|
||||
using SpinningMutex = tbb::spin_mutex;
|
||||
using BlockingMutex = std::mutex;
|
||||
|
||||
template<class It, class Fn>
|
||||
static void for_each(const ExecutionTBB &,
|
||||
It from, It to, Fn &&fn, size_t granularity)
|
||||
{
|
||||
tbb::parallel_for(tbb::blocked_range{from, to, granularity},
|
||||
[&fn](const auto &range) {
|
||||
loop_(range, std::forward<Fn>(fn));
|
||||
});
|
||||
}
|
||||
|
||||
template<class I, class MergeFn, class T, class AccessFn>
|
||||
static T reduce(const ExecutionTBB &,
|
||||
I from,
|
||||
I to,
|
||||
const T &init,
|
||||
MergeFn &&mergefn,
|
||||
AccessFn &&access,
|
||||
size_t granularity = 1
|
||||
)
|
||||
{
|
||||
return tbb::parallel_reduce(
|
||||
tbb::blocked_range{from, to, granularity}, init,
|
||||
[&](const auto &range, T subinit) {
|
||||
T acc = subinit;
|
||||
loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); });
|
||||
return acc;
|
||||
},
|
||||
std::forward<MergeFn>(mergefn));
|
||||
}
|
||||
|
||||
static size_t max_concurrency(const ExecutionTBB &)
|
||||
{
|
||||
return tbb::this_task_arena::max_concurrency();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // EXECUTIONTBB_HPP
|
||||
181
src/libslic3r/Extruder.cpp
Normal file
181
src/libslic3r/Extruder.cpp
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
#include "Extruder.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
double Extruder::m_share_E = 0.;
|
||||
double Extruder::m_share_retracted = 0.;
|
||||
|
||||
Extruder::Extruder(unsigned int id, GCodeConfig *config, bool share_extruder) :
|
||||
m_id(id),
|
||||
m_config(config),
|
||||
m_share_extruder(m_share_extruder)
|
||||
{
|
||||
reset();
|
||||
|
||||
// cache values that are going to be called often
|
||||
m_e_per_mm3 = this->filament_flow_ratio();
|
||||
m_e_per_mm3 /= this->filament_crossection();
|
||||
}
|
||||
|
||||
double Extruder::extrude(double dE)
|
||||
{
|
||||
// BBS
|
||||
if (m_share_extruder) {
|
||||
if (RELATIVE_E_AXIS)
|
||||
m_share_E = 0.;
|
||||
m_share_E += dE;
|
||||
m_absolute_E += dE;
|
||||
if (dE < 0.)
|
||||
m_share_retracted -= dE;
|
||||
} else {
|
||||
// in case of relative E distances we always reset to 0 before any output
|
||||
if (RELATIVE_E_AXIS)
|
||||
m_E = 0.;
|
||||
m_E += dE;
|
||||
m_absolute_E += dE;
|
||||
if (dE < 0.)
|
||||
m_retracted -= dE;
|
||||
}
|
||||
return dE;
|
||||
}
|
||||
|
||||
/* This method makes sure the extruder is retracted by the specified amount
|
||||
of filament and returns the amount of filament retracted.
|
||||
If the extruder is already retracted by the same or a greater amount,
|
||||
this method is a no-op.
|
||||
The restart_extra argument sets the extra length to be used for
|
||||
unretraction. If we're actually performing a retraction, any restart_extra
|
||||
value supplied will overwrite the previous one if any. */
|
||||
double Extruder::retract(double length, double restart_extra)
|
||||
{
|
||||
// BBS
|
||||
if (m_share_extruder) {
|
||||
if (RELATIVE_E_AXIS)
|
||||
m_share_E = 0.;
|
||||
double to_retract = std::max(0., length - m_share_retracted);
|
||||
if (to_retract > 0.) {
|
||||
m_share_E -= to_retract;
|
||||
m_absolute_E -= to_retract;
|
||||
m_share_retracted += to_retract;
|
||||
}
|
||||
return to_retract;
|
||||
} else {
|
||||
// in case of relative E distances we always reset to 0 before any output
|
||||
if (RELATIVE_E_AXIS)
|
||||
m_E = 0.;
|
||||
double to_retract = std::max(0., length - m_retracted);
|
||||
if (to_retract > 0.) {
|
||||
m_E -= to_retract;
|
||||
m_absolute_E -= to_retract;
|
||||
m_retracted += to_retract;
|
||||
m_restart_extra = restart_extra;
|
||||
}
|
||||
return to_retract;
|
||||
}
|
||||
}
|
||||
|
||||
double Extruder::unretract()
|
||||
{
|
||||
// BBS
|
||||
if (m_share_extruder) {
|
||||
double dE = m_share_retracted;
|
||||
this->extrude(dE);
|
||||
m_share_retracted = 0.;
|
||||
return dE;
|
||||
} else {
|
||||
double dE = m_retracted + m_restart_extra;
|
||||
this->extrude(dE);
|
||||
m_retracted = 0.;
|
||||
m_restart_extra = 0.;
|
||||
return dE;
|
||||
}
|
||||
}
|
||||
|
||||
// Used filament volume in mm^3.
|
||||
double Extruder::extruded_volume() const
|
||||
{
|
||||
// BBS
|
||||
if (m_share_extruder) {
|
||||
// FIXME: need to count m_retracted for share extruder machine
|
||||
return this->used_filament() * this->filament_crossection();
|
||||
} else {
|
||||
return this->used_filament() * this->filament_crossection();
|
||||
}
|
||||
}
|
||||
|
||||
// Used filament length in mm.
|
||||
double Extruder::used_filament() const
|
||||
{
|
||||
// BBS
|
||||
if (m_share_extruder) {
|
||||
// FIXME: need to count retracted length for share-extruder machine
|
||||
return m_absolute_E;
|
||||
} else {
|
||||
return m_absolute_E + m_retracted;
|
||||
}
|
||||
}
|
||||
|
||||
double Extruder::filament_diameter() const
|
||||
{
|
||||
return m_config->filament_diameter.get_at(m_id);
|
||||
}
|
||||
|
||||
double Extruder::filament_density() const
|
||||
{
|
||||
return m_config->filament_density.get_at(m_id);
|
||||
}
|
||||
|
||||
double Extruder::filament_cost() const
|
||||
{
|
||||
return m_config->filament_cost.get_at(m_id);
|
||||
}
|
||||
|
||||
double Extruder::filament_flow_ratio() const
|
||||
{
|
||||
return m_config->filament_flow_ratio.get_at(m_id);
|
||||
}
|
||||
|
||||
// Return a "retract_before_wipe" percentage as a factor clamped to <0, 1>
|
||||
double Extruder::retract_before_wipe() const
|
||||
{
|
||||
return std::min(1., std::max(0., m_config->retract_before_wipe.get_at(m_id) * 0.01));
|
||||
}
|
||||
|
||||
double Extruder::retraction_length() const
|
||||
{
|
||||
return m_config->retraction_length.get_at(m_id);
|
||||
}
|
||||
|
||||
double Extruder::retract_lift() const
|
||||
{
|
||||
return m_config->z_hop.get_at(m_id);
|
||||
}
|
||||
|
||||
int Extruder::retract_speed() const
|
||||
{
|
||||
return int(floor(m_config->retraction_speed.get_at(m_id)+0.5));
|
||||
}
|
||||
|
||||
int Extruder::deretract_speed() const
|
||||
{
|
||||
int speed = int(floor(m_config->deretraction_speed.get_at(m_id)+0.5));
|
||||
return (speed > 0) ? speed : this->retract_speed();
|
||||
}
|
||||
|
||||
double Extruder::retract_restart_extra() const
|
||||
{
|
||||
return m_config->retract_restart_extra.get_at(m_id);
|
||||
}
|
||||
|
||||
double Extruder::retract_length_toolchange() const
|
||||
{
|
||||
return m_config->retract_length_toolchange.get_at(m_id);
|
||||
}
|
||||
|
||||
double Extruder::retract_restart_extra_toolchange() const
|
||||
{
|
||||
return m_config->retract_restart_extra_toolchange.get_at(m_id);
|
||||
}
|
||||
|
||||
}
|
||||
92
src/libslic3r/Extruder.hpp
Normal file
92
src/libslic3r/Extruder.hpp
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#ifndef slic3r_Extruder_hpp_
|
||||
#define slic3r_Extruder_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Point.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCodeConfig;
|
||||
|
||||
class Extruder
|
||||
{
|
||||
public:
|
||||
Extruder(unsigned int id, GCodeConfig *config, bool share_extruder);
|
||||
virtual ~Extruder() {}
|
||||
|
||||
void reset() {
|
||||
// BBS
|
||||
if (m_share_extruder) {
|
||||
m_share_E = 0.;
|
||||
m_share_retracted = 0.;
|
||||
} else {
|
||||
m_E = 0;
|
||||
m_retracted = 0;
|
||||
m_restart_extra = 0;
|
||||
}
|
||||
|
||||
m_absolute_E = 0;
|
||||
}
|
||||
|
||||
unsigned int id() const { return m_id; }
|
||||
|
||||
double extrude(double dE);
|
||||
double retract(double length, double restart_extra);
|
||||
double unretract();
|
||||
double E() const { return m_share_extruder ? m_share_E : m_E; }
|
||||
void reset_E() { m_E = 0.; }
|
||||
double e_per_mm(double mm3_per_mm) const { return mm3_per_mm * m_e_per_mm3; }
|
||||
double e_per_mm3() const { return m_e_per_mm3; }
|
||||
// Used filament volume in mm^3.
|
||||
double extruded_volume() const;
|
||||
// Used filament length in mm.
|
||||
double used_filament() const;
|
||||
|
||||
double filament_diameter() const;
|
||||
double filament_crossection() const { return this->filament_diameter() * this->filament_diameter() * 0.25 * PI; }
|
||||
double filament_density() const;
|
||||
double filament_cost() const;
|
||||
double filament_flow_ratio() const;
|
||||
double retract_before_wipe() const;
|
||||
double retraction_length() const;
|
||||
double retract_lift() const;
|
||||
int retract_speed() const;
|
||||
int deretract_speed() const;
|
||||
double retract_restart_extra() const;
|
||||
double retract_length_toolchange() const;
|
||||
double retract_restart_extra_toolchange() const;
|
||||
|
||||
private:
|
||||
// Private constructor to create a key for a search in std::set.
|
||||
Extruder(unsigned int id) : m_id(id) {}
|
||||
|
||||
// Reference to GCodeWriter instance owned by GCodeWriter.
|
||||
GCodeConfig *m_config;
|
||||
// Print-wide global ID of this extruder.
|
||||
unsigned int m_id;
|
||||
// Current state of the extruder axis, may be resetted if relative_e_axis.
|
||||
double m_E;
|
||||
// Current state of the extruder tachometer, used to output the extruded_volume() and used_filament() statistics.
|
||||
double m_absolute_E;
|
||||
// Current positive amount of retraction.
|
||||
double m_retracted;
|
||||
// When retracted, this value stores the extra amount of priming on deretraction.
|
||||
double m_restart_extra;
|
||||
double m_e_per_mm3;
|
||||
|
||||
// BBS.
|
||||
// Create shared E and retraction data for single extruder multi-material machine
|
||||
bool m_share_extruder;
|
||||
static double m_share_E;
|
||||
static double m_share_retracted;
|
||||
};
|
||||
|
||||
// Sort Extruder objects by the extruder id by default.
|
||||
inline bool operator==(const Extruder &e1, const Extruder &e2) { return e1.id() == e2.id(); }
|
||||
inline bool operator!=(const Extruder &e1, const Extruder &e2) { return e1.id() != e2.id(); }
|
||||
inline bool operator< (const Extruder &e1, const Extruder &e2) { return e1.id() < e2.id(); }
|
||||
inline bool operator> (const Extruder &e1, const Extruder &e2) { return e1.id() > e2.id(); }
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
402
src/libslic3r/ExtrusionEntity.cpp
Normal file
402
src/libslic3r/ExtrusionEntity.cpp
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
#include "ExtrusionEntity.hpp"
|
||||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "ExPolygonCollection.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Extruder.hpp"
|
||||
#include "Flow.hpp"
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
#define L(s) (s)
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
|
||||
{
|
||||
this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection.expolygons), retval);
|
||||
}
|
||||
|
||||
void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
|
||||
{
|
||||
this->_inflate_collection(diff_pl(Polylines{ this->polyline }, collection.expolygons), retval);
|
||||
}
|
||||
|
||||
void ExtrusionPath::clip_end(double distance)
|
||||
{
|
||||
this->polyline.clip_end(distance);
|
||||
}
|
||||
|
||||
void ExtrusionPath::simplify(double tolerance)
|
||||
{
|
||||
this->polyline.simplify(tolerance);
|
||||
}
|
||||
|
||||
void ExtrusionPath::simplify_by_fitting_arc(double tolerance)
|
||||
{
|
||||
this->polyline.simplify_by_fitting_arc(tolerance);
|
||||
}
|
||||
|
||||
double ExtrusionPath::length() const
|
||||
{
|
||||
return this->polyline.length();
|
||||
}
|
||||
|
||||
void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
|
||||
{
|
||||
for (const Polyline &polyline : polylines)
|
||||
collection->entities.emplace_back(new ExtrusionPath(polyline, *this));
|
||||
}
|
||||
|
||||
void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
|
||||
{
|
||||
polygons_append(out, offset(this->polyline, float(scale_(this->width/2)) + scaled_epsilon));
|
||||
}
|
||||
|
||||
void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
|
||||
{
|
||||
// Instantiating the Flow class to get the line spacing.
|
||||
// Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler.
|
||||
bool bridge = is_bridge(this->role());
|
||||
assert(! bridge || this->width == this->height);
|
||||
auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f);
|
||||
polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
|
||||
}
|
||||
|
||||
void ExtrusionMultiPath::reverse()
|
||||
{
|
||||
for (ExtrusionPath &path : this->paths)
|
||||
path.reverse();
|
||||
std::reverse(this->paths.begin(), this->paths.end());
|
||||
}
|
||||
|
||||
double ExtrusionMultiPath::length() const
|
||||
{
|
||||
double len = 0;
|
||||
for (const ExtrusionPath &path : this->paths)
|
||||
len += path.polyline.length();
|
||||
return len;
|
||||
}
|
||||
|
||||
void ExtrusionMultiPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
|
||||
{
|
||||
for (const ExtrusionPath &path : this->paths)
|
||||
path.polygons_covered_by_width(out, scaled_epsilon);
|
||||
}
|
||||
|
||||
void ExtrusionMultiPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
|
||||
{
|
||||
for (const ExtrusionPath &path : this->paths)
|
||||
path.polygons_covered_by_spacing(out, scaled_epsilon);
|
||||
}
|
||||
|
||||
double ExtrusionMultiPath::min_mm3_per_mm() const
|
||||
{
|
||||
double min_mm3_per_mm = std::numeric_limits<double>::max();
|
||||
for (const ExtrusionPath &path : this->paths)
|
||||
min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
|
||||
return min_mm3_per_mm;
|
||||
}
|
||||
|
||||
Polyline ExtrusionMultiPath::as_polyline() const
|
||||
{
|
||||
Polyline out;
|
||||
if (! paths.empty()) {
|
||||
size_t len = 0;
|
||||
for (size_t i_path = 0; i_path < paths.size(); ++ i_path) {
|
||||
assert(! paths[i_path].polyline.points.empty());
|
||||
assert(i_path == 0 || paths[i_path - 1].polyline.points.back() == paths[i_path].polyline.points.front());
|
||||
len += paths[i_path].polyline.points.size();
|
||||
}
|
||||
// The connecting points between the segments are equal.
|
||||
len -= paths.size() - 1;
|
||||
assert(len > 0);
|
||||
out.points.reserve(len);
|
||||
out.points.push_back(paths.front().polyline.points.front());
|
||||
for (size_t i_path = 0; i_path < paths.size(); ++ i_path)
|
||||
out.points.insert(out.points.end(), paths[i_path].polyline.points.begin() + 1, paths[i_path].polyline.points.end());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool ExtrusionLoop::make_clockwise()
|
||||
{
|
||||
bool was_ccw = this->polygon().is_counter_clockwise();
|
||||
if (was_ccw) this->reverse();
|
||||
return was_ccw;
|
||||
}
|
||||
|
||||
bool ExtrusionLoop::make_counter_clockwise()
|
||||
{
|
||||
bool was_cw = this->polygon().is_clockwise();
|
||||
if (was_cw) this->reverse();
|
||||
return was_cw;
|
||||
}
|
||||
|
||||
void ExtrusionLoop::reverse()
|
||||
{
|
||||
for (ExtrusionPath &path : this->paths)
|
||||
path.reverse();
|
||||
std::reverse(this->paths.begin(), this->paths.end());
|
||||
}
|
||||
|
||||
Polygon ExtrusionLoop::polygon() const
|
||||
{
|
||||
Polygon polygon;
|
||||
for (const ExtrusionPath &path : this->paths) {
|
||||
// for each polyline, append all points except the last one (because it coincides with the first one of the next polyline)
|
||||
polygon.points.insert(polygon.points.end(), path.polyline.points.begin(), path.polyline.points.end()-1);
|
||||
}
|
||||
return polygon;
|
||||
}
|
||||
|
||||
double ExtrusionLoop::length() const
|
||||
{
|
||||
double len = 0;
|
||||
for (const ExtrusionPath &path : this->paths)
|
||||
len += path.polyline.length();
|
||||
return len;
|
||||
}
|
||||
|
||||
bool ExtrusionLoop::split_at_vertex(const Point &point)
|
||||
{
|
||||
for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
|
||||
int idx = path->polyline.find_point(point);
|
||||
if (idx != -1) {
|
||||
if (this->paths.size() == 1) {
|
||||
// just change the order of points
|
||||
Polyline p1, p2;
|
||||
path->polyline.split_at_index(idx, &p1, &p2);
|
||||
if (p1.is_valid() && p2.is_valid()) {
|
||||
p2.append(std::move(p1));
|
||||
std::swap(path->polyline.points, p2.points);
|
||||
std::swap(path->polyline.fitting_result, p2.fitting_result);
|
||||
}
|
||||
} else {
|
||||
// new paths list starts with the second half of current path
|
||||
ExtrusionPaths new_paths;
|
||||
Polyline p1, p2;
|
||||
path->polyline.split_at_index(idx, &p1, &p2);
|
||||
new_paths.reserve(this->paths.size() + 1);
|
||||
{
|
||||
ExtrusionPath p = *path;
|
||||
std::swap(p.polyline.points, p2.points);
|
||||
std::swap(p.polyline.fitting_result, p2.fitting_result);
|
||||
if (p.polyline.is_valid()) new_paths.push_back(p);
|
||||
}
|
||||
|
||||
// then we add all paths until the end of current path list
|
||||
new_paths.insert(new_paths.end(), path+1, this->paths.end()); // not including this path
|
||||
|
||||
// then we add all paths since the beginning of current list up to the previous one
|
||||
new_paths.insert(new_paths.end(), this->paths.begin(), path); // not including this path
|
||||
|
||||
// finally we add the first half of current path
|
||||
{
|
||||
ExtrusionPath p = *path;
|
||||
std::swap(p.polyline.points, p1.points);
|
||||
std::swap(p.polyline.fitting_result, p1.fitting_result);
|
||||
if (p.polyline.is_valid()) new_paths.push_back(p);
|
||||
}
|
||||
// we can now override the old path list with the new one and stop looping
|
||||
std::swap(this->paths, new_paths);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<size_t, Point> ExtrusionLoop::get_closest_path_and_point(const Point& point, bool prefer_non_overhang) const
|
||||
{
|
||||
// Find the closest path and closest point belonging to that path. Avoid overhangs, if asked for.
|
||||
size_t path_idx = 0;
|
||||
Point p;
|
||||
{
|
||||
double min = std::numeric_limits<double>::max();
|
||||
Point p_non_overhang;
|
||||
size_t path_idx_non_overhang = 0;
|
||||
double min_non_overhang = std::numeric_limits<double>::max();
|
||||
for (const ExtrusionPath& path : this->paths) {
|
||||
Point p_tmp = point.projection_onto(path.polyline);
|
||||
double dist = (p_tmp - point).cast<double>().norm();
|
||||
if (dist < min) {
|
||||
p = p_tmp;
|
||||
min = dist;
|
||||
path_idx = &path - &this->paths.front();
|
||||
}
|
||||
if (prefer_non_overhang && !is_bridge(path.role()) && dist < min_non_overhang) {
|
||||
p_non_overhang = p_tmp;
|
||||
min_non_overhang = dist;
|
||||
path_idx_non_overhang = &path - &this->paths.front();
|
||||
}
|
||||
}
|
||||
if (prefer_non_overhang && min_non_overhang != std::numeric_limits<double>::max()) {
|
||||
// Only apply the non-overhang point if there is one.
|
||||
path_idx = path_idx_non_overhang;
|
||||
p = p_non_overhang;
|
||||
}
|
||||
}
|
||||
return std::make_pair(path_idx, p);
|
||||
}
|
||||
|
||||
// Splitting an extrusion loop, possibly made of multiple segments, some of the segments may be bridging.
|
||||
void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
|
||||
{
|
||||
if (this->paths.empty())
|
||||
return;
|
||||
|
||||
auto [path_idx, p] = get_closest_path_and_point(point, prefer_non_overhang);
|
||||
|
||||
// now split path_idx in two parts
|
||||
const ExtrusionPath &path = this->paths[path_idx];
|
||||
ExtrusionPath p1(path.overhang_degree, path.curve_degree, path.role(), path.mm3_per_mm, path.width, path.height);
|
||||
ExtrusionPath p2(path.overhang_degree, path.curve_degree, path.role(), path.mm3_per_mm, path.width, path.height);
|
||||
path.polyline.split_at(p, &p1.polyline, &p2.polyline);
|
||||
|
||||
if (this->paths.size() == 1) {
|
||||
if (!p1.polyline.is_valid()) {
|
||||
std::swap(this->paths.front().polyline.points, p2.polyline.points);
|
||||
std::swap(this->paths.front().polyline.fitting_result, p2.polyline.fitting_result);
|
||||
}
|
||||
else if (!p2.polyline.is_valid()) {
|
||||
std::swap(this->paths.front().polyline.points, p1.polyline.points);
|
||||
std::swap(this->paths.front().polyline.fitting_result, p1.polyline.fitting_result);
|
||||
}
|
||||
else {
|
||||
p2.polyline.append(std::move(p1.polyline));
|
||||
std::swap(this->paths.front().polyline.points, p2.polyline.points);
|
||||
std::swap(this->paths.front().polyline.fitting_result, p2.polyline.fitting_result);
|
||||
}
|
||||
} else {
|
||||
// install the two paths
|
||||
this->paths.erase(this->paths.begin() + path_idx);
|
||||
if (p2.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p2);
|
||||
if (p1.polyline.is_valid()) this->paths.insert(this->paths.begin() + path_idx, p1);
|
||||
}
|
||||
|
||||
// split at the new vertex
|
||||
this->split_at_vertex(p);
|
||||
}
|
||||
|
||||
void ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const
|
||||
{
|
||||
*paths = this->paths;
|
||||
|
||||
while (distance > 0 && !paths->empty()) {
|
||||
ExtrusionPath &last = paths->back();
|
||||
double len = last.length();
|
||||
if (len <= distance) {
|
||||
paths->pop_back();
|
||||
distance -= len;
|
||||
} else {
|
||||
last.polyline.clip_end(distance);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ExtrusionLoop::has_overhang_point(const Point &point) const
|
||||
{
|
||||
for (const ExtrusionPath &path : this->paths) {
|
||||
int pos = path.polyline.find_point(point);
|
||||
if (pos != -1) {
|
||||
// point belongs to this path
|
||||
// we consider it overhang only if it's not an endpoint
|
||||
return (is_bridge(path.role()) && pos > 0 && pos != (int)(path.polyline.points.size())-1);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExtrusionLoop::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
|
||||
{
|
||||
for (const ExtrusionPath &path : this->paths)
|
||||
path.polygons_covered_by_width(out, scaled_epsilon);
|
||||
}
|
||||
|
||||
void ExtrusionLoop::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
|
||||
{
|
||||
for (const ExtrusionPath &path : this->paths)
|
||||
path.polygons_covered_by_spacing(out, scaled_epsilon);
|
||||
}
|
||||
|
||||
double ExtrusionLoop::min_mm3_per_mm() const
|
||||
{
|
||||
double min_mm3_per_mm = std::numeric_limits<double>::max();
|
||||
for (const ExtrusionPath &path : this->paths)
|
||||
min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
|
||||
return min_mm3_per_mm;
|
||||
}
|
||||
|
||||
|
||||
std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
|
||||
{
|
||||
switch (role) {
|
||||
case erNone : return L("Undefined");
|
||||
case erPerimeter : return L("Inner wall");
|
||||
case erExternalPerimeter : return L("Outer wall");
|
||||
case erOverhangPerimeter : return L("Overhang wall");
|
||||
case erInternalInfill : return L("Sparse infill");
|
||||
case erSolidInfill : return L("Internal solid infill");
|
||||
case erTopSolidInfill : return L("Top surface");
|
||||
case erBottomSurface : return L("Bottom surface");
|
||||
case erIroning : return L("Ironing");
|
||||
case erBridgeInfill : return L("Bridge");
|
||||
case erGapFill : return L("Gap infill");
|
||||
case erSkirt : return ("Skirt");
|
||||
case erBrim : return ("Brim");
|
||||
case erSupportMaterial : return L("Support");
|
||||
case erSupportMaterialInterface : return L("Support interface");
|
||||
case erSupportTransition : return L("Support transition");
|
||||
case erWipeTower : return L("Prime tower");
|
||||
case erCustom : return L("Custom");
|
||||
case erMixed : return L("Multiple");
|
||||
default : assert(false);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
ExtrusionRole ExtrusionEntity::string_to_role(const std::string_view role)
|
||||
{
|
||||
if (role == L("Inner wall"))
|
||||
return erPerimeter;
|
||||
else if (role == L("Outer wall"))
|
||||
return erExternalPerimeter;
|
||||
else if (role == L("Overhang wall"))
|
||||
return erOverhangPerimeter;
|
||||
else if (role == L("Sparse infill"))
|
||||
return erInternalInfill;
|
||||
else if (role == L("Internal solid infill"))
|
||||
return erSolidInfill;
|
||||
else if (role == L("Top surface"))
|
||||
return erTopSolidInfill;
|
||||
else if (role == L("Bottom surface"))
|
||||
return erBottomSurface;
|
||||
else if (role == L("Ironing"))
|
||||
return erIroning;
|
||||
else if (role == L("Bridge"))
|
||||
return erBridgeInfill;
|
||||
else if (role == L("Gap infill"))
|
||||
return erGapFill;
|
||||
else if (role == ("Skirt"))
|
||||
return erSkirt;
|
||||
else if (role == ("Brim"))
|
||||
return erBrim;
|
||||
else if (role == L("Support"))
|
||||
return erSupportMaterial;
|
||||
else if (role == L("Support interface"))
|
||||
return erSupportMaterialInterface;
|
||||
else if (role == L("Support transition"))
|
||||
return erSupportTransition;
|
||||
else if (role == L("Prime tower"))
|
||||
return erWipeTower;
|
||||
else if (role == L("Custom"))
|
||||
return erCustom;
|
||||
else if (role == L("Multiple"))
|
||||
return erMixed;
|
||||
else
|
||||
return erNone;
|
||||
}
|
||||
|
||||
}
|
||||
498
src/libslic3r/ExtrusionEntity.hpp
Normal file
498
src/libslic3r/ExtrusionEntity.hpp
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
#ifndef slic3r_ExtrusionEntity_hpp_
|
||||
#define slic3r_ExtrusionEntity_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Polygon.hpp"
|
||||
#include "Polyline.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string_view>
|
||||
#include <numeric>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExPolygonCollection;
|
||||
class ExtrusionEntityCollection;
|
||||
class Extruder;
|
||||
|
||||
// Each ExtrusionRole value identifies a distinct set of { extruder, speed }
|
||||
enum ExtrusionRole : uint8_t {
|
||||
erNone,
|
||||
erPerimeter,
|
||||
erExternalPerimeter,
|
||||
erOverhangPerimeter,
|
||||
erInternalInfill,
|
||||
erSolidInfill,
|
||||
erTopSolidInfill,
|
||||
erBottomSurface,
|
||||
erIroning,
|
||||
erBridgeInfill,
|
||||
erGapFill,
|
||||
erSkirt,
|
||||
erBrim,
|
||||
erSupportMaterial,
|
||||
erSupportMaterialInterface,
|
||||
erSupportTransition,
|
||||
erWipeTower,
|
||||
erCustom,
|
||||
// Extrusion role for a collection with multiple extrusion roles.
|
||||
erMixed,
|
||||
erCount
|
||||
};
|
||||
|
||||
// Special flags describing loop
|
||||
enum ExtrusionLoopRole {
|
||||
elrDefault,
|
||||
elrContourInternalPerimeter,
|
||||
elrSkirt,
|
||||
};
|
||||
|
||||
|
||||
inline bool is_perimeter(ExtrusionRole role)
|
||||
{
|
||||
return role == erPerimeter
|
||||
|| role == erExternalPerimeter
|
||||
|| role == erOverhangPerimeter;
|
||||
}
|
||||
|
||||
inline bool is_infill(ExtrusionRole role)
|
||||
{
|
||||
return role == erBridgeInfill
|
||||
|| role == erInternalInfill
|
||||
|| role == erSolidInfill
|
||||
|| role == erTopSolidInfill
|
||||
|| role == erBottomSurface
|
||||
|| role == erIroning;
|
||||
}
|
||||
|
||||
inline bool is_top_surface(ExtrusionRole role)
|
||||
{
|
||||
return role == erTopSolidInfill;
|
||||
}
|
||||
|
||||
inline bool is_solid_infill(ExtrusionRole role)
|
||||
{
|
||||
return role == erBridgeInfill
|
||||
|| role == erSolidInfill
|
||||
|| role == erTopSolidInfill
|
||||
|| role == erBottomSurface
|
||||
|| role == erIroning;
|
||||
}
|
||||
|
||||
inline bool is_bridge(ExtrusionRole role) {
|
||||
return role == erBridgeInfill
|
||||
|| role == erOverhangPerimeter;
|
||||
}
|
||||
|
||||
class ExtrusionEntity
|
||||
{
|
||||
public:
|
||||
virtual ExtrusionRole role() const = 0;
|
||||
virtual bool is_collection() const { return false; }
|
||||
virtual bool is_loop() const { return false; }
|
||||
virtual bool can_reverse() const { return true; }
|
||||
virtual ExtrusionEntity* clone() const = 0;
|
||||
// Create a new object, initialize it with this object using the move semantics.
|
||||
virtual ExtrusionEntity* clone_move() = 0;
|
||||
virtual ~ExtrusionEntity() {}
|
||||
virtual void reverse() = 0;
|
||||
virtual const Point& first_point() const = 0;
|
||||
virtual const Point& last_point() const = 0;
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0;
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
|
||||
virtual void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const = 0;
|
||||
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
|
||||
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
|
||||
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
|
||||
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
|
||||
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
|
||||
virtual double min_mm3_per_mm() const = 0;
|
||||
virtual Polyline as_polyline() const = 0;
|
||||
virtual void collect_polylines(Polylines &dst) const = 0;
|
||||
virtual void collect_points(Points &dst) const = 0;
|
||||
virtual Polylines as_polylines() const { Polylines dst; this->collect_polylines(dst); return dst; }
|
||||
virtual double length() const = 0;
|
||||
virtual double total_volume() const = 0;
|
||||
|
||||
static std::string role_to_string(ExtrusionRole role);
|
||||
static ExtrusionRole string_to_role(const std::string_view role);
|
||||
};
|
||||
|
||||
typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;
|
||||
|
||||
class ExtrusionPath : public ExtrusionEntity
|
||||
{
|
||||
public:
|
||||
Polyline polyline;
|
||||
int overhang_degree = 0;
|
||||
int curve_degree = 0;
|
||||
// Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator.
|
||||
double mm3_per_mm;
|
||||
// Width of the extrusion, used for visualization purposes.
|
||||
float width;
|
||||
// Height of the extrusion, used for visualization purposes.
|
||||
float height;
|
||||
|
||||
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role), m_no_extrusion(false) {}
|
||||
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height, bool no_extrusion = false) : mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role), m_no_extrusion(no_extrusion) {}
|
||||
ExtrusionPath(int overhang_degree, int curve_degree, ExtrusionRole role, double mm3_per_mm, float width, float height) : overhang_degree(overhang_degree), curve_degree(curve_degree), mm3_per_mm(mm3_per_mm), width(width), height(height), m_role(role) {}
|
||||
ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), overhang_degree(rhs.overhang_degree), curve_degree(rhs.curve_degree), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role), m_no_extrusion(rhs.m_no_extrusion) {}
|
||||
ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), overhang_degree(rhs.overhang_degree), curve_degree(rhs.curve_degree), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role), m_no_extrusion(rhs.m_no_extrusion) {}
|
||||
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), overhang_degree(rhs.overhang_degree), curve_degree(rhs.curve_degree), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role), m_no_extrusion(rhs.m_no_extrusion) {}
|
||||
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), overhang_degree(rhs.overhang_degree), curve_degree(rhs.curve_degree), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), m_role(rhs.m_role), m_no_extrusion(rhs.m_no_extrusion) {}
|
||||
|
||||
ExtrusionPath& operator=(const ExtrusionPath& rhs) {
|
||||
m_role = rhs.m_role;
|
||||
m_no_extrusion = rhs.m_no_extrusion;
|
||||
this->mm3_per_mm = rhs.mm3_per_mm;
|
||||
this->width = rhs.width;
|
||||
this->height = rhs.height;
|
||||
this->overhang_degree = rhs.overhang_degree;
|
||||
this->curve_degree = rhs.curve_degree;
|
||||
this->polyline = rhs.polyline;
|
||||
return *this;
|
||||
}
|
||||
ExtrusionPath& operator=(ExtrusionPath&& rhs) {
|
||||
m_role = rhs.m_role;
|
||||
m_no_extrusion = rhs.m_no_extrusion;
|
||||
this->mm3_per_mm = rhs.mm3_per_mm;
|
||||
this->width = rhs.width;
|
||||
this->height = rhs.height;
|
||||
this->overhang_degree = rhs.overhang_degree;
|
||||
this->curve_degree = rhs.curve_degree;
|
||||
this->polyline = std::move(rhs.polyline);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); }
|
||||
// Create a new object, initialize it with this object using the move semantics.
|
||||
ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); }
|
||||
void reverse() override { this->polyline.reverse(); }
|
||||
const Point& first_point() const override { return this->polyline.points.front(); }
|
||||
const Point& last_point() const override { return this->polyline.points.back(); }
|
||||
size_t size() const { return this->polyline.size(); }
|
||||
bool empty() const { return this->polyline.empty(); }
|
||||
bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
|
||||
// Produce a list of extrusion paths into retval by clipping this path by ExPolygonCollection.
|
||||
// Currently not used.
|
||||
void intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
|
||||
// Produce a list of extrusion paths into retval by removing parts of this path by ExPolygonCollection.
|
||||
// Currently not used.
|
||||
void subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const;
|
||||
void clip_end(double distance);
|
||||
void simplify(double tolerance);
|
||||
double length() const override;
|
||||
ExtrusionRole role() const override { return m_role; }
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
|
||||
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
|
||||
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
|
||||
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
|
||||
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
|
||||
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
|
||||
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
|
||||
double min_mm3_per_mm() const override { return this->mm3_per_mm; }
|
||||
Polyline as_polyline() const override { return this->polyline; }
|
||||
void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
|
||||
void collect_points(Points &dst) const override { append(dst, this->polyline.points); }
|
||||
double total_volume() const override { return mm3_per_mm * unscale<double>(length()); }
|
||||
|
||||
void set_overhang_degree(int overhang) {
|
||||
if (is_perimeter(m_role))
|
||||
overhang_degree = (overhang < 0)?0:(overhang > 10 ? 10 : overhang);
|
||||
};
|
||||
int get_overhang_degree() const {
|
||||
// only perimeter has overhang degree. Other return 0;
|
||||
if (is_perimeter(m_role))
|
||||
return overhang_degree;
|
||||
return 0;
|
||||
};
|
||||
void set_curve_degree(int curve) {
|
||||
curve_degree = (curve < 0)?0:(curve > 10 ? 10 : curve);
|
||||
};
|
||||
int get_curve_degree() const {
|
||||
return curve_degree;
|
||||
};
|
||||
//BBS: add new simplifing method by fitting arc
|
||||
void simplify_by_fitting_arc(double tolerance);
|
||||
//BBS:
|
||||
bool is_force_no_extrusion() const { return m_no_extrusion; }
|
||||
|
||||
private:
|
||||
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
|
||||
|
||||
ExtrusionRole m_role;
|
||||
//BBS
|
||||
bool m_no_extrusion = false;
|
||||
};
|
||||
|
||||
typedef std::vector<ExtrusionPath> ExtrusionPaths;
|
||||
|
||||
// Single continuous extrusion path, possibly with varying extrusion thickness, extrusion height or bridging / non bridging.
|
||||
class ExtrusionMultiPath : public ExtrusionEntity
|
||||
{
|
||||
public:
|
||||
ExtrusionPaths paths;
|
||||
|
||||
ExtrusionMultiPath() {}
|
||||
ExtrusionMultiPath(const ExtrusionMultiPath &rhs) : paths(rhs.paths) {}
|
||||
ExtrusionMultiPath(ExtrusionMultiPath &&rhs) : paths(std::move(rhs.paths)) {}
|
||||
ExtrusionMultiPath(const ExtrusionPaths &paths) : paths(paths) {}
|
||||
ExtrusionMultiPath(const ExtrusionPath &path) { this->paths.push_back(path); }
|
||||
|
||||
ExtrusionMultiPath& operator=(const ExtrusionMultiPath &rhs) { this->paths = rhs.paths; return *this; }
|
||||
ExtrusionMultiPath& operator=(ExtrusionMultiPath &&rhs) { this->paths = std::move(rhs.paths); return *this; }
|
||||
|
||||
bool is_loop() const override { return false; }
|
||||
bool can_reverse() const override { return true; }
|
||||
ExtrusionEntity* clone() const override { return new ExtrusionMultiPath(*this); }
|
||||
// Create a new object, initialize it with this object using the move semantics.
|
||||
ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); }
|
||||
void reverse() override;
|
||||
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
|
||||
const Point& last_point() const override { return this->paths.back().polyline.points.back(); }
|
||||
size_t size() const { return this->paths.size(); }
|
||||
bool empty() const { return this->paths.empty(); }
|
||||
double length() const override;
|
||||
ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
|
||||
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
|
||||
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
|
||||
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
|
||||
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
|
||||
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
|
||||
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
|
||||
double min_mm3_per_mm() const override;
|
||||
Polyline as_polyline() const override;
|
||||
void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); }
|
||||
void collect_points(Points &dst) const override {
|
||||
size_t n = std::accumulate(paths.begin(), paths.end(), 0, [](const size_t n, const ExtrusionPath &p){ return n + p.polyline.size(); });
|
||||
dst.reserve(dst.size() + n);
|
||||
for (const ExtrusionPath &p : this->paths)
|
||||
append(dst, p.polyline.points);
|
||||
}
|
||||
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
|
||||
};
|
||||
|
||||
// Single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height or bridging / non bridging.
|
||||
class ExtrusionLoop : public ExtrusionEntity
|
||||
{
|
||||
public:
|
||||
ExtrusionPaths paths;
|
||||
|
||||
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {}
|
||||
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {}
|
||||
ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {}
|
||||
ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
|
||||
{ this->paths.push_back(path); }
|
||||
ExtrusionLoop(const ExtrusionPath &&path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
|
||||
{ this->paths.emplace_back(std::move(path)); }
|
||||
bool is_loop() const override{ return true; }
|
||||
bool can_reverse() const override { return false; }
|
||||
ExtrusionEntity* clone() const override{ return new ExtrusionLoop (*this); }
|
||||
// Create a new object, initialize it with this object using the move semantics.
|
||||
ExtrusionEntity* clone_move() override { return new ExtrusionLoop(std::move(*this)); }
|
||||
bool make_clockwise();
|
||||
bool make_counter_clockwise();
|
||||
bool is_clockwise() { return this->polygon().is_clockwise(); }
|
||||
bool is_counter_clockwise() { return this->polygon().is_counter_clockwise(); }
|
||||
void reverse() override;
|
||||
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
|
||||
const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); }
|
||||
Polygon polygon() const;
|
||||
double length() const override;
|
||||
bool split_at_vertex(const Point &point);
|
||||
void split_at(const Point &point, bool prefer_non_overhang);
|
||||
std::pair<size_t, Point> get_closest_path_and_point(const Point& point, bool prefer_non_overhang) const;
|
||||
void clip_end(double distance, ExtrusionPaths* paths) const;
|
||||
// Test, whether the point is extruded by a bridging flow.
|
||||
// This used to be used to avoid placing seams on overhangs, but now the EdgeGrid is used instead.
|
||||
bool has_overhang_point(const Point &point) const;
|
||||
ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
|
||||
ExtrusionLoopRole loop_role() const { return m_loop_role; }
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
|
||||
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
|
||||
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
|
||||
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
|
||||
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
|
||||
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
|
||||
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
|
||||
double min_mm3_per_mm() const override;
|
||||
Polyline as_polyline() const override { return this->polygon().split_at_first_point(); }
|
||||
void collect_polylines(Polylines &dst) const override { Polyline pl = this->as_polyline(); if (! pl.empty()) dst.emplace_back(std::move(pl)); }
|
||||
void collect_points(Points &dst) const override {
|
||||
size_t n = std::accumulate(paths.begin(), paths.end(), 0, [](const size_t n, const ExtrusionPath &p){ return n + p.polyline.size(); });
|
||||
dst.reserve(dst.size() + n);
|
||||
for (const ExtrusionPath &p : this->paths)
|
||||
append(dst, p.polyline.points);
|
||||
}
|
||||
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
|
||||
|
||||
//static inline std::string role_to_string(ExtrusionLoopRole role);
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool validate() const {
|
||||
assert(this->first_point() == this->paths.back().polyline.points.back());
|
||||
for (size_t i = 1; i < paths.size(); ++ i)
|
||||
assert(this->paths[i - 1].polyline.points.back() == this->paths[i].polyline.points.front());
|
||||
return true;
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
private:
|
||||
ExtrusionLoopRole m_loop_role;
|
||||
};
|
||||
|
||||
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
|
||||
dst.back().polyline = polyline;
|
||||
}
|
||||
}
|
||||
|
||||
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, int overhang_degree, int curva_degree, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
dst.push_back(ExtrusionPath(overhang_degree, curva_degree, role, mm3_per_mm, width, height));
|
||||
dst.back().polyline = polyline;
|
||||
}
|
||||
}
|
||||
|
||||
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
|
||||
dst.back().polyline = std::move(polyline);
|
||||
}
|
||||
polylines.clear();
|
||||
}
|
||||
|
||||
inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, int overhang_degree, int curva_degree, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
dst.push_back(ExtrusionPath(overhang_degree, curva_degree, role, mm3_per_mm, width, height));
|
||||
dst.back().polyline = std::move(polyline);
|
||||
}
|
||||
polylines.clear();
|
||||
}
|
||||
|
||||
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
|
||||
dst.push_back(extrusion_path);
|
||||
extrusion_path->polyline = polyline;
|
||||
}
|
||||
}
|
||||
|
||||
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines)
|
||||
if (polyline.is_valid()) {
|
||||
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
|
||||
dst.push_back(extrusion_path);
|
||||
extrusion_path->polyline = std::move(polyline);
|
||||
}
|
||||
polylines.clear();
|
||||
}
|
||||
|
||||
//BBS: a kind of special extrusion path has start and end wiping for half spacing
|
||||
inline void extrusion_entities_append_paths_with_wipe(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
Point new_start, new_end, last_end_point;
|
||||
bool last_end_point_valid = false;
|
||||
Vec2d temp;
|
||||
ExtrusionMultiPath* multi_path = new ExtrusionMultiPath();
|
||||
for (Polyline& polyline : polylines) {
|
||||
if (polyline.is_valid()) {
|
||||
|
||||
if (last_end_point_valid) {
|
||||
Point temp = polyline.first_point() - last_end_point;
|
||||
if (Vec2d(temp.x(), temp.y()).norm() <= 3 * scaled(width)) {
|
||||
multi_path->paths.push_back(ExtrusionPath(role, mm3_per_mm, width, height, true));
|
||||
multi_path->paths.back().polyline = std::move(Polyline(last_end_point, polyline.first_point()));
|
||||
} else {
|
||||
dst.push_back(multi_path);
|
||||
multi_path = new ExtrusionMultiPath();
|
||||
}
|
||||
}
|
||||
|
||||
multi_path->paths.push_back(ExtrusionPath(role, mm3_per_mm, width, height));
|
||||
multi_path->paths.back().polyline = std::move(polyline);
|
||||
last_end_point_valid = true;
|
||||
last_end_point = multi_path->paths.back().polyline.last_point();
|
||||
}
|
||||
}
|
||||
if (!multi_path->empty())
|
||||
dst.push_back(multi_path);
|
||||
polylines.clear();
|
||||
dst.shrink_to_fit();
|
||||
}
|
||||
|
||||
inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
{
|
||||
dst.reserve(dst.size() + loops.size());
|
||||
for (Polygon &poly : loops) {
|
||||
if (poly.is_valid()) {
|
||||
ExtrusionPath path(role, mm3_per_mm, width, height);
|
||||
path.polyline.points = std::move(poly.points);
|
||||
path.polyline.points.push_back(path.polyline.points.front());
|
||||
dst.emplace_back(new ExtrusionLoop(std::move(path)));
|
||||
}
|
||||
}
|
||||
loops.clear();
|
||||
}
|
||||
|
||||
inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
|
||||
{
|
||||
dst.reserve(dst.size() + polylines.size());
|
||||
for (Polyline &polyline : polylines) {
|
||||
if (polyline.is_valid()) {
|
||||
if (polyline.is_closed()) {
|
||||
ExtrusionPath extrusion_path(role, mm3_per_mm, width, height);
|
||||
extrusion_path.polyline = std::move(polyline);
|
||||
dst.emplace_back(new ExtrusionLoop(std::move(extrusion_path)));
|
||||
} else {
|
||||
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
|
||||
extrusion_path->polyline = std::move(polyline);
|
||||
dst.emplace_back(extrusion_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
polylines.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
158
src/libslic3r/ExtrusionEntityCollection.cpp
Normal file
158
src/libslic3r/ExtrusionEntityCollection.cpp
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
#include "ExtrusionEntityCollection.hpp"
|
||||
#include "ShortestPath.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role)
|
||||
{
|
||||
if (role != erMixed) {
|
||||
auto first = extrusion_entities.begin();
|
||||
auto last = extrusion_entities.end();
|
||||
extrusion_entities.erase(
|
||||
std::remove_if(first, last, [&role](const ExtrusionEntity* ee) {
|
||||
return ee->role() != role; }),
|
||||
last);
|
||||
}
|
||||
}
|
||||
|
||||
ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths)
|
||||
: no_sort(false)
|
||||
{
|
||||
this->append(paths);
|
||||
}
|
||||
|
||||
ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionEntityCollection &other)
|
||||
{
|
||||
this->entities = other.entities;
|
||||
for (size_t i = 0; i < this->entities.size(); ++i)
|
||||
this->entities[i] = this->entities[i]->clone();
|
||||
this->no_sort = other.no_sort;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c)
|
||||
{
|
||||
std::swap(this->entities, c.entities);
|
||||
std::swap(this->no_sort, c.no_sort);
|
||||
}
|
||||
|
||||
void ExtrusionEntityCollection::clear()
|
||||
{
|
||||
for (size_t i = 0; i < this->entities.size(); ++i)
|
||||
delete this->entities[i];
|
||||
this->entities.clear();
|
||||
}
|
||||
|
||||
ExtrusionEntityCollection::operator ExtrusionPaths() const
|
||||
{
|
||||
ExtrusionPaths paths;
|
||||
for (const ExtrusionEntity *ptr : this->entities) {
|
||||
if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(ptr))
|
||||
paths.push_back(*path);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
ExtrusionEntity* ExtrusionEntityCollection::clone() const
|
||||
{
|
||||
ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(*this);
|
||||
for (size_t i = 0; i < coll->entities.size(); ++i)
|
||||
coll->entities[i] = this->entities[i]->clone();
|
||||
return coll;
|
||||
}
|
||||
|
||||
void ExtrusionEntityCollection::reverse()
|
||||
{
|
||||
for (ExtrusionEntity *ptr : this->entities)
|
||||
// Don't reverse it if it's a loop, as it doesn't change anything in terms of elements ordering
|
||||
// and caller might rely on winding order
|
||||
if (! ptr->is_loop())
|
||||
ptr->reverse();
|
||||
std::reverse(this->entities.begin(), this->entities.end());
|
||||
}
|
||||
|
||||
void ExtrusionEntityCollection::replace(size_t i, const ExtrusionEntity &entity)
|
||||
{
|
||||
delete this->entities[i];
|
||||
this->entities[i] = entity.clone();
|
||||
}
|
||||
|
||||
void ExtrusionEntityCollection::remove(size_t i)
|
||||
{
|
||||
delete this->entities[i];
|
||||
this->entities.erase(this->entities.begin() + i);
|
||||
}
|
||||
|
||||
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const ExtrusionEntitiesPtr& extrusion_entities, const Point &start_near, ExtrusionRole role)
|
||||
{
|
||||
// Return a filtered copy of the collection.
|
||||
ExtrusionEntityCollection out;
|
||||
out.entities = filter_by_extrusion_role(extrusion_entities, role);
|
||||
// Clone the extrusion entities.
|
||||
for (auto &ptr : out.entities)
|
||||
ptr = ptr->clone();
|
||||
chain_and_reorder_extrusion_entities(out.entities, &start_near);
|
||||
return out;
|
||||
}
|
||||
|
||||
void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
|
||||
{
|
||||
for (const ExtrusionEntity *entity : this->entities)
|
||||
entity->polygons_covered_by_width(out, scaled_epsilon);
|
||||
}
|
||||
|
||||
void ExtrusionEntityCollection::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
|
||||
{
|
||||
for (const ExtrusionEntity *entity : this->entities)
|
||||
entity->polygons_covered_by_spacing(out, scaled_epsilon);
|
||||
}
|
||||
|
||||
// Recursively count paths and loops contained in this collection.
|
||||
size_t ExtrusionEntityCollection::items_count() const
|
||||
{
|
||||
size_t count = 0;
|
||||
for (const ExtrusionEntity *entity : this->entities)
|
||||
if (entity->is_collection())
|
||||
count += static_cast<const ExtrusionEntityCollection*>(entity)->items_count();
|
||||
else
|
||||
++ count;
|
||||
return count;
|
||||
}
|
||||
|
||||
// Returns a single vector of pointers to all non-collection items contained in this one.
|
||||
ExtrusionEntityCollection ExtrusionEntityCollection::flatten(bool preserve_ordering) const
|
||||
{
|
||||
struct Flatten {
|
||||
Flatten(bool preserve_ordering) : preserve_ordering(preserve_ordering) {}
|
||||
ExtrusionEntityCollection out;
|
||||
bool preserve_ordering;
|
||||
void recursive_do(const ExtrusionEntityCollection &collection) {
|
||||
if (collection.no_sort && preserve_ordering) {
|
||||
// Don't flatten whatever happens below this level.
|
||||
out.append(collection);
|
||||
} else {
|
||||
for (const ExtrusionEntity *entity : collection.entities)
|
||||
if (entity->is_collection())
|
||||
this->recursive_do(*static_cast<const ExtrusionEntityCollection*>(entity));
|
||||
else
|
||||
out.append(*entity);
|
||||
}
|
||||
}
|
||||
} flatten(preserve_ordering);
|
||||
|
||||
flatten.recursive_do(*this);
|
||||
return flatten.out;
|
||||
}
|
||||
|
||||
double ExtrusionEntityCollection::min_mm3_per_mm() const
|
||||
{
|
||||
double min_mm3_per_mm = std::numeric_limits<double>::max();
|
||||
for (const ExtrusionEntity *entity : this->entities)
|
||||
min_mm3_per_mm = std::min(min_mm3_per_mm, entity->min_mm3_per_mm());
|
||||
return min_mm3_per_mm;
|
||||
}
|
||||
|
||||
}
|
||||
133
src/libslic3r/ExtrusionEntityCollection.hpp
Normal file
133
src/libslic3r/ExtrusionEntityCollection.hpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#ifndef slic3r_ExtrusionEntityCollection_hpp_
|
||||
#define slic3r_ExtrusionEntityCollection_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Exception.hpp"
|
||||
#include "ExtrusionEntity.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Remove those items from extrusion_entities, that do not match role.
|
||||
// Do nothing if role is mixed.
|
||||
// Removed elements are NOT being deleted.
|
||||
void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role);
|
||||
|
||||
// Return new vector of ExtrusionEntities* with only those items from input extrusion_entities, that match role.
|
||||
// Return all extrusion entities if role is mixed.
|
||||
// Returned extrusion entities are shared with the source vector, they are NOT cloned, they are considered to be owned by extrusion_entities.
|
||||
inline ExtrusionEntitiesPtr filter_by_extrusion_role(const ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role)
|
||||
{
|
||||
ExtrusionEntitiesPtr out { extrusion_entities };
|
||||
filter_by_extrusion_role_in_place(out, role);
|
||||
return out;
|
||||
}
|
||||
|
||||
class ExtrusionEntityCollection : public ExtrusionEntity
|
||||
{
|
||||
public:
|
||||
ExtrusionEntity* clone() const override;
|
||||
// Create a new object, initialize it with this object using the move semantics.
|
||||
ExtrusionEntity* clone_move() override { return new ExtrusionEntityCollection(std::move(*this)); }
|
||||
|
||||
ExtrusionEntitiesPtr entities; // we own these entities
|
||||
bool no_sort;
|
||||
ExtrusionEntityCollection(): no_sort(false) {}
|
||||
ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort) { this->append(other.entities); }
|
||||
ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), no_sort(other.no_sort) {}
|
||||
explicit ExtrusionEntityCollection(const ExtrusionPaths &paths);
|
||||
ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other);
|
||||
ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other)
|
||||
{ this->entities = std::move(other.entities); this->no_sort = other.no_sort; return *this; }
|
||||
~ExtrusionEntityCollection() { clear(); }
|
||||
explicit operator ExtrusionPaths() const;
|
||||
|
||||
bool is_collection() const override { return true; }
|
||||
ExtrusionRole role() const override {
|
||||
ExtrusionRole out = erNone;
|
||||
for (const ExtrusionEntity *ee : entities) {
|
||||
ExtrusionRole er = ee->role();
|
||||
out = (out == erNone || out == er) ? er : erMixed;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
bool can_reverse() const override { return !this->no_sort; }
|
||||
bool empty() const { return this->entities.empty(); }
|
||||
void clear();
|
||||
void swap (ExtrusionEntityCollection &c);
|
||||
void append(const ExtrusionEntity &entity) { this->entities.emplace_back(entity.clone()); }
|
||||
void append(ExtrusionEntity &&entity) { this->entities.emplace_back(entity.clone_move()); }
|
||||
void append(const ExtrusionEntitiesPtr &entities) {
|
||||
this->entities.reserve(this->entities.size() + entities.size());
|
||||
for (const ExtrusionEntity *ptr : entities)
|
||||
this->entities.emplace_back(ptr->clone());
|
||||
}
|
||||
void append(ExtrusionEntitiesPtr &&src) {
|
||||
if (entities.empty())
|
||||
entities = std::move(src);
|
||||
else {
|
||||
std::move(std::begin(src), std::end(src), std::back_inserter(entities));
|
||||
src.clear();
|
||||
}
|
||||
}
|
||||
void append(const ExtrusionPaths &paths) {
|
||||
this->entities.reserve(this->entities.size() + paths.size());
|
||||
for (const ExtrusionPath &path : paths)
|
||||
this->entities.emplace_back(path.clone());
|
||||
}
|
||||
void append(ExtrusionPaths &&paths) {
|
||||
this->entities.reserve(this->entities.size() + paths.size());
|
||||
for (ExtrusionPath &path : paths)
|
||||
this->entities.emplace_back(new ExtrusionPath(std::move(path)));
|
||||
}
|
||||
void replace(size_t i, const ExtrusionEntity &entity);
|
||||
void remove(size_t i);
|
||||
static ExtrusionEntityCollection chained_path_from(const ExtrusionEntitiesPtr &extrusion_entities, const Point &start_near, ExtrusionRole role = erMixed);
|
||||
ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const
|
||||
{ return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); }
|
||||
void reverse() override;
|
||||
const Point& first_point() const override { return this->entities.front()->first_point(); }
|
||||
const Point& last_point() const override { return this->entities.back()->last_point(); }
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
|
||||
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing.
|
||||
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
|
||||
// Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill.
|
||||
void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override;
|
||||
Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const
|
||||
{ Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; }
|
||||
Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const
|
||||
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
|
||||
size_t items_count() const;
|
||||
/// Returns a flattened copy of this ExtrusionEntityCollection. That is, all of the items in its entities vector are not collections.
|
||||
/// You should be iterating over flatten().entities if you are interested in the underlying ExtrusionEntities (and don't care about hierarchy).
|
||||
/// \param preserve_ordering Flag to method that will flatten if and only if the underlying collection is sortable when True (default: False).
|
||||
ExtrusionEntityCollection flatten(bool preserve_ordering = false) const;
|
||||
double min_mm3_per_mm() const override;
|
||||
double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; }
|
||||
|
||||
// Following methods shall never be called on an ExtrusionEntityCollection.
|
||||
Polyline as_polyline() const override {
|
||||
throw Slic3r::RuntimeError("Calling as_polyline() on a ExtrusionEntityCollection");
|
||||
return Polyline();
|
||||
};
|
||||
|
||||
void collect_polylines(Polylines &dst) const override {
|
||||
for (ExtrusionEntity* extrusion_entity : this->entities)
|
||||
extrusion_entity->collect_polylines(dst);
|
||||
}
|
||||
|
||||
void collect_points(Points &dst) const override {
|
||||
for (ExtrusionEntity* extrusion_entity : this->entities)
|
||||
extrusion_entity->collect_points(dst);
|
||||
}
|
||||
|
||||
double length() const override {
|
||||
throw Slic3r::RuntimeError("Calling length() on a ExtrusionEntityCollection");
|
||||
return 0.;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
1030
src/libslic3r/ExtrusionSimulator.cpp
Normal file
1030
src/libslic3r/ExtrusionSimulator.cpp
Normal file
File diff suppressed because it is too large
Load diff
59
src/libslic3r/ExtrusionSimulator.hpp
Normal file
59
src/libslic3r/ExtrusionSimulator.hpp
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#ifndef slic3r_ExtrusionSimulator_hpp_
|
||||
#define slic3r_ExtrusionSimulator_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "ExtrusionEntity.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum ExtrusionSimulationType
|
||||
{
|
||||
ExtrusionSimulationSimple,
|
||||
ExtrusionSimulationDontSpread,
|
||||
ExtrisopmSimulationSpreadNotOverfilled,
|
||||
ExtrusionSimulationSpreadFull,
|
||||
ExtrusionSimulationSpreadExcess
|
||||
};
|
||||
|
||||
// An opaque class, to keep the boost stuff away from the header.
|
||||
class ExtrusionSimulatorImpl;
|
||||
|
||||
class ExtrusionSimulator
|
||||
{
|
||||
public:
|
||||
ExtrusionSimulator();
|
||||
~ExtrusionSimulator();
|
||||
|
||||
// Size of the image, that will be returned by image_ptr().
|
||||
// The image may be bigger than the viewport as many graphics drivers
|
||||
// expect the size of a texture to be rounded to a power of two.
|
||||
void set_image_size(const Point &image_size);
|
||||
// Which part of the image shall be rendered to?
|
||||
void set_viewport(const BoundingBox &viewport);
|
||||
// Shift and scale of the rendered extrusion paths into the viewport.
|
||||
void set_bounding_box(const BoundingBox &bbox);
|
||||
|
||||
// Reset the extrusion accumulator to zero for all buckets.
|
||||
void reset_accumulator();
|
||||
// Paint a thick path into an extrusion buffer.
|
||||
// A simple implementation is provided now, splatting a rectangular extrusion for each linear segment.
|
||||
// In the future, spreading and suqashing of a material will be simulated.
|
||||
void extrude_to_accumulator(const ExtrusionPath &path, const Point &shift, ExtrusionSimulationType simulationType);
|
||||
// Evaluate the content of the accumulator and paint it into the viewport.
|
||||
// After this call the image_ptr() call will return a valid image.
|
||||
void evaluate_accumulator(ExtrusionSimulationType simulationType);
|
||||
// An RGBA image of image_size, to be loaded into a GPU texture.
|
||||
const void* image_ptr() const;
|
||||
|
||||
private:
|
||||
Point image_size;
|
||||
BoundingBox viewport;
|
||||
BoundingBox bbox;
|
||||
|
||||
ExtrusionSimulatorImpl *pimpl;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* slic3r_ExtrusionSimulator_hpp_ */
|
||||
89
src/libslic3r/FaceDetector.cpp
Normal file
89
src/libslic3r/FaceDetector.cpp
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#include "FaceDetector.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "SLA/IndexedMesh.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
static const double BBOX_OFFSET = 2.0;
|
||||
void FaceDetector::detect_exterior_face()
|
||||
{
|
||||
struct MeshFacetRange {
|
||||
MeshFacetRange(TriangleMesh* tm, uint32_t facet_begin, uint32_t facet_end) :
|
||||
tm(tm), facet_begin(facet_begin), facet_end(facet_end) {}
|
||||
MeshFacetRange() : tm(nullptr), facet_begin(0), facet_end(0) {}
|
||||
TriangleMesh* tm;
|
||||
uint32_t facet_begin;
|
||||
uint32_t facet_end;
|
||||
};
|
||||
|
||||
TriangleMesh object_mesh;
|
||||
std::vector<MeshFacetRange> volume_facet_ranges;
|
||||
for (int i = 0; i < m_meshes.size(); i++) {
|
||||
TriangleMesh vol_mesh = m_meshes[i];
|
||||
volume_facet_ranges.emplace_back(&m_meshes[i], object_mesh.stats().number_of_facets, object_mesh.stats().number_of_facets + vol_mesh.stats().number_of_facets);
|
||||
|
||||
vol_mesh.transform(m_transfos[i]);
|
||||
object_mesh.merge(std::move(vol_mesh));
|
||||
}
|
||||
|
||||
sla::IndexedMesh indexed_mesh(object_mesh);
|
||||
BoundingBoxf3 bbox = object_mesh.bounding_box();
|
||||
bbox.offset(BBOX_OFFSET);
|
||||
|
||||
std::unordered_set<size_t> hit_face_indices;
|
||||
|
||||
// x-axis rays
|
||||
for (double y = bbox.min.y(); y < bbox.max.y(); y += m_sample_interval) {
|
||||
for (double z = bbox.min.z(); z < bbox.max.z(); z += m_sample_interval) {
|
||||
auto hit_result = indexed_mesh.query_ray_hit({ bbox.min.x(), y, z }, { 1.0, 0.0, 0.0 });
|
||||
if (hit_result.is_hit())
|
||||
hit_face_indices.insert(hit_result.face());
|
||||
|
||||
hit_result = indexed_mesh.query_ray_hit({ bbox.max.x(), y, z }, { -1.0, 0.0, 0.0 });
|
||||
if (hit_result.is_hit())
|
||||
hit_face_indices.insert(hit_result.face());
|
||||
}
|
||||
}
|
||||
|
||||
// y-axis rays
|
||||
for (double x = bbox.min.x(); x < bbox.max.x(); x += m_sample_interval) {
|
||||
for (double z = bbox.min.z(); z < bbox.max.z(); z += m_sample_interval) {
|
||||
auto hit_result = indexed_mesh.query_ray_hit({ x, bbox.min.y(), z }, { 0.0, 1.0, 0.0 });
|
||||
if (hit_result.is_hit())
|
||||
hit_face_indices.insert(hit_result.face());
|
||||
|
||||
hit_result = indexed_mesh.query_ray_hit({ x, bbox.max.y(), z }, { 0.0, -1.0, 0.0 });
|
||||
if (hit_result.is_hit())
|
||||
hit_face_indices.insert(hit_result.face());
|
||||
}
|
||||
}
|
||||
|
||||
// z-axis rays
|
||||
for (double x = bbox.min.x(); x < bbox.max.x(); x += m_sample_interval) {
|
||||
for (double y = bbox.min.y(); y < bbox.max.y(); y += m_sample_interval) {
|
||||
auto hit_result = indexed_mesh.query_ray_hit({ x, y, bbox.min.z() }, { 0.0, 0.0, 1.0 });
|
||||
if (hit_result.is_hit())
|
||||
hit_face_indices.insert(hit_result.face());
|
||||
|
||||
hit_result = indexed_mesh.query_ray_hit({ x, y, bbox.max.z() }, { 0.0, 0.0, -1.0 });
|
||||
if (hit_result.is_hit())
|
||||
hit_face_indices.insert(hit_result.face());
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t facet_idx : hit_face_indices) {
|
||||
TriangleMesh* tm = nullptr;
|
||||
uint32_t vol_facet_idx = 0;
|
||||
for (auto range : volume_facet_ranges) {
|
||||
if (facet_idx >= range.facet_begin && facet_idx < range.facet_end) {
|
||||
tm = range.tm;
|
||||
vol_facet_idx = facet_idx - range.facet_begin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tm->its.get_property(vol_facet_idx).type = EnumFaceTypes::eExteriorAppearance;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
25
src/libslic3r/FaceDetector.hpp
Normal file
25
src/libslic3r/FaceDetector.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef slic3r_FaceDetector_hpp_
|
||||
#define slic3r_FaceDetector_hpp_
|
||||
|
||||
#include "Point.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
class ModelObject;
|
||||
class TriangleMesh;
|
||||
|
||||
class FaceDetector {
|
||||
public:
|
||||
FaceDetector(std::vector<TriangleMesh>& tms, std::vector<Transform3d>& transfos, double sample_interval)
|
||||
: m_meshes(tms), m_transfos(transfos), m_sample_interval(sample_interval) {}
|
||||
|
||||
void detect_exterior_face();
|
||||
|
||||
private:
|
||||
std::vector<TriangleMesh>& m_meshes;
|
||||
std::vector<Transform3d>& m_transfos;
|
||||
double m_sample_interval;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // #ifndef slic3r_FaceDetector_hpp_
|
||||
52
src/libslic3r/FileParserError.hpp
Normal file
52
src/libslic3r/FileParserError.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef slic3r_FileParserError_hpp_
|
||||
#define slic3r_FileParserError_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
|
||||
#include <string>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Generic file parser error, mostly copied from boost::property_tree::file_parser_error
|
||||
class file_parser_error: public Slic3r::RuntimeError
|
||||
{
|
||||
public:
|
||||
file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) :
|
||||
Slic3r::RuntimeError(format_what(msg, file, line)),
|
||||
m_message(msg), m_filename(file), m_line(line) {}
|
||||
file_parser_error(const std::string &msg, const boost::filesystem::path &file, unsigned long line = 0) :
|
||||
Slic3r::RuntimeError(format_what(msg, file.string(), line)),
|
||||
m_message(msg), m_filename(file.string()), m_line(line) {}
|
||||
// gcc 3.4.2 complains about lack of throw specifier on compiler
|
||||
// generated dtor
|
||||
~file_parser_error() throw() {}
|
||||
|
||||
// Get error message (without line and file - use what() to get full message)
|
||||
std::string message() const { return m_message; }
|
||||
// Get error filename
|
||||
std::string filename() const { return m_filename; }
|
||||
// Get error line number
|
||||
unsigned long line() const { return m_line; }
|
||||
|
||||
private:
|
||||
std::string m_message;
|
||||
std::string m_filename;
|
||||
unsigned long m_line;
|
||||
|
||||
// Format error message to be returned by Slic3r::RuntimeError::what()
|
||||
static std::string format_what(const std::string &msg, const std::string &file, unsigned long l)
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << (file.empty() ? "<unspecified file>" : file.c_str());
|
||||
if (l > 0)
|
||||
stream << '(' << l << ')';
|
||||
stream << ": " << msg;
|
||||
return stream.str();
|
||||
}
|
||||
};
|
||||
|
||||
}; // Slic3r
|
||||
|
||||
#endif // slic3r_FileParserError_hpp_
|
||||
673
src/libslic3r/Fill/Fill.cpp
Normal file
673
src/libslic3r/Fill/Fill.cpp
Normal file
|
|
@ -0,0 +1,673 @@
|
|||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <memory>
|
||||
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../Layer.hpp"
|
||||
#include "../Print.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include "FillBase.hpp"
|
||||
#include "FillRectilinear.hpp"
|
||||
|
||||
#define NARROW_INFILL_AREA_THRESHOLD 3
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct SurfaceFillParams
|
||||
{
|
||||
// Zero based extruder ID.
|
||||
unsigned int extruder = 0;
|
||||
// Infill pattern, adjusted for the density etc.
|
||||
InfillPattern pattern = InfillPattern(0);
|
||||
|
||||
// FillBase
|
||||
// in unscaled coordinates
|
||||
coordf_t spacing = 0.;
|
||||
// infill / perimeter overlap, in unscaled coordinates
|
||||
coordf_t overlap = 0.;
|
||||
// Angle as provided by the region config, in radians.
|
||||
float angle = 0.f;
|
||||
// Is bridging used for this fill? Bridging parameters may be used even if this->flow.bridge() is not set.
|
||||
bool bridge;
|
||||
// Non-negative for a bridge.
|
||||
float bridge_angle = 0.f;
|
||||
|
||||
// FillParams
|
||||
float density = 0.f;
|
||||
// Don't adjust spacing to fill the space evenly.
|
||||
// bool dont_adjust = false;
|
||||
// Length of the infill anchor along the perimeter line.
|
||||
// 1000mm is roughly the maximum length line that fits into a 32bit coord_t.
|
||||
float anchor_length = 1000.f;
|
||||
float anchor_length_max = 1000.f;
|
||||
|
||||
// width, height of extrusion, nozzle diameter, is bridge
|
||||
// For the output, for fill generator.
|
||||
Flow flow;
|
||||
|
||||
// For the output
|
||||
ExtrusionRole extrusion_role = ExtrusionRole(0);
|
||||
|
||||
// Various print settings?
|
||||
|
||||
// Index of this entry in a linear vector.
|
||||
size_t idx = 0;
|
||||
|
||||
|
||||
bool operator<(const SurfaceFillParams &rhs) const {
|
||||
#define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false;
|
||||
#define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false;
|
||||
|
||||
// Sort first by decreasing bridging angle, so that the bridges are processed with priority when trimming one layer by the other.
|
||||
if (this->bridge_angle > rhs.bridge_angle) return true;
|
||||
if (this->bridge_angle < rhs.bridge_angle) return false;
|
||||
|
||||
RETURN_COMPARE_NON_EQUAL(extruder);
|
||||
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, pattern);
|
||||
RETURN_COMPARE_NON_EQUAL(spacing);
|
||||
RETURN_COMPARE_NON_EQUAL(overlap);
|
||||
RETURN_COMPARE_NON_EQUAL(angle);
|
||||
RETURN_COMPARE_NON_EQUAL(density);
|
||||
// RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust);
|
||||
RETURN_COMPARE_NON_EQUAL(anchor_length);
|
||||
RETURN_COMPARE_NON_EQUAL(anchor_length_max);
|
||||
RETURN_COMPARE_NON_EQUAL(flow.width());
|
||||
RETURN_COMPARE_NON_EQUAL(flow.height());
|
||||
RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter());
|
||||
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, bridge);
|
||||
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, extrusion_role);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator==(const SurfaceFillParams &rhs) const {
|
||||
return this->extruder == rhs.extruder &&
|
||||
this->pattern == rhs.pattern &&
|
||||
this->spacing == rhs.spacing &&
|
||||
this->overlap == rhs.overlap &&
|
||||
this->angle == rhs.angle &&
|
||||
this->bridge == rhs.bridge &&
|
||||
// this->bridge_angle == rhs.bridge_angle &&
|
||||
this->density == rhs.density &&
|
||||
// this->dont_adjust == rhs.dont_adjust &&
|
||||
this->anchor_length == rhs.anchor_length &&
|
||||
this->anchor_length_max == rhs.anchor_length_max &&
|
||||
this->flow == rhs.flow &&
|
||||
this->extrusion_role == rhs.extrusion_role;
|
||||
}
|
||||
};
|
||||
|
||||
struct SurfaceFill {
|
||||
SurfaceFill(const SurfaceFillParams& params) : region_id(size_t(-1)), surface(stCount, ExPolygon()), params(params) {}
|
||||
|
||||
size_t region_id;
|
||||
Surface surface;
|
||||
ExPolygons expolygons;
|
||||
SurfaceFillParams params;
|
||||
};
|
||||
|
||||
// BBS: used to judge whether the internal solid infill area is narrow
|
||||
static bool is_narrow_infill_area(const ExPolygon& expolygon)
|
||||
{
|
||||
ExPolygons offsets = offset_ex(expolygon, -scale_(NARROW_INFILL_AREA_THRESHOLD));
|
||||
if (offsets.empty())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
{
|
||||
std::vector<SurfaceFill> surface_fills;
|
||||
|
||||
// Fill in a map of a region & surface to SurfaceFillParams.
|
||||
std::set<SurfaceFillParams> set_surface_params;
|
||||
std::vector<std::vector<const SurfaceFillParams*>> region_to_surface_params(layer.regions().size(), std::vector<const SurfaceFillParams*>());
|
||||
SurfaceFillParams params;
|
||||
bool has_internal_voids = false;
|
||||
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
|
||||
const LayerRegion &layerm = *layer.regions()[region_id];
|
||||
region_to_surface_params[region_id].assign(layerm.fill_surfaces.size(), nullptr);
|
||||
for (const Surface &surface : layerm.fill_surfaces.surfaces)
|
||||
if (surface.surface_type == stInternalVoid)
|
||||
has_internal_voids = true;
|
||||
else {
|
||||
const PrintRegionConfig ®ion_config = layerm.region().config();
|
||||
FlowRole extrusion_role = surface.is_top() ? frTopSolidInfill : (surface.is_solid() ? frSolidInfill : frInfill);
|
||||
bool is_bridge = layer.id() > 0 && surface.is_bridge();
|
||||
params.extruder = layerm.region().extruder(extrusion_role);
|
||||
params.pattern = region_config.sparse_infill_pattern.value;
|
||||
params.density = float(region_config.sparse_infill_density);
|
||||
|
||||
if (surface.is_solid()) {
|
||||
params.density = 100.f;
|
||||
//FIXME for non-thick bridges, shall we allow a bottom surface pattern?
|
||||
params.pattern = (surface.is_external() && ! is_bridge) ?
|
||||
(surface.is_top() ? region_config.top_surface_pattern.value : region_config.bottom_surface_pattern.value) :
|
||||
region_config.top_surface_pattern == ipMonotonic ? ipMonotonic : ipRectilinear;
|
||||
} else if (params.density <= 0)
|
||||
continue;
|
||||
|
||||
params.extrusion_role =
|
||||
is_bridge ?
|
||||
erBridgeInfill :
|
||||
(surface.is_solid() ?
|
||||
(surface.is_top() ? erTopSolidInfill : (surface.is_bottom()? erBottomSurface : erSolidInfill)) :
|
||||
erInternalInfill);
|
||||
params.bridge_angle = float(surface.bridge_angle);
|
||||
params.angle = float(Geometry::deg2rad(region_config.infill_direction.value));
|
||||
|
||||
// Calculate the actual flow we'll be using for this infill.
|
||||
params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
|
||||
params.flow = params.bridge ?
|
||||
//BBS: always enable thick bridge for internal bridge
|
||||
layerm.bridging_flow(extrusion_role, (surface.is_bridge() && !surface.is_external()) || g_config_thick_bridges) :
|
||||
layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
|
||||
|
||||
// Calculate flow spacing for infill pattern generation.
|
||||
if (surface.is_solid() || is_bridge) {
|
||||
params.spacing = params.flow.spacing();
|
||||
// Don't limit anchor length for solid or bridging infill.
|
||||
params.anchor_length = 1000.f;
|
||||
params.anchor_length_max = 1000.f;
|
||||
} else {
|
||||
// Internal infill. Calculating infill line spacing independent of the current layer height and 1st layer status,
|
||||
// so that internall infill will be aligned over all layers of the current region.
|
||||
params.spacing = layerm.region().flow(*layer.object(), frInfill, layer.object()->config().layer_height, false).spacing();
|
||||
// Anchor a sparse infill to inner perimeters with the following anchor length:
|
||||
params.anchor_length = float(Fill::infill_anchor * 0.01 * params.spacing);
|
||||
params.anchor_length_max = Fill::infill_anchor_max;
|
||||
params.anchor_length = std::min(params.anchor_length, params.anchor_length_max);
|
||||
}
|
||||
|
||||
auto it_params = set_surface_params.find(params);
|
||||
if (it_params == set_surface_params.end())
|
||||
it_params = set_surface_params.insert(it_params, params);
|
||||
region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()] = &(*it_params);
|
||||
}
|
||||
}
|
||||
|
||||
surface_fills.reserve(set_surface_params.size());
|
||||
for (const SurfaceFillParams ¶ms : set_surface_params) {
|
||||
const_cast<SurfaceFillParams&>(params).idx = surface_fills.size();
|
||||
surface_fills.emplace_back(params);
|
||||
}
|
||||
|
||||
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
|
||||
const LayerRegion &layerm = *layer.regions()[region_id];
|
||||
for (const Surface &surface : layerm.fill_surfaces.surfaces)
|
||||
if (surface.surface_type != stInternalVoid) {
|
||||
const SurfaceFillParams *params = region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()];
|
||||
if (params != nullptr) {
|
||||
SurfaceFill &fill = surface_fills[params->idx];
|
||||
if (fill.region_id == size_t(-1)) {
|
||||
fill.region_id = region_id;
|
||||
fill.surface = surface;
|
||||
fill.expolygons.emplace_back(std::move(fill.surface.expolygon));
|
||||
} else
|
||||
fill.expolygons.emplace_back(surface.expolygon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Polygons all_polygons;
|
||||
for (SurfaceFill &fill : surface_fills)
|
||||
if (! fill.expolygons.empty()) {
|
||||
if (fill.expolygons.size() > 1 || ! all_polygons.empty()) {
|
||||
Polygons polys = to_polygons(std::move(fill.expolygons));
|
||||
// Make a union of polygons, use a safety offset, subtract the preceding polygons.
|
||||
// Bridges are processed first (see SurfaceFill::operator<())
|
||||
fill.expolygons = all_polygons.empty() ? union_safety_offset_ex(polys) : diff_ex(polys, all_polygons, ApplySafetyOffset::Yes);
|
||||
append(all_polygons, std::move(polys));
|
||||
} else if (&fill != &surface_fills.back())
|
||||
append(all_polygons, to_polygons(fill.expolygons));
|
||||
}
|
||||
}
|
||||
|
||||
// we need to detect any narrow surfaces that might collapse
|
||||
// when adding spacing below
|
||||
// such narrow surfaces are often generated in sloping walls
|
||||
// by bridge_over_infill() and combine_infill() as a result of the
|
||||
// subtraction of the combinable area from the layer infill area,
|
||||
// which leaves small areas near the perimeters
|
||||
// we are going to grow such regions by overlapping them with the void (if any)
|
||||
// TODO: detect and investigate whether there could be narrow regions without
|
||||
// any void neighbors
|
||||
if (has_internal_voids) {
|
||||
// Internal voids are generated only if "infill_only_where_needed" or "infill_every_layers" are active.
|
||||
coord_t distance_between_surfaces = 0;
|
||||
Polygons surfaces_polygons;
|
||||
Polygons voids;
|
||||
int region_internal_infill = -1;
|
||||
int region_solid_infill = -1;
|
||||
int region_some_infill = -1;
|
||||
for (SurfaceFill &surface_fill : surface_fills)
|
||||
if (! surface_fill.expolygons.empty()) {
|
||||
distance_between_surfaces = std::max(distance_between_surfaces, surface_fill.params.flow.scaled_spacing());
|
||||
append((surface_fill.surface.surface_type == stInternalVoid) ? voids : surfaces_polygons, to_polygons(surface_fill.expolygons));
|
||||
if (surface_fill.surface.surface_type == stInternalSolid)
|
||||
region_internal_infill = (int)surface_fill.region_id;
|
||||
if (surface_fill.surface.is_solid())
|
||||
region_solid_infill = (int)surface_fill.region_id;
|
||||
if (surface_fill.surface.surface_type != stInternalVoid)
|
||||
region_some_infill = (int)surface_fill.region_id;
|
||||
}
|
||||
if (! voids.empty() && ! surfaces_polygons.empty()) {
|
||||
// First clip voids by the printing polygons, as the voids were ignored by the loop above during mutual clipping.
|
||||
voids = diff(voids, surfaces_polygons);
|
||||
// Corners of infill regions, which would not be filled with an extrusion path with a radius of distance_between_surfaces/2
|
||||
Polygons collapsed = diff(
|
||||
surfaces_polygons,
|
||||
opening(surfaces_polygons, float(distance_between_surfaces /2), float(distance_between_surfaces / 2 + ClipperSafetyOffset)));
|
||||
//FIXME why the voids are added to collapsed here? First it is expensive, second the result may lead to some unwanted regions being
|
||||
// added if two offsetted void regions merge.
|
||||
// polygons_append(voids, collapsed);
|
||||
ExPolygons extensions = intersection_ex(expand(collapsed, float(distance_between_surfaces)), voids, ApplySafetyOffset::Yes);
|
||||
// Now find an internal infill SurfaceFill to add these extrusions to.
|
||||
SurfaceFill *internal_solid_fill = nullptr;
|
||||
unsigned int region_id = 0;
|
||||
if (region_internal_infill != -1)
|
||||
region_id = region_internal_infill;
|
||||
else if (region_solid_infill != -1)
|
||||
region_id = region_solid_infill;
|
||||
else if (region_some_infill != -1)
|
||||
region_id = region_some_infill;
|
||||
const LayerRegion& layerm = *layer.regions()[region_id];
|
||||
for (SurfaceFill &surface_fill : surface_fills)
|
||||
if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) {
|
||||
internal_solid_fill = &surface_fill;
|
||||
break;
|
||||
}
|
||||
if (internal_solid_fill == nullptr) {
|
||||
// Produce another solid fill.
|
||||
params.extruder = layerm.region().extruder(frSolidInfill);
|
||||
params.pattern = layerm.region().config().top_surface_pattern == ipMonotonic ? ipMonotonic : ipRectilinear;
|
||||
params.density = 100.f;
|
||||
params.extrusion_role = erInternalInfill;
|
||||
params.angle = float(Geometry::deg2rad(layerm.region().config().infill_direction.value));
|
||||
// calculate the actual flow we'll be using for this infill
|
||||
params.flow = layerm.flow(frSolidInfill);
|
||||
params.spacing = params.flow.spacing();
|
||||
surface_fills.emplace_back(params);
|
||||
surface_fills.back().surface.surface_type = stInternalSolid;
|
||||
surface_fills.back().surface.thickness = layer.height;
|
||||
surface_fills.back().expolygons = std::move(extensions);
|
||||
} else {
|
||||
append(extensions, std::move(internal_solid_fill->expolygons));
|
||||
internal_solid_fill->expolygons = union_ex(extensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BBS: detect narrow internal solid infill area and use ipConcentricGapFill pattern instead
|
||||
if (layer.object()->config().detect_narrow_internal_solid_infill) {
|
||||
size_t surface_fills_size = surface_fills.size();
|
||||
for (size_t i = 0; i < surface_fills_size; i++) {
|
||||
if (surface_fills[i].surface.surface_type != stInternalSolid)
|
||||
continue;
|
||||
|
||||
size_t expolygons_size = surface_fills[i].expolygons.size();
|
||||
std::vector<size_t> narrow_expolygons_index;
|
||||
narrow_expolygons_index.reserve(expolygons_size);
|
||||
// BBS: get the index list of narrow expolygon
|
||||
for (size_t j = 0; j < expolygons_size; j++)
|
||||
if (is_narrow_infill_area(surface_fills[i].expolygons[j]))
|
||||
narrow_expolygons_index.push_back(j);
|
||||
|
||||
if (narrow_expolygons_index.size() == 0) {
|
||||
// BBS: has no narrow expolygon
|
||||
continue;
|
||||
}
|
||||
else if (narrow_expolygons_index.size() == expolygons_size) {
|
||||
// BBS: all expolygons are narrow, directly change the fill pattern
|
||||
surface_fills[i].params.pattern = ipConcentricGapFill;
|
||||
}
|
||||
else {
|
||||
// BBS: some expolygons are narrow, spilit surface_fills[i] and rearrange the expolygons
|
||||
params = surface_fills[i].params;
|
||||
params.pattern = ipConcentricGapFill;
|
||||
surface_fills.emplace_back(params);
|
||||
surface_fills.back().region_id = surface_fills[i].region_id;
|
||||
surface_fills.back().surface.surface_type = stInternalSolid;
|
||||
surface_fills.back().surface.thickness = surface_fills[i].surface.thickness;
|
||||
for (size_t j = 0; j < narrow_expolygons_index.size(); j++) {
|
||||
// BBS: move the narrow expolygons to new surface_fills.back();
|
||||
surface_fills.back().expolygons.emplace_back(std::move(surface_fills[i].expolygons[narrow_expolygons_index[j]]));
|
||||
}
|
||||
for (int j = narrow_expolygons_index.size() - 1; j >= 0; j--) {
|
||||
// BBS: delete the narrow expolygons from old surface_fills
|
||||
surface_fills[i].expolygons.erase(surface_fills[i].expolygons.begin() + narrow_expolygons_index[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return surface_fills;
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill> &fills)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const auto &fill : fills)
|
||||
for (const auto &expoly : fill.expolygons)
|
||||
bbox.merge(get_extents(expoly));
|
||||
Point legend_size = export_surface_type_legend_to_svg_box_size();
|
||||
Point legend_pos(bbox.min(0), bbox.max(1));
|
||||
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
|
||||
|
||||
SVG svg(path, bbox);
|
||||
const float transparency = 0.5f;
|
||||
for (const auto &fill : fills)
|
||||
for (const auto &expoly : fill.expolygons)
|
||||
svg.draw(expoly, surface_type_to_color_name(fill.surface.surface_type), transparency);
|
||||
export_surface_type_legend_to_svg(svg, legend_pos);
|
||||
svg.Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
// friend to Layer
|
||||
void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree)
|
||||
{
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
layerm->fills.clear();
|
||||
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
// this->export_region_fill_surfaces_to_svg_debug("10_fill-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
std::vector<SurfaceFill> surface_fills = group_fills(*this);
|
||||
const Slic3r::BoundingBox bbox = this->object()->bounding_box();
|
||||
const auto resolution = this->object()->print()->config().resolution.value;
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
export_group_fills_to_svg(debug_out_path("Layer-fill_surfaces-10_fill-final-%d.svg", iRun ++).c_str(), surface_fills);
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
for (SurfaceFill &surface_fill : surface_fills) {
|
||||
// Create the filler object.
|
||||
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
|
||||
f->set_bounding_box(bbox);
|
||||
f->layer_id = this->id();
|
||||
f->z = this->print_z;
|
||||
f->angle = surface_fill.params.angle;
|
||||
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
|
||||
// calculate flow spacing for infill pattern generation
|
||||
bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge;
|
||||
double link_max_length = 0.;
|
||||
if (! surface_fill.params.bridge) {
|
||||
#if 0
|
||||
link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
|
||||
// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
|
||||
#else
|
||||
if (surface_fill.params.density > 80.) // 80%
|
||||
link_max_length = 3. * f->spacing;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Maximum length of the perimeter segment linking two infill lines.
|
||||
f->link_max_length = (coord_t)scale_(link_max_length);
|
||||
// Used by the concentric infill pattern to clip the loops to create extrusion paths.
|
||||
f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
|
||||
|
||||
// apply half spacing using this flow's own spacing and generate infill
|
||||
FillParams params;
|
||||
params.density = float(0.01 * surface_fill.params.density);
|
||||
params.dont_adjust = false; // surface_fill.params.dont_adjust;
|
||||
params.anchor_length = surface_fill.params.anchor_length;
|
||||
params.anchor_length_max = surface_fill.params.anchor_length_max;
|
||||
params.resolution = resolution;
|
||||
|
||||
// BBS
|
||||
params.flow = surface_fill.params.flow;
|
||||
params.extrusion_role = surface_fill.params.extrusion_role;
|
||||
params.using_internal_flow = using_internal_flow;
|
||||
params.no_extrusion_overlap = surface_fill.params.overlap;
|
||||
|
||||
LayerRegion* layerm = this->m_regions[surface_fill.region_id];
|
||||
for (ExPolygon& expoly : surface_fill.expolygons) {
|
||||
f->no_overlap_expolygons = intersection_ex(layerm->fill_no_overlap_expolygons, ExPolygons() = { expoly });
|
||||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
||||
f->spacing = surface_fill.params.spacing;
|
||||
surface_fill.surface.expolygon = std::move(expoly);
|
||||
// BBS: make fill
|
||||
f->fill_surface_extrusion(&surface_fill.surface,
|
||||
params,
|
||||
m_regions[surface_fill.region_id]->fills.entities);
|
||||
}
|
||||
}
|
||||
|
||||
// add thin fill regions
|
||||
// Unpacks the collection, creates multiple collections per path.
|
||||
// The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection.
|
||||
// Why the paths are unpacked?
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
for (const ExtrusionEntity *thin_fill : layerm->thin_fills.entities) {
|
||||
ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection());
|
||||
layerm->fills.entities.push_back(&collection);
|
||||
collection.entities.push_back(thin_fill->clone());
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
for (size_t i = 0; i < layerm->fills.entities.size(); ++ i)
|
||||
assert(dynamic_cast<ExtrusionEntityCollection*>(layerm->fills.entities[i]) != nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Create ironing extrusions over top surfaces.
|
||||
void Layer::make_ironing()
|
||||
{
|
||||
// LayerRegion::slices contains surfaces marked with SurfaceType.
|
||||
// Here we want to collect top surfaces extruded with the same extruder.
|
||||
// A surface will be ironed with the same extruder to not contaminate the print with another material leaking from the nozzle.
|
||||
|
||||
// First classify regions based on the extruder used.
|
||||
struct IroningParams {
|
||||
int extruder = -1;
|
||||
bool just_infill = false;
|
||||
// Spacing of the ironing lines, also to calculate the extrusion flow from.
|
||||
double line_spacing;
|
||||
// Height of the extrusion, to calculate the extrusion flow from.
|
||||
double height;
|
||||
double speed;
|
||||
double angle;
|
||||
|
||||
bool operator<(const IroningParams &rhs) const {
|
||||
if (this->extruder < rhs.extruder)
|
||||
return true;
|
||||
if (this->extruder > rhs.extruder)
|
||||
return false;
|
||||
if (int(this->just_infill) < int(rhs.just_infill))
|
||||
return true;
|
||||
if (int(this->just_infill) > int(rhs.just_infill))
|
||||
return false;
|
||||
if (this->line_spacing < rhs.line_spacing)
|
||||
return true;
|
||||
if (this->line_spacing > rhs.line_spacing)
|
||||
return false;
|
||||
if (this->height < rhs.height)
|
||||
return true;
|
||||
if (this->height > rhs.height)
|
||||
return false;
|
||||
if (this->speed < rhs.speed)
|
||||
return true;
|
||||
if (this->speed > rhs.speed)
|
||||
return false;
|
||||
if (this->angle < rhs.angle)
|
||||
return true;
|
||||
if (this->angle > rhs.angle)
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator==(const IroningParams &rhs) const {
|
||||
return this->extruder == rhs.extruder && this->just_infill == rhs.just_infill &&
|
||||
this->line_spacing == rhs.line_spacing && this->height == rhs.height && this->speed == rhs.speed &&
|
||||
this->angle == rhs.angle;
|
||||
}
|
||||
|
||||
LayerRegion *layerm = nullptr;
|
||||
|
||||
// IdeaMaker: ironing
|
||||
// ironing flowrate (5% percent)
|
||||
// ironing speed (10 mm/sec)
|
||||
|
||||
// Kisslicer:
|
||||
// iron off, Sweep, Group
|
||||
// ironing speed: 15 mm/sec
|
||||
|
||||
// Cura:
|
||||
// Pattern (zig-zag / concentric)
|
||||
// line spacing (0.1mm)
|
||||
// flow: from normal layer height. 10%
|
||||
// speed: 20 mm/sec
|
||||
};
|
||||
|
||||
std::vector<IroningParams> by_extruder;
|
||||
double default_layer_height = this->object()->config().layer_height;
|
||||
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
if (! layerm->slices.empty()) {
|
||||
IroningParams ironing_params;
|
||||
const PrintRegionConfig &config = layerm->region().config();
|
||||
if (config.ironing_type != IroningType::NoIroning &&
|
||||
(config.ironing_type == IroningType::AllSolid ||
|
||||
(config.top_shell_layers > 0 &&
|
||||
(config.ironing_type == IroningType::TopSurfaces ||
|
||||
(config.ironing_type == IroningType::TopmostOnly && layerm->layer()->upper_layer == nullptr))))) {
|
||||
if (config.wall_filament == config.solid_infill_filament || config.wall_loops == 0) {
|
||||
// Iron the whole face.
|
||||
ironing_params.extruder = config.solid_infill_filament;
|
||||
} else {
|
||||
// Iron just the infill.
|
||||
ironing_params.extruder = config.solid_infill_filament;
|
||||
}
|
||||
}
|
||||
if (ironing_params.extruder != -1) {
|
||||
//TODO just_infill is currently not used.
|
||||
ironing_params.just_infill = false;
|
||||
ironing_params.line_spacing = config.ironing_spacing;
|
||||
ironing_params.height = default_layer_height * 0.01 * config.ironing_flow;
|
||||
ironing_params.speed = config.ironing_speed;
|
||||
ironing_params.angle = config.infill_direction * M_PI / 180.;
|
||||
ironing_params.layerm = layerm;
|
||||
by_extruder.emplace_back(ironing_params);
|
||||
}
|
||||
}
|
||||
std::sort(by_extruder.begin(), by_extruder.end());
|
||||
|
||||
FillRectilinear fill;
|
||||
FillParams fill_params;
|
||||
fill.set_bounding_box(this->object()->bounding_box());
|
||||
fill.layer_id = this->id();
|
||||
fill.z = this->print_z;
|
||||
fill.overlap = 0;
|
||||
fill_params.density = 1.;
|
||||
fill_params.monotonic = true;
|
||||
|
||||
for (size_t i = 0; i < by_extruder.size();) {
|
||||
// Find span of regions equivalent to the ironing operation.
|
||||
IroningParams &ironing_params = by_extruder[i];
|
||||
size_t j = i;
|
||||
for (++ j; j < by_extruder.size() && ironing_params == by_extruder[j]; ++ j) ;
|
||||
|
||||
// Create the ironing extrusions for regions <i, j)
|
||||
ExPolygons ironing_areas;
|
||||
double nozzle_dmr = this->object()->print()->config().nozzle_diameter.get_at(ironing_params.extruder - 1);
|
||||
if (ironing_params.just_infill) {
|
||||
//TODO just_infill is currently not used.
|
||||
// Just infill.
|
||||
} else {
|
||||
// Infill and perimeter.
|
||||
// Merge top surfaces with the same ironing parameters.
|
||||
Polygons polys;
|
||||
Polygons infills;
|
||||
for (size_t k = i; k < j; ++ k) {
|
||||
const IroningParams &ironing_params = by_extruder[k];
|
||||
const PrintRegionConfig ®ion_config = ironing_params.layerm->region().config();
|
||||
bool iron_everything = region_config.ironing_type == IroningType::AllSolid;
|
||||
bool iron_completely = iron_everything;
|
||||
if (iron_everything) {
|
||||
// Check whether there is any non-solid hole in the regions.
|
||||
bool internal_infill_solid = region_config.sparse_infill_density.value > 95.;
|
||||
for (const Surface &surface : ironing_params.layerm->fill_surfaces.surfaces)
|
||||
if ((! internal_infill_solid && surface.surface_type == stInternal) || surface.surface_type == stInternalBridge || surface.surface_type == stInternalVoid) {
|
||||
// Some fill region is not quite solid. Don't iron over the whole surface.
|
||||
iron_completely = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (iron_completely) {
|
||||
// Iron everything. This is likely only good for solid transparent objects.
|
||||
for (const Surface &surface : ironing_params.layerm->slices.surfaces)
|
||||
polygons_append(polys, surface.expolygon);
|
||||
} else {
|
||||
for (const Surface &surface : ironing_params.layerm->slices.surfaces)
|
||||
if (surface.surface_type == stTop || (iron_everything && surface.surface_type == stBottom))
|
||||
// stBottomBridge is not being ironed on purpose, as it would likely destroy the bridges.
|
||||
polygons_append(polys, surface.expolygon);
|
||||
}
|
||||
if (iron_everything && ! iron_completely) {
|
||||
// Add solid fill surfaces. This may not be ideal, as one will not iron perimeters touching these
|
||||
// solid fill surfaces, but it is likely better than nothing.
|
||||
for (const Surface &surface : ironing_params.layerm->fill_surfaces.surfaces)
|
||||
if (surface.surface_type == stInternalSolid)
|
||||
polygons_append(infills, surface.expolygon);
|
||||
}
|
||||
}
|
||||
|
||||
if (! infills.empty() || j > i + 1) {
|
||||
// Ironing over more than a single region or over solid internal infill.
|
||||
if (! infills.empty())
|
||||
// For IroningType::AllSolid only:
|
||||
// Add solid infill areas for layers, that contain some non-ironable infil (sparse infill, bridge infill).
|
||||
append(polys, std::move(infills));
|
||||
polys = union_safety_offset(polys);
|
||||
}
|
||||
// Trim the top surfaces with half the nozzle diameter.
|
||||
ironing_areas = intersection_ex(polys, offset(this->lslices, - float(scale_(0.5 * nozzle_dmr))));
|
||||
}
|
||||
|
||||
// Create the filler object.
|
||||
fill.spacing = ironing_params.line_spacing;
|
||||
fill.angle = float(ironing_params.angle + 0.25 * M_PI);
|
||||
fill.link_max_length = (coord_t)scale_(3. * fill.spacing);
|
||||
double extrusion_height = ironing_params.height * fill.spacing / nozzle_dmr;
|
||||
float extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(float(nozzle_dmr), float(extrusion_height));
|
||||
double flow_mm3_per_mm = nozzle_dmr * extrusion_height;
|
||||
Surface surface_fill(stTop, ExPolygon());
|
||||
for (ExPolygon &expoly : ironing_areas) {
|
||||
surface_fill.expolygon = std::move(expoly);
|
||||
Polylines polylines;
|
||||
try {
|
||||
polylines = fill.fill_surface(&surface_fill, fill_params);
|
||||
} catch (InfillFailedException &) {
|
||||
}
|
||||
if (! polylines.empty()) {
|
||||
// Save into layer.
|
||||
ExtrusionEntityCollection *eec = nullptr;
|
||||
ironing_params.layerm->fills.entities.push_back(eec = new ExtrusionEntityCollection());
|
||||
// Don't sort the ironing infill lines as they are monotonicly ordered.
|
||||
eec->no_sort = true;
|
||||
extrusion_entities_append_paths(
|
||||
eec->entities, std::move(polylines),
|
||||
erIroning,
|
||||
flow_mm3_per_mm, extrusion_width, float(extrusion_height));
|
||||
}
|
||||
}
|
||||
|
||||
// Regions up to j were processed.
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
33
src/libslic3r/Fill/Fill.hpp
Normal file
33
src/libslic3r/Fill/Fill.hpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef slic3r_Fill_hpp_
|
||||
#define slic3r_Fill_hpp_
|
||||
|
||||
#include <memory.h>
|
||||
#include <float.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExtrusionEntityCollection;
|
||||
class LayerRegion;
|
||||
|
||||
// An interface class to Perl, aggregating an instance of a Fill and a FillData.
|
||||
class Filler
|
||||
{
|
||||
public:
|
||||
Filler() : fill(nullptr) {}
|
||||
~Filler() {
|
||||
delete fill;
|
||||
fill = nullptr;
|
||||
}
|
||||
Fill *fill;
|
||||
FillParams params;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_Fill_hpp_
|
||||
174
src/libslic3r/Fill/Fill3DHoneycomb.cpp
Normal file
174
src/libslic3r/Fill/Fill3DHoneycomb.cpp
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
#include "../ClipperUtils.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include "Fill3DHoneycomb.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/*
|
||||
Creates a contiguous sequence of points at a specified height that make
|
||||
up a horizontal slice of the edges of a space filling truncated
|
||||
octahedron tesselation. The octahedrons are oriented so that the
|
||||
square faces are in the horizontal plane with edges parallel to the X
|
||||
and Y axes.
|
||||
|
||||
Credits: David Eccles (gringer).
|
||||
*/
|
||||
|
||||
// Generate an array of points that are in the same direction as the
|
||||
// basic printing line (i.e. Y points for columns, X points for rows)
|
||||
// Note: a negative offset only causes a change in the perpendicular
|
||||
// direction
|
||||
static std::vector<coordf_t> colinearPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength)
|
||||
{
|
||||
const coordf_t offset2 = std::abs(offset / coordf_t(2.));
|
||||
std::vector<coordf_t> points;
|
||||
points.push_back(baseLocation - offset2);
|
||||
for (size_t i = 0; i < gridLength; ++i) {
|
||||
points.push_back(baseLocation + i + offset2);
|
||||
points.push_back(baseLocation + i + 1 - offset2);
|
||||
}
|
||||
points.push_back(baseLocation + gridLength + offset2);
|
||||
return points;
|
||||
}
|
||||
|
||||
// Generate an array of points for the dimension that is perpendicular to
|
||||
// the basic printing line (i.e. X points for columns, Y points for rows)
|
||||
static std::vector<coordf_t> perpendPoints(const coordf_t offset, const size_t baseLocation, size_t gridLength)
|
||||
{
|
||||
coordf_t offset2 = offset / coordf_t(2.);
|
||||
coord_t side = 2 * (baseLocation & 1) - 1;
|
||||
std::vector<coordf_t> points;
|
||||
points.push_back(baseLocation - offset2 * side);
|
||||
for (size_t i = 0; i < gridLength; ++i) {
|
||||
side = 2*((i+baseLocation) & 1) - 1;
|
||||
points.push_back(baseLocation + offset2 * side);
|
||||
points.push_back(baseLocation + offset2 * side);
|
||||
}
|
||||
points.push_back(baseLocation - offset2 * side);
|
||||
return points;
|
||||
}
|
||||
|
||||
// Trims an array of points to specified rectangular limits. Point
|
||||
// components that are outside these limits are set to the limits.
|
||||
static inline void trim(Pointfs &pts, coordf_t minX, coordf_t minY, coordf_t maxX, coordf_t maxY)
|
||||
{
|
||||
for (Vec2d &pt : pts) {
|
||||
pt.x() = std::clamp(pt.x(), minX, maxX);
|
||||
pt.y() = std::clamp(pt.y(), minY, maxY);
|
||||
}
|
||||
}
|
||||
|
||||
static inline Pointfs zip(const std::vector<coordf_t> &x, const std::vector<coordf_t> &y)
|
||||
{
|
||||
assert(x.size() == y.size());
|
||||
Pointfs out;
|
||||
out.reserve(x.size());
|
||||
for (size_t i = 0; i < x.size(); ++ i)
|
||||
out.push_back(Vec2d(x[i], y[i]));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Generate a set of curves (array of array of 2d points) that describe a
|
||||
// horizontal slice of a truncated regular octahedron with edge length 1.
|
||||
// curveType specifies which lines to print, 1 for vertical lines
|
||||
// (columns), 2 for horizontal lines (rows), and 3 for both.
|
||||
static std::vector<Pointfs> makeNormalisedGrid(coordf_t z, size_t gridWidth, size_t gridHeight, size_t curveType)
|
||||
{
|
||||
// offset required to create a regular octagram
|
||||
coordf_t octagramGap = coordf_t(0.5);
|
||||
|
||||
// sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap]
|
||||
coordf_t a = std::sqrt(coordf_t(2.)); // period
|
||||
coordf_t wave = fabs(fmod(z, a) - a/2.)/a*4. - 1.;
|
||||
coordf_t offset = wave * octagramGap;
|
||||
|
||||
std::vector<Pointfs> points;
|
||||
if ((curveType & 1) != 0) {
|
||||
for (size_t x = 0; x <= gridWidth; ++x) {
|
||||
points.push_back(Pointfs());
|
||||
Pointfs &newPoints = points.back();
|
||||
newPoints = zip(
|
||||
perpendPoints(offset, x, gridHeight),
|
||||
colinearPoints(offset, 0, gridHeight));
|
||||
// trim points to grid edges
|
||||
trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight));
|
||||
if (x & 1)
|
||||
std::reverse(newPoints.begin(), newPoints.end());
|
||||
}
|
||||
}
|
||||
if ((curveType & 2) != 0) {
|
||||
for (size_t y = 0; y <= gridHeight; ++y) {
|
||||
points.push_back(Pointfs());
|
||||
Pointfs &newPoints = points.back();
|
||||
newPoints = zip(
|
||||
colinearPoints(offset, 0, gridWidth),
|
||||
perpendPoints(offset, y, gridWidth));
|
||||
// trim points to grid edges
|
||||
trim(newPoints, coordf_t(0.), coordf_t(0.), coordf_t(gridWidth), coordf_t(gridHeight));
|
||||
if (y & 1)
|
||||
std::reverse(newPoints.begin(), newPoints.end());
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
// Generate a set of curves (array of array of 2d points) that describe a
|
||||
// horizontal slice of a truncated regular octahedron with a specified
|
||||
// grid square size.
|
||||
static Polylines makeGrid(coord_t z, coord_t gridSize, size_t gridWidth, size_t gridHeight, size_t curveType)
|
||||
{
|
||||
coord_t scaleFactor = gridSize;
|
||||
coordf_t normalisedZ = coordf_t(z) / coordf_t(scaleFactor);
|
||||
std::vector<Pointfs> polylines = makeNormalisedGrid(normalisedZ, gridWidth, gridHeight, curveType);
|
||||
Polylines result;
|
||||
result.reserve(polylines.size());
|
||||
for (std::vector<Pointfs>::const_iterator it_polylines = polylines.begin(); it_polylines != polylines.end(); ++ it_polylines) {
|
||||
result.push_back(Polyline());
|
||||
Polyline &polyline = result.back();
|
||||
for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it)
|
||||
polyline.points.push_back(Point(coord_t((*it)(0) * scaleFactor), coord_t((*it)(1) * scaleFactor)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Fill3DHoneycomb::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out)
|
||||
{
|
||||
// no rotation is supported for this infill pattern
|
||||
BoundingBox bb = expolygon.contour.bounding_box();
|
||||
coord_t distance = coord_t(scale_(this->spacing) / params.density);
|
||||
|
||||
// align bounding box to a multiple of our honeycomb grid module
|
||||
// (a module is 2*$distance since one $distance half-module is
|
||||
// growing while the other $distance half-module is shrinking)
|
||||
bb.merge(align_to_grid(bb.min, Point(2*distance, 2*distance)));
|
||||
|
||||
// generate pattern
|
||||
Polylines polylines = makeGrid(
|
||||
scale_(this->z),
|
||||
distance,
|
||||
ceil(bb.size()(0) / distance) + 1,
|
||||
ceil(bb.size()(1) / distance) + 1,
|
||||
((this->layer_id/thickness_layers) % 2) + 1);
|
||||
|
||||
// move pattern in place
|
||||
for (Polyline &pl : polylines)
|
||||
pl.translate(bb.min);
|
||||
|
||||
// clip pattern to boundaries, chain the clipped polylines
|
||||
polylines = intersection_pl(polylines, expolygon);
|
||||
|
||||
// connect lines if needed
|
||||
if (params.dont_connect() || polylines.size() <= 1)
|
||||
append(polylines_out, chain_polylines(std::move(polylines)));
|
||||
else
|
||||
this->connect_infill(std::move(polylines), expolygon, polylines_out, this->spacing, params);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
32
src/libslic3r/Fill/Fill3DHoneycomb.hpp
Normal file
32
src/libslic3r/Fill/Fill3DHoneycomb.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#ifndef slic3r_Fill3DHoneycomb_hpp_
|
||||
#define slic3r_Fill3DHoneycomb_hpp_
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Fill3DHoneycomb : public Fill
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new Fill3DHoneycomb(*this); };
|
||||
~Fill3DHoneycomb() override {}
|
||||
|
||||
// require bridge flow since most of this pattern hangs in air
|
||||
bool use_bridge_flow() const override { return true; }
|
||||
|
||||
protected:
|
||||
void _fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out) override;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_Fill3DHoneycomb_hpp_
|
||||
1552
src/libslic3r/Fill/FillAdaptive.cpp
Normal file
1552
src/libslic3r/Fill/FillAdaptive.cpp
Normal file
File diff suppressed because it is too large
Load diff
79
src/libslic3r/Fill/FillAdaptive.hpp
Normal file
79
src/libslic3r/Fill/FillAdaptive.hpp
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// Adaptive cubic infill was inspired by the work of @mboerwinkle
|
||||
// as implemented for Cura.
|
||||
// https://github.com/Ultimaker/CuraEngine/issues/381
|
||||
// https://github.com/Ultimaker/CuraEngine/pull/401
|
||||
//
|
||||
// Our implementation is more accurate (discretizes a bit less cubes than Cura's)
|
||||
// by splitting only such cubes which contain a triangle.
|
||||
// Our line extraction is time optimal instead of O(n^2) when connecting extracted lines,
|
||||
// and we also implemented adaptivity for supporting internal overhangs only.
|
||||
|
||||
#ifndef slic3r_FillAdaptive_hpp_
|
||||
#define slic3r_FillAdaptive_hpp_
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
struct indexed_triangle_set;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
|
||||
namespace FillAdaptive
|
||||
{
|
||||
|
||||
struct Octree;
|
||||
// To keep the definition of Octree opaque, we have to define a custom deleter.
|
||||
struct OctreeDeleter { void operator()(Octree *p); };
|
||||
using OctreePtr = std::unique_ptr<Octree, OctreeDeleter>;
|
||||
|
||||
// Calculate line spacing for
|
||||
// 1) adaptive cubic infill
|
||||
// 2) adaptive internal support cubic infill
|
||||
// Returns zero for a particular infill type if no such infill is to be generated.
|
||||
std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object);
|
||||
|
||||
// Rotation of the octree to stand on one of its corners.
|
||||
Eigen::Quaterniond transform_to_world();
|
||||
// Inverse roation of the above.
|
||||
Eigen::Quaterniond transform_to_octree();
|
||||
|
||||
FillAdaptive::OctreePtr build_octree(
|
||||
// Mesh is rotated to the coordinate system of the octree.
|
||||
const indexed_triangle_set &triangle_mesh,
|
||||
// Overhang triangles extracted from fill surfaces with stInternalBridge type,
|
||||
// rotated to the coordinate system of the octree.
|
||||
const std::vector<Vec3d> &overhang_triangles,
|
||||
coordf_t line_spacing,
|
||||
// If true, octree is densified below internal overhangs only.
|
||||
bool support_overhangs_only);
|
||||
|
||||
//
|
||||
// Some of the algorithms used by class FillAdaptive were inspired by
|
||||
// Cura Engine's class SubDivCube
|
||||
// https://github.com/Ultimaker/CuraEngine/blob/master/src/infill/SubDivCube.h
|
||||
//
|
||||
class Filler : public Slic3r::Fill
|
||||
{
|
||||
public:
|
||||
~Filler() override {}
|
||||
|
||||
protected:
|
||||
Fill* clone() const override { return new Filler(*this); }
|
||||
void _fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out) override;
|
||||
// Let the G-code export reoder the infill lines.
|
||||
//FIXME letting the G-code exporter to reorder infill lines of Adaptive Cubic Infill
|
||||
// may not be optimal as the internal infill lines may get extruded before the long infill
|
||||
// lines to which the short infill lines are supposed to anchor.
|
||||
bool no_sort() const override { return false; }
|
||||
};
|
||||
|
||||
} // namespace FillAdaptive
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillAdaptive_hpp_
|
||||
2570
src/libslic3r/Fill/FillBase.cpp
Normal file
2570
src/libslic3r/Fill/FillBase.cpp
Normal file
File diff suppressed because it is too large
Load diff
169
src/libslic3r/Fill/FillBase.hpp
Normal file
169
src/libslic3r/Fill/FillBase.hpp
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
#ifndef slic3r_FillBase_hpp_
|
||||
#define slic3r_FillBase_hpp_
|
||||
|
||||
#include <assert.h>
|
||||
#include <memory.h>
|
||||
#include <float.h>
|
||||
#include <stdint.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../BoundingBox.hpp"
|
||||
#include "../Exception.hpp"
|
||||
#include "../Utils.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
//BBS: necessary header for new function
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../Flow.hpp"
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../ExtrusionEntityCollection.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Surface;
|
||||
enum InfillPattern : int;
|
||||
|
||||
namespace FillAdaptive {
|
||||
struct Octree;
|
||||
};
|
||||
|
||||
// Infill shall never fail, therefore the error is classified as RuntimeError, not SlicingError.
|
||||
class InfillFailedException : public Slic3r::RuntimeError {
|
||||
public:
|
||||
InfillFailedException() : Slic3r::RuntimeError("Infill failed") {}
|
||||
};
|
||||
|
||||
struct FillParams
|
||||
{
|
||||
bool full_infill() const { return density > 0.9999f; }
|
||||
// Don't connect the fill lines around the inner perimeter.
|
||||
bool dont_connect() const { return anchor_length_max < 0.05f; }
|
||||
|
||||
// Fill density, fraction in <0, 1>
|
||||
float density { 0.f };
|
||||
|
||||
// Length of an infill anchor along the perimeter.
|
||||
// 1000mm is roughly the maximum length line that fits into a 32bit coord_t.
|
||||
float anchor_length { 1000.f };
|
||||
float anchor_length_max { 1000.f };
|
||||
|
||||
// G-code resolution.
|
||||
double resolution { 0.0125 };
|
||||
|
||||
// Don't adjust spacing to fill the space evenly.
|
||||
bool dont_adjust { true };
|
||||
|
||||
// Monotonic infill - strictly left to right for better surface quality of top infills.
|
||||
bool monotonic { false };
|
||||
|
||||
// For Honeycomb.
|
||||
// we were requested to complete each loop;
|
||||
// in this case we don't try to make more continuous paths
|
||||
bool complete { false };
|
||||
|
||||
// BBS
|
||||
Flow flow;
|
||||
ExtrusionRole extrusion_role{ ExtrusionRole(0) };
|
||||
bool using_internal_flow{ false };
|
||||
//BBS: only used for new top surface pattern
|
||||
float no_extrusion_overlap{ 0.0 };
|
||||
};
|
||||
static_assert(IsTriviallyCopyable<FillParams>::value, "FillParams class is not POD (and it should be - see constructor).");
|
||||
|
||||
class Fill
|
||||
{
|
||||
public:
|
||||
// Index of the layer.
|
||||
size_t layer_id;
|
||||
// Z coordinate of the top print surface, in unscaled coordinates
|
||||
coordf_t z;
|
||||
// in unscaled coordinates
|
||||
coordf_t spacing;
|
||||
// infill / perimeter overlap, in unscaled coordinates
|
||||
coordf_t overlap;
|
||||
// in radians, ccw, 0 = East
|
||||
float angle;
|
||||
// In scaled coordinates. Maximum lenght of a perimeter segment connecting two infill lines.
|
||||
// Used by the FillRectilinear2, FillGrid2, FillTriangles, FillStars and FillCubic.
|
||||
// If left to zero, the links will not be limited.
|
||||
coord_t link_max_length;
|
||||
// In scaled coordinates. Used by the concentric infill pattern to clip the loops to create extrusion paths.
|
||||
coord_t loop_clipping;
|
||||
// In scaled coordinates. Bounding box of the 2D projection of the object.
|
||||
BoundingBox bounding_box;
|
||||
|
||||
// Octree builds on mesh for usage in the adaptive cubic infill
|
||||
FillAdaptive::Octree* adapt_fill_octree = nullptr;
|
||||
|
||||
// BBS: all no overlap expolygons in same layer
|
||||
ExPolygons no_overlap_expolygons;
|
||||
|
||||
static float infill_anchor;
|
||||
static float infill_anchor_max;
|
||||
|
||||
public:
|
||||
virtual ~Fill() {}
|
||||
virtual Fill* clone() const = 0;
|
||||
|
||||
static Fill* new_from_type(const InfillPattern type);
|
||||
static Fill* new_from_type(const std::string &type);
|
||||
static bool use_bridge_flow(const InfillPattern type);
|
||||
|
||||
void set_bounding_box(const Slic3r::BoundingBox &bbox) { bounding_box = bbox; }
|
||||
|
||||
// Use bridge flow for the fill?
|
||||
virtual bool use_bridge_flow() const { return false; }
|
||||
|
||||
// Do not sort the fill lines to optimize the print head path?
|
||||
virtual bool no_sort() const { return false; }
|
||||
|
||||
// Perform the fill.
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
|
||||
// BBS: this method is used to fill the ExtrusionEntityCollection.
|
||||
// It call fill_surface by default
|
||||
virtual void fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out);
|
||||
|
||||
protected:
|
||||
Fill() :
|
||||
layer_id(size_t(-1)),
|
||||
z(0.),
|
||||
spacing(0.),
|
||||
// Infill / perimeter overlap.
|
||||
overlap(0.),
|
||||
// Initial angle is undefined.
|
||||
angle(FLT_MAX),
|
||||
link_max_length(0),
|
||||
loop_clipping(0),
|
||||
// The initial bounding box is empty, therefore undefined.
|
||||
bounding_box(Point(0, 0), Point(-1, -1))
|
||||
{}
|
||||
|
||||
// The expolygon may be modified by the method to avoid a copy.
|
||||
virtual void _fill_surface_single(
|
||||
const FillParams & /* params */,
|
||||
unsigned int /* thickness_layers */,
|
||||
const std::pair<float, Point> & /* direction */,
|
||||
ExPolygon /* expolygon */,
|
||||
Polylines & /* polylines_out */) {};
|
||||
|
||||
virtual float _layer_angle(size_t idx) const { return (idx & 1) ? float(M_PI/2.) : 0; }
|
||||
|
||||
virtual std::pair<float, Point> _infill_direction(const Surface *surface) const;
|
||||
|
||||
public:
|
||||
static void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const double spacing, const FillParams ¶ms);
|
||||
static void connect_infill(Polylines &&infill_ordered, const Polygons &boundary, const BoundingBox& bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms);
|
||||
static void connect_infill(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary, const BoundingBox &bbox, Polylines &polylines_out, double spacing, const FillParams ¶ms);
|
||||
|
||||
static void connect_base_support(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms);
|
||||
static void connect_base_support(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms);
|
||||
|
||||
static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillBase_hpp_
|
||||
64
src/libslic3r/Fill/FillConcentric.cpp
Normal file
64
src/libslic3r/Fill/FillConcentric.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include "../ClipperUtils.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include "FillConcentric.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void FillConcentric::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out)
|
||||
{
|
||||
// no rotation is supported for this infill pattern
|
||||
BoundingBox bounding_box = expolygon.contour.bounding_box();
|
||||
|
||||
coord_t min_spacing = scale_(this->spacing);
|
||||
coord_t distance = coord_t(min_spacing / params.density);
|
||||
|
||||
if (params.density > 0.9999f && !params.dont_adjust) {
|
||||
distance = this->_adjust_solid_spacing(bounding_box.size()(0), distance);
|
||||
this->spacing = unscale<double>(distance);
|
||||
}
|
||||
|
||||
Polygons loops = to_polygons(expolygon);
|
||||
ExPolygons last { std::move(expolygon) };
|
||||
while (! last.empty()) {
|
||||
last = offset2_ex(last, -(distance + min_spacing/2), +min_spacing/2);
|
||||
append(loops, to_polygons(last));
|
||||
}
|
||||
|
||||
// generate paths from the outermost to the innermost, to avoid
|
||||
// adhesion problems of the first central tiny loops
|
||||
loops = union_pt_chained_outside_in(loops);
|
||||
|
||||
// split paths using a nearest neighbor search
|
||||
size_t iPathFirst = polylines_out.size();
|
||||
Point last_pos(0, 0);
|
||||
for (const Polygon &loop : loops) {
|
||||
polylines_out.emplace_back(loop.split_at_index(last_pos.nearest_point_index(loop.points)));
|
||||
last_pos = polylines_out.back().last_point();
|
||||
}
|
||||
|
||||
// clip the paths to prevent the extruder from getting exactly on the first point of the loop
|
||||
// Keep valid paths only.
|
||||
size_t j = iPathFirst;
|
||||
for (size_t i = iPathFirst; i < polylines_out.size(); ++ i) {
|
||||
polylines_out[i].clip_end(this->loop_clipping);
|
||||
if (polylines_out[i].is_valid()) {
|
||||
if (j < i)
|
||||
polylines_out[j] = std::move(polylines_out[i]);
|
||||
++ j;
|
||||
}
|
||||
}
|
||||
if (j < polylines_out.size())
|
||||
polylines_out.erase(polylines_out.begin() + j, polylines_out.end());
|
||||
//TODO: return ExtrusionLoop objects to get better chained paths,
|
||||
// otherwise the outermost loop starts at the closest point to (0, 0).
|
||||
// We want the loops to be split inside the G-code generator to get optimum path planning.
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
27
src/libslic3r/Fill/FillConcentric.hpp
Normal file
27
src/libslic3r/Fill/FillConcentric.hpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef slic3r_FillConcentric_hpp_
|
||||
#define slic3r_FillConcentric_hpp_
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillConcentric : public Fill
|
||||
{
|
||||
public:
|
||||
~FillConcentric() override = default;
|
||||
|
||||
protected:
|
||||
Fill* clone() const override { return new FillConcentric(*this); };
|
||||
void _fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out) override;
|
||||
|
||||
bool no_sort() const override { return true; }
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillConcentric_hpp_
|
||||
137
src/libslic3r/Fill/FillConcentricWGapFill.cpp
Normal file
137
src/libslic3r/Fill/FillConcentricWGapFill.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#include "../ClipperUtils.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
#include "../Surface.hpp"
|
||||
#include "../VariableWidth.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
|
||||
#include "FillConcentricWGapFill.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
const float concentric_overlap_threshold = 0.02;
|
||||
|
||||
void FillConcentricWGapFill::fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out)
|
||||
{
|
||||
//BBS: FillConcentricWGapFill.cpp is absolutely newly add by BBL for narrow internal solid infill area to reduce vibration
|
||||
// Because the area is narrow, we should not use the surface->expolygon which has overlap with perimeter, but
|
||||
// use no_overlap_expolygons instead to avoid overflow in narrow area.
|
||||
//Slic3r::ExPolygons expp = offset_ex(surface->expolygon, double(scale_(0 - 0.5 * this->spacing)));
|
||||
float min_spacing = this->spacing * (1 - concentric_overlap_threshold);
|
||||
Slic3r::ExPolygons expp = offset2_ex(this->no_overlap_expolygons, -double(scale_(0.5 * this->spacing + 0.5 * min_spacing) - 1),
|
||||
+double(scale_(0.5 * min_spacing) - 1));
|
||||
// Create the infills for each of the regions.
|
||||
Polylines polylines_out;
|
||||
for (size_t i = 0; i < expp.size(); ++i) {
|
||||
ExPolygon expolygon = expp[i];
|
||||
|
||||
coord_t distance = scale_(this->spacing / params.density);
|
||||
if (params.density > 0.9999f && !params.dont_adjust) {
|
||||
distance = scale_(this->spacing);
|
||||
}
|
||||
|
||||
ExPolygons gaps;
|
||||
Polygons loops = (Polygons)expolygon;
|
||||
ExPolygons last = { expolygon };
|
||||
bool first = true;
|
||||
while (!last.empty()) {
|
||||
ExPolygons next_onion = offset2_ex(last, -double(distance + scale_(this->spacing) / 2), +double(scale_(this->spacing) / 2));
|
||||
for (auto it = next_onion.begin(); it != next_onion.end(); it++) {
|
||||
Polygons temp_loops = (Polygons)(*it);
|
||||
loops.insert(loops.end(), temp_loops.begin(), temp_loops.end());
|
||||
}
|
||||
append(gaps, diff_ex(
|
||||
offset(last, -0.5f * distance),
|
||||
offset(next_onion, 0.5f * distance + 10))); // 10 is safty offset
|
||||
last = next_onion;
|
||||
if (first && !this->no_overlap_expolygons.empty()) {
|
||||
gaps = intersection_ex(gaps, this->no_overlap_expolygons);
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
|
||||
ExtrusionRole good_role = params.extrusion_role;
|
||||
ExtrusionEntityCollection *coll_nosort = new ExtrusionEntityCollection();
|
||||
coll_nosort->no_sort = this->no_sort(); //can be sorted inside the pass
|
||||
extrusion_entities_append_loops(
|
||||
coll_nosort->entities, std::move(loops),
|
||||
good_role,
|
||||
params.flow.mm3_per_mm(),
|
||||
params.flow.width(),
|
||||
params.flow.height());
|
||||
|
||||
//BBS: add internal gapfills between infill loops
|
||||
if (!gaps.empty() && params.density >= 1) {
|
||||
double min = 0.2 * distance * (1 - INSET_OVERLAP_TOLERANCE);
|
||||
double max = 2. * distance;
|
||||
ExPolygons gaps_ex = diff_ex(
|
||||
offset2_ex(gaps, -float(min / 2), float(min / 2)),
|
||||
offset2_ex(gaps, -float(max / 2), float(max / 2)),
|
||||
ApplySafetyOffset::Yes);
|
||||
//BBS: sort the gap_ex to avoid mess travel
|
||||
Points ordering_points;
|
||||
ordering_points.reserve(gaps_ex.size());
|
||||
ExPolygons gaps_ex_sorted;
|
||||
gaps_ex_sorted.reserve(gaps_ex.size());
|
||||
for (const ExPolygon &ex : gaps_ex)
|
||||
ordering_points.push_back(ex.contour.first_point());
|
||||
std::vector<Points::size_type> order = chain_points(ordering_points);
|
||||
for (size_t i : order)
|
||||
gaps_ex_sorted.emplace_back(std::move(gaps_ex[i]));
|
||||
|
||||
ThickPolylines polylines;
|
||||
for (ExPolygon& ex : gaps_ex_sorted) {
|
||||
//BBS: medial axis algorithm can't handle duplicated points in expolygon.
|
||||
//Use DP simplify to avoid duplicated points and accelerate medial-axis calculation as well.
|
||||
ex.douglas_peucker(SCALED_RESOLUTION);
|
||||
ex.medial_axis(max, min, &polylines);
|
||||
}
|
||||
|
||||
if (!polylines.empty() && !is_bridge(good_role)) {
|
||||
ExtrusionEntityCollection gap_fill;
|
||||
variable_width(polylines, erGapFill, params.flow, gap_fill.entities);
|
||||
coll_nosort->append(std::move(gap_fill.entities));
|
||||
}
|
||||
}
|
||||
|
||||
if (!coll_nosort->entities.empty())
|
||||
out.push_back(coll_nosort);
|
||||
else
|
||||
delete coll_nosort;
|
||||
}
|
||||
|
||||
//BBS: add external gapfill between perimeter and infill
|
||||
ExPolygons external_gaps = diff_ex(this->no_overlap_expolygons, offset_ex(expp, double(scale_(0.5 * this->spacing))), ApplySafetyOffset::Yes);
|
||||
external_gaps = union_ex(external_gaps);
|
||||
if (!this->no_overlap_expolygons.empty())
|
||||
external_gaps = intersection_ex(external_gaps, this->no_overlap_expolygons);
|
||||
|
||||
if (!external_gaps.empty()) {
|
||||
double min = 0.4 * scale_(params.flow.nozzle_diameter()) * (1 - INSET_OVERLAP_TOLERANCE);
|
||||
double max = 2. * params.flow.scaled_width();
|
||||
//BBS: collapse, be sure we don't gapfill where the perimeters are already touching each other (negative spacing).
|
||||
min = std::max(min, (double)Flow::rounded_rectangle_extrusion_width_from_spacing((float)EPSILON, (float)params.flow.height()));
|
||||
ExPolygons external_gaps_collapsed = offset2_ex(external_gaps, double(-min / 2), double(+min / 2));
|
||||
|
||||
ThickPolylines polylines;
|
||||
for (ExPolygon& ex : external_gaps_collapsed) {
|
||||
//BBS: medial axis algorithm can't handle duplicated points in expolygon.
|
||||
//Use DP simplify to avoid duplicated points and accelerate medial-axis calculation as well.
|
||||
ex.douglas_peucker(SCALED_RESOLUTION);
|
||||
ex.medial_axis(max, min, &polylines);
|
||||
}
|
||||
|
||||
ExtrusionEntityCollection* coll_external_gapfill = new ExtrusionEntityCollection();
|
||||
coll_external_gapfill->no_sort = this->no_sort();
|
||||
if (!polylines.empty() && !is_bridge(params.extrusion_role)) {
|
||||
ExtrusionEntityCollection gap_fill;
|
||||
variable_width(polylines, erGapFill, params.flow, gap_fill.entities);
|
||||
coll_external_gapfill->append(std::move(gap_fill.entities));
|
||||
}
|
||||
if (!coll_external_gapfill->entities.empty())
|
||||
out.push_back(coll_external_gapfill);
|
||||
else
|
||||
delete coll_external_gapfill;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
21
src/libslic3r/Fill/FillConcentricWGapFill.hpp
Normal file
21
src/libslic3r/Fill/FillConcentricWGapFill.hpp
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef slic3r_FillConcentricWGapFil_hpp_
|
||||
#define slic3r_FillConcentricWGapFil_hpp_
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillConcentricWGapFill : public Fill
|
||||
{
|
||||
public:
|
||||
~FillConcentricWGapFill() override = default;
|
||||
void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override;
|
||||
|
||||
protected:
|
||||
Fill* clone() const override { return new FillConcentricWGapFill(*this); };
|
||||
bool no_sort() const override { return true; }
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillConcentricWGapFil_hpp_
|
||||
210
src/libslic3r/Fill/FillGyroid.cpp
Normal file
210
src/libslic3r/Fill/FillGyroid.cpp
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
#include "../ClipperUtils.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include "FillGyroid.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static inline double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
|
||||
{
|
||||
if (vertical) {
|
||||
double phase_offset = (z_cos < 0 ? M_PI : 0) + M_PI;
|
||||
double a = sin(x + phase_offset);
|
||||
double b = - z_cos;
|
||||
double res = z_sin * cos(x + phase_offset + (flip ? M_PI : 0.));
|
||||
double r = sqrt(sqr(a) + sqr(b));
|
||||
return asin(a/r) + asin(res/r) + M_PI;
|
||||
}
|
||||
else {
|
||||
double phase_offset = z_sin < 0 ? M_PI : 0.;
|
||||
double a = cos(x + phase_offset);
|
||||
double b = - z_sin;
|
||||
double res = z_cos * sin(x + phase_offset + (flip ? 0 : M_PI));
|
||||
double r = sqrt(sqr(a) + sqr(b));
|
||||
return (asin(a/r) + asin(res/r) + 0.5 * M_PI);
|
||||
}
|
||||
}
|
||||
|
||||
static inline Polyline make_wave(
|
||||
const std::vector<Vec2d>& one_period, double width, double height, double offset, double scaleFactor,
|
||||
double z_cos, double z_sin, bool vertical, bool flip)
|
||||
{
|
||||
std::vector<Vec2d> points = one_period;
|
||||
double period = points.back()(0);
|
||||
if (width != period) // do not extend if already truncated
|
||||
{
|
||||
points.reserve(one_period.size() * size_t(floor(width / period)));
|
||||
points.pop_back();
|
||||
|
||||
size_t n = points.size();
|
||||
do {
|
||||
points.emplace_back(points[points.size()-n].x() + period, points[points.size()-n].y());
|
||||
} while (points.back()(0) < width - EPSILON);
|
||||
|
||||
points.emplace_back(Vec2d(width, f(width, z_sin, z_cos, vertical, flip)));
|
||||
}
|
||||
|
||||
// and construct the final polyline to return:
|
||||
Polyline polyline;
|
||||
polyline.points.reserve(points.size());
|
||||
for (auto& point : points) {
|
||||
point(1) += offset;
|
||||
point(1) = std::clamp(double(point.y()), 0., height);
|
||||
if (vertical)
|
||||
std::swap(point(0), point(1));
|
||||
polyline.points.emplace_back((point * scaleFactor).cast<coord_t>());
|
||||
}
|
||||
|
||||
return polyline;
|
||||
}
|
||||
|
||||
static std::vector<Vec2d> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance)
|
||||
{
|
||||
std::vector<Vec2d> points;
|
||||
double dx = M_PI_2; // exact coordinates on main inflexion lobes
|
||||
double limit = std::min(2*M_PI, width);
|
||||
points.reserve(coord_t(ceil(limit / tolerance / 3)));
|
||||
|
||||
for (double x = 0.; x < limit - EPSILON; x += dx) {
|
||||
points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
|
||||
}
|
||||
points.emplace_back(Vec2d(limit, f(limit, z_sin, z_cos, vertical, flip)));
|
||||
|
||||
// piecewise increase in resolution up to requested tolerance
|
||||
for(;;)
|
||||
{
|
||||
size_t size = points.size();
|
||||
for (unsigned int i = 1;i < size; ++i) {
|
||||
auto& lp = points[i-1]; // left point
|
||||
auto& rp = points[i]; // right point
|
||||
double x = lp(0) + (rp(0) - lp(0)) / 2;
|
||||
double y = f(x, z_sin, z_cos, vertical, flip);
|
||||
Vec2d ip = {x, y};
|
||||
if (std::abs(cross2(Vec2d(ip - lp), Vec2d(ip - rp))) > sqr(tolerance)) {
|
||||
points.emplace_back(std::move(ip));
|
||||
}
|
||||
}
|
||||
|
||||
if (size == points.size())
|
||||
break;
|
||||
else
|
||||
{
|
||||
// insert new points in order
|
||||
std::sort(points.begin(), points.end(),
|
||||
[](const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0); });
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
|
||||
{
|
||||
const double scaleFactor = scale_(line_spacing) / density_adjusted;
|
||||
|
||||
// tolerance in scaled units. clamp the maximum tolerance as there's
|
||||
// no processing-speed benefit to do so beyond a certain point
|
||||
const double tolerance = std::min(line_spacing / 2, FillGyroid::PatternTolerance) / unscale<double>(scaleFactor);
|
||||
|
||||
//scale factor for 5% : 8 712 388
|
||||
// 1z = 10^-6 mm ?
|
||||
const double z = gridZ / scaleFactor;
|
||||
const double z_sin = sin(z);
|
||||
const double z_cos = cos(z);
|
||||
|
||||
bool vertical = (std::abs(z_sin) <= std::abs(z_cos));
|
||||
double lower_bound = 0.;
|
||||
double upper_bound = height;
|
||||
bool flip = true;
|
||||
if (vertical) {
|
||||
flip = false;
|
||||
lower_bound = -M_PI;
|
||||
upper_bound = width - M_PI_2;
|
||||
std::swap(width,height);
|
||||
}
|
||||
|
||||
std::vector<Vec2d> one_period_odd = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time
|
||||
flip = !flip; // even polylines are a bit shifted
|
||||
std::vector<Vec2d> one_period_even = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance);
|
||||
Polylines result;
|
||||
|
||||
for (double y0 = lower_bound; y0 < upper_bound + EPSILON; y0 += M_PI) {
|
||||
// creates odd polylines
|
||||
result.emplace_back(make_wave(one_period_odd, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip));
|
||||
// creates even polylines
|
||||
y0 += M_PI;
|
||||
if (y0 < upper_bound + EPSILON) {
|
||||
result.emplace_back(make_wave(one_period_even, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// FIXME: needed to fix build on Mac on buildserver
|
||||
constexpr double FillGyroid::PatternTolerance;
|
||||
|
||||
void FillGyroid::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out)
|
||||
{
|
||||
auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.);
|
||||
if(std::abs(infill_angle) >= EPSILON)
|
||||
expolygon.rotate(-infill_angle);
|
||||
|
||||
BoundingBox bb = expolygon.contour.bounding_box();
|
||||
// Density adjusted to have a good %of weight.
|
||||
double density_adjusted = std::max(0., params.density * DensityAdjust);
|
||||
// Distance between the gyroid waves in scaled coordinates.
|
||||
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
|
||||
|
||||
// align bounding box to a multiple of our grid module
|
||||
bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
|
||||
|
||||
// generate pattern
|
||||
Polylines polylines = make_gyroid_waves(
|
||||
scale_(this->z),
|
||||
density_adjusted,
|
||||
this->spacing,
|
||||
ceil(bb.size()(0) / distance) + 1.,
|
||||
ceil(bb.size()(1) / distance) + 1.);
|
||||
|
||||
// shift the polyline to the grid origin
|
||||
for (Polyline &pl : polylines)
|
||||
pl.translate(bb.min);
|
||||
|
||||
polylines = intersection_pl(polylines, expolygon);
|
||||
|
||||
if (! polylines.empty()) {
|
||||
// Remove very small bits, but be careful to not remove infill lines connecting thin walls!
|
||||
// The infill perimeter lines should be separated by around a single infill line width.
|
||||
const double minlength = scale_(0.8 * this->spacing);
|
||||
polylines.erase(
|
||||
std::remove_if(polylines.begin(), polylines.end(), [minlength](const Polyline &pl) { return pl.length() < minlength; }),
|
||||
polylines.end());
|
||||
}
|
||||
|
||||
if (! polylines.empty()) {
|
||||
// connect lines
|
||||
size_t polylines_out_first_idx = polylines_out.size();
|
||||
if (params.dont_connect())
|
||||
append(polylines_out, chain_polylines(polylines));
|
||||
else
|
||||
this->connect_infill(std::move(polylines), expolygon, polylines_out, this->spacing, params);
|
||||
|
||||
// new paths must be rotated back
|
||||
if (std::abs(infill_angle) >= EPSILON) {
|
||||
for (auto it = polylines_out.begin() + polylines_out_first_idx; it != polylines_out.end(); ++ it)
|
||||
it->rotate(infill_angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
41
src/libslic3r/Fill/FillGyroid.hpp
Normal file
41
src/libslic3r/Fill/FillGyroid.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef slic3r_FillGyroid_hpp_
|
||||
#define slic3r_FillGyroid_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillGyroid : public Fill
|
||||
{
|
||||
public:
|
||||
FillGyroid() {}
|
||||
Fill* clone() const override { return new FillGyroid(*this); }
|
||||
|
||||
// require bridge flow since most of this pattern hangs in air
|
||||
bool use_bridge_flow() const override { return false; }
|
||||
|
||||
// Correction applied to regular infill angle to maximize printing
|
||||
// speed in default configuration (degrees)
|
||||
static constexpr float CorrectionAngle = -45.;
|
||||
|
||||
// Density adjustment to have a good %of weight.
|
||||
static constexpr double DensityAdjust = 2.44;
|
||||
|
||||
// Gyroid upper resolution tolerance (mm^-2)
|
||||
static constexpr double PatternTolerance = 0.2;
|
||||
|
||||
|
||||
protected:
|
||||
void _fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out) override;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillGyroid_hpp_
|
||||
83
src/libslic3r/Fill/FillHoneycomb.cpp
Normal file
83
src/libslic3r/Fill/FillHoneycomb.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#include "../ClipperUtils.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include "FillHoneycomb.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void FillHoneycomb::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out)
|
||||
{
|
||||
// cache hexagons math
|
||||
CacheID cache_id(params.density, this->spacing);
|
||||
Cache::iterator it_m = this->cache.find(cache_id);
|
||||
if (it_m == this->cache.end()) {
|
||||
it_m = this->cache.insert(it_m, std::pair<CacheID, CacheData>(cache_id, CacheData()));
|
||||
CacheData &m = it_m->second;
|
||||
coord_t min_spacing = coord_t(scale_(this->spacing));
|
||||
m.distance = coord_t(min_spacing / params.density);
|
||||
m.hex_side = coord_t(m.distance / (sqrt(3)/2));
|
||||
m.hex_width = m.distance * 2; // $m->{hex_width} == $m->{hex_side} * sqrt(3);
|
||||
coord_t hex_height = m.hex_side * 2;
|
||||
m.pattern_height = hex_height + m.hex_side;
|
||||
m.y_short = coord_t(m.distance * sqrt(3)/3);
|
||||
m.x_offset = min_spacing / 2;
|
||||
m.y_offset = coord_t(m.x_offset * sqrt(3)/3);
|
||||
m.hex_center = Point(m.hex_width/2, m.hex_side);
|
||||
}
|
||||
CacheData &m = it_m->second;
|
||||
|
||||
Polylines all_polylines;
|
||||
{
|
||||
// adjust actual bounding box to the nearest multiple of our hex pattern
|
||||
// and align it so that it matches across layers
|
||||
|
||||
BoundingBox bounding_box = expolygon.contour.bounding_box();
|
||||
{
|
||||
// rotate bounding box according to infill direction
|
||||
Polygon bb_polygon = bounding_box.polygon();
|
||||
bb_polygon.rotate(direction.first, m.hex_center);
|
||||
bounding_box = bb_polygon.bounding_box();
|
||||
|
||||
// extend bounding box so that our pattern will be aligned with other layers
|
||||
// $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one
|
||||
// The infill is not aligned to the object bounding box, but to a world coordinate system. Supposedly good enough.
|
||||
bounding_box.merge(align_to_grid(bounding_box.min, Point(m.hex_width, m.pattern_height)));
|
||||
}
|
||||
|
||||
coord_t x = bounding_box.min(0);
|
||||
while (x <= bounding_box.max(0)) {
|
||||
Polyline p;
|
||||
coord_t ax[2] = { x + m.x_offset, x + m.distance - m.x_offset };
|
||||
for (size_t i = 0; i < 2; ++ i) {
|
||||
std::reverse(p.points.begin(), p.points.end()); // turn first half upside down
|
||||
for (coord_t y = bounding_box.min(1); y <= bounding_box.max(1); y += m.y_short + m.hex_side + m.y_short + m.hex_side) {
|
||||
p.points.push_back(Point(ax[1], y + m.y_offset));
|
||||
p.points.push_back(Point(ax[0], y + m.y_short - m.y_offset));
|
||||
p.points.push_back(Point(ax[0], y + m.y_short + m.hex_side + m.y_offset));
|
||||
p.points.push_back(Point(ax[1], y + m.y_short + m.hex_side + m.y_short - m.y_offset));
|
||||
p.points.push_back(Point(ax[1], y + m.y_short + m.hex_side + m.y_short + m.hex_side + m.y_offset));
|
||||
}
|
||||
ax[0] = ax[0] + m.distance;
|
||||
ax[1] = ax[1] + m.distance;
|
||||
std::swap(ax[0], ax[1]); // draw symmetrical pattern
|
||||
x += m.distance;
|
||||
}
|
||||
p.rotate(-direction.first, m.hex_center);
|
||||
all_polylines.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
all_polylines = intersection_pl(std::move(all_polylines), expolygon);
|
||||
if (params.dont_connect() || all_polylines.size() <= 1)
|
||||
append(polylines_out, chain_polylines(std::move(all_polylines)));
|
||||
else
|
||||
connect_infill(std::move(all_polylines), expolygon, polylines_out, this->spacing, params);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
57
src/libslic3r/Fill/FillHoneycomb.hpp
Normal file
57
src/libslic3r/Fill/FillHoneycomb.hpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#ifndef slic3r_FillHoneycomb_hpp_
|
||||
#define slic3r_FillHoneycomb_hpp_
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class FillHoneycomb : public Fill
|
||||
{
|
||||
public:
|
||||
~FillHoneycomb() override {}
|
||||
|
||||
protected:
|
||||
Fill* clone() const override { return new FillHoneycomb(*this); };
|
||||
void _fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out) override;
|
||||
|
||||
// Caching the
|
||||
struct CacheID
|
||||
{
|
||||
CacheID(float adensity, coordf_t aspacing) :
|
||||
density(adensity), spacing(aspacing) {}
|
||||
float density;
|
||||
coordf_t spacing;
|
||||
bool operator<(const CacheID &other) const
|
||||
{ return (density < other.density) || (density == other.density && spacing < other.spacing); }
|
||||
bool operator==(const CacheID &other) const
|
||||
{ return density == other.density && spacing == other.spacing; }
|
||||
};
|
||||
struct CacheData
|
||||
{
|
||||
coord_t distance;
|
||||
coord_t hex_side;
|
||||
coord_t hex_width;
|
||||
coord_t pattern_height;
|
||||
coord_t y_short;
|
||||
coord_t x_offset;
|
||||
coord_t y_offset;
|
||||
Point hex_center;
|
||||
};
|
||||
typedef std::map<CacheID, CacheData> Cache;
|
||||
Cache cache;
|
||||
|
||||
float _layer_angle(size_t idx) const override { return float(M_PI/3.) * (idx % 3); }
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillHoneycomb_hpp_
|
||||
29
src/libslic3r/Fill/FillLightning.cpp
Normal file
29
src/libslic3r/Fill/FillLightning.cpp
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#include "../Print.hpp"
|
||||
|
||||
#include "FillLightning.hpp"
|
||||
#include "Lightning/Generator.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
Polylines Filler::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
{
|
||||
const Layer &layer = generator->getTreesForLayer(this->layer_id);
|
||||
return layer.convertToLines(to_polygons(surface->expolygon), generator->infilll_extrusion_width());
|
||||
}
|
||||
|
||||
void GeneratorDeleter::operator()(Generator *p) {
|
||||
delete p;
|
||||
}
|
||||
|
||||
GeneratorPtr build_generator(const PrintObject &print_object)
|
||||
{
|
||||
return GeneratorPtr(new Generator(print_object));
|
||||
}
|
||||
|
||||
} // namespace Slic3r::FillAdaptive
|
||||
36
src/libslic3r/Fill/FillLightning.hpp
Normal file
36
src/libslic3r/Fill/FillLightning.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#ifndef slic3r_FillLightning_hpp_
|
||||
#define slic3r_FillLightning_hpp_
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
|
||||
namespace FillLightning {
|
||||
|
||||
class Generator;
|
||||
// To keep the definition of Octree opaque, we have to define a custom deleter.
|
||||
struct GeneratorDeleter { void operator()(Generator *p); };
|
||||
using GeneratorPtr = std::unique_ptr<Generator, GeneratorDeleter>;
|
||||
|
||||
GeneratorPtr build_generator(const PrintObject &print_object);
|
||||
|
||||
class Filler : public Slic3r::Fill
|
||||
{
|
||||
public:
|
||||
~Filler() override = default;
|
||||
|
||||
Generator *generator { nullptr };
|
||||
protected:
|
||||
Fill* clone() const override { return new Filler(*this); }
|
||||
// Perform the fill.
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override;
|
||||
// Let the G-code export reoder the infill lines.
|
||||
bool no_sort() const override { return false; }
|
||||
};
|
||||
|
||||
} // namespace FillAdaptive
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillLightning_hpp_
|
||||
122
src/libslic3r/Fill/FillLine.cpp
Normal file
122
src/libslic3r/Fill/FillLine.cpp
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#include "../ClipperUtils.hpp"
|
||||
#include "../ExPolygon.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include "FillLine.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void FillLine::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out)
|
||||
{
|
||||
// rotate polygons so that we can work with vertical lines here
|
||||
expolygon.rotate(- direction.first);
|
||||
|
||||
this->_min_spacing = scale_(this->spacing);
|
||||
assert(params.density > 0.0001f && params.density <= 1.f);
|
||||
this->_line_spacing = coord_t(coordf_t(this->_min_spacing) / params.density);
|
||||
this->_diagonal_distance = this->_line_spacing * 2;
|
||||
this->_line_oscillation = this->_line_spacing - this->_min_spacing; // only for Line infill
|
||||
BoundingBox bounding_box = expolygon.contour.bounding_box();
|
||||
|
||||
// define flow spacing according to requested density
|
||||
if (params.density > 0.9999f && !params.dont_adjust) {
|
||||
this->_line_spacing = this->_adjust_solid_spacing(bounding_box.size()(0), this->_line_spacing);
|
||||
this->spacing = unscale<double>(this->_line_spacing);
|
||||
} else {
|
||||
// extend bounding box so that our pattern will be aligned with other layers
|
||||
// Transform the reference point to the rotated coordinate system.
|
||||
bounding_box.merge(align_to_grid(
|
||||
bounding_box.min,
|
||||
Point(this->_line_spacing, this->_line_spacing),
|
||||
direction.second.rotated(- direction.first)));
|
||||
}
|
||||
|
||||
// generate the basic pattern
|
||||
coord_t x_max = bounding_box.max(0) + SCALED_EPSILON;
|
||||
Lines lines;
|
||||
for (coord_t x = bounding_box.min(0); x <= x_max; x += this->_line_spacing)
|
||||
lines.push_back(this->_line(lines.size(), x, bounding_box.min(1), bounding_box.max(1)));
|
||||
|
||||
// clip paths against a slightly larger expolygon, so that the first and last paths
|
||||
// are kept even if the expolygon has vertical sides
|
||||
// the minimum offset for preventing edge lines from being clipped is SCALED_EPSILON;
|
||||
// however we use a larger offset to support expolygons with slightly skewed sides and
|
||||
// not perfectly straight
|
||||
//FIXME Vojtech: Update the intersecton function to work directly with lines.
|
||||
Polylines polylines_src;
|
||||
polylines_src.reserve(lines.size());
|
||||
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it) {
|
||||
polylines_src.push_back(Polyline());
|
||||
Points &pts = polylines_src.back().points;
|
||||
pts.reserve(2);
|
||||
pts.push_back(it->a);
|
||||
pts.push_back(it->b);
|
||||
}
|
||||
Polylines polylines = intersection_pl(polylines_src, offset(expolygon, scale_(0.02)));
|
||||
|
||||
// FIXME Vojtech: This is only performed for horizontal lines, not for the vertical lines!
|
||||
const float INFILL_OVERLAP_OVER_SPACING = 0.3f;
|
||||
// How much to extend an infill path from expolygon outside?
|
||||
coord_t extra = coord_t(floor(this->_min_spacing * INFILL_OVERLAP_OVER_SPACING + 0.5f));
|
||||
for (Polylines::iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) {
|
||||
Point *first_point = &it_polyline->points.front();
|
||||
Point *last_point = &it_polyline->points.back();
|
||||
if (first_point->y() > last_point->y())
|
||||
std::swap(first_point, last_point);
|
||||
first_point->y() -= extra;
|
||||
last_point->y() += extra;
|
||||
}
|
||||
|
||||
size_t n_polylines_out_old = polylines_out.size();
|
||||
|
||||
// connect lines
|
||||
if (! params.dont_connect() && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections
|
||||
// offset the expolygon by max(min_spacing/2, extra)
|
||||
ExPolygon expolygon_off;
|
||||
{
|
||||
ExPolygons expolygons_off = offset_ex(expolygon, this->_min_spacing/2);
|
||||
if (! expolygons_off.empty()) {
|
||||
// When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island.
|
||||
assert(expolygons_off.size() == 1);
|
||||
std::swap(expolygon_off, expolygons_off.front());
|
||||
}
|
||||
}
|
||||
bool first = true;
|
||||
for (Polyline &polyline : chain_polylines(std::move(polylines))) {
|
||||
if (! first) {
|
||||
// Try to connect the lines.
|
||||
Points &pts_end = polylines_out.back().points;
|
||||
const Point &first_point = polyline.points.front();
|
||||
const Point &last_point = pts_end.back();
|
||||
// Distance in X, Y.
|
||||
const Vector distance = last_point - first_point;
|
||||
// TODO: we should also check that both points are on a fill_boundary to avoid
|
||||
// connecting paths on the boundaries of internal regions
|
||||
if (this->_can_connect(std::abs(distance(0)), std::abs(distance(1))) &&
|
||||
expolygon_off.contains(Line(last_point, first_point))) {
|
||||
// Append the polyline.
|
||||
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// The lines cannot be connected.
|
||||
polylines_out.emplace_back(std::move(polyline));
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
// paths must be rotated back
|
||||
for (Polylines::iterator it = polylines_out.begin() + n_polylines_out_old; it != polylines_out.end(); ++ it) {
|
||||
// No need to translate, the absolute position is irrelevant.
|
||||
// it->translate(- direction.second(0), - direction.second(1));
|
||||
it->rotate(direction.first);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
49
src/libslic3r/Fill/FillLine.hpp
Normal file
49
src/libslic3r/Fill/FillLine.hpp
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef slic3r_FillLine_hpp_
|
||||
#define slic3r_FillLine_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Surface;
|
||||
|
||||
class FillLine : public Fill
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillLine(*this); };
|
||||
~FillLine() override = default;
|
||||
|
||||
protected:
|
||||
void _fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out) override;
|
||||
|
||||
coord_t _min_spacing;
|
||||
coord_t _line_spacing;
|
||||
// distance threshold for allowing the horizontal infill lines to be connected into a continuous path
|
||||
coord_t _diagonal_distance;
|
||||
// only for line infill
|
||||
coord_t _line_oscillation;
|
||||
|
||||
Line _line(int i, coord_t x, coord_t y_min, coord_t y_max) const {
|
||||
coord_t osc = (i & 1) ? this->_line_oscillation : 0;
|
||||
return Line(Point(x - osc, y_min), Point(x + osc, y_max));
|
||||
}
|
||||
|
||||
bool _can_connect(coord_t dist_X, coord_t dist_Y)
|
||||
{
|
||||
const auto TOLERANCE = coord_t(10 * SCALED_EPSILON);
|
||||
return (dist_X >= (this->_line_spacing - this->_line_oscillation) - TOLERANCE)
|
||||
&& (dist_X <= (this->_line_spacing + this->_line_oscillation) + TOLERANCE)
|
||||
&& (dist_Y <= this->_diagonal_distance);
|
||||
}
|
||||
};
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillLine_hpp_
|
||||
185
src/libslic3r/Fill/FillPlanePath.cpp
Normal file
185
src/libslic3r/Fill/FillPlanePath.cpp
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#include "../ClipperUtils.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include "FillPlanePath.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void FillPlanePath::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out)
|
||||
{
|
||||
expolygon.rotate(- direction.first);
|
||||
|
||||
coord_t distance_between_lines = coord_t(scale_(this->spacing) / params.density);
|
||||
|
||||
// align infill across layers using the object's bounding box
|
||||
// Rotated bounding box of the whole object.
|
||||
BoundingBox bounding_box = this->bounding_box.rotated(- direction.first);
|
||||
|
||||
Point shift = this->_centered() ?
|
||||
bounding_box.center() :
|
||||
bounding_box.min;
|
||||
expolygon.translate(-shift.x(), -shift.y());
|
||||
bounding_box.translate(-shift.x(), -shift.y());
|
||||
|
||||
Pointfs pts = _generate(
|
||||
coord_t(ceil(coordf_t(bounding_box.min.x()) / distance_between_lines)),
|
||||
coord_t(ceil(coordf_t(bounding_box.min.y()) / distance_between_lines)),
|
||||
coord_t(ceil(coordf_t(bounding_box.max.x()) / distance_between_lines)),
|
||||
coord_t(ceil(coordf_t(bounding_box.max.y()) / distance_between_lines)),
|
||||
params.resolution);
|
||||
|
||||
if (pts.size() >= 2) {
|
||||
// Convert points to a polyline, upscale.
|
||||
Polylines polylines(1, Polyline());
|
||||
Polyline &polyline = polylines.front();
|
||||
polyline.points.reserve(pts.size());
|
||||
for (const Vec2d &pt : pts)
|
||||
polyline.points.emplace_back(
|
||||
coord_t(floor(pt.x() * distance_between_lines + 0.5)),
|
||||
coord_t(floor(pt.y() * distance_between_lines + 0.5)));
|
||||
polylines = intersection_pl(polylines, expolygon);
|
||||
Polylines chained;
|
||||
if (params.dont_connect() || params.density > 0.5 || polylines.size() <= 1)
|
||||
chained = chain_polylines(std::move(polylines));
|
||||
else
|
||||
connect_infill(std::move(polylines), expolygon, chained, this->spacing, params);
|
||||
// paths must be repositioned and rotated back
|
||||
for (Polyline &pl : chained) {
|
||||
pl.translate(shift.x(), shift.y());
|
||||
pl.rotate(direction.first);
|
||||
}
|
||||
append(polylines_out, std::move(chained));
|
||||
}
|
||||
}
|
||||
|
||||
// Follow an Archimedean spiral, in polar coordinates: r=a+b\theta
|
||||
Pointfs FillArchimedeanChords::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution)
|
||||
{
|
||||
// Radius to achieve.
|
||||
coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5;
|
||||
// Now unwind the spiral.
|
||||
coordf_t a = 1.;
|
||||
coordf_t b = 1./(2.*M_PI);
|
||||
coordf_t theta = 0.;
|
||||
coordf_t r = 1;
|
||||
Pointfs out;
|
||||
//FIXME Vojtech: If used as a solid infill, there is a gap left at the center.
|
||||
out.emplace_back(0, 0);
|
||||
out.emplace_back(1, 0);
|
||||
while (r < rmax) {
|
||||
// Discretization angle to achieve a discretization error lower than resolution.
|
||||
theta += 2. * acos(1. - resolution / r);
|
||||
r = a + b * theta;
|
||||
out.emplace_back(r * cos(theta), r * sin(theta));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Adapted from
|
||||
// http://cpansearch.perl.org/src/KRYDE/Math-PlanePath-122/lib/Math/PlanePath/HilbertCurve.pm
|
||||
//
|
||||
// state=0 3--2 plain
|
||||
// |
|
||||
// 0--1
|
||||
//
|
||||
// state=4 1--2 transpose
|
||||
// | |
|
||||
// 0 3
|
||||
//
|
||||
// state=8
|
||||
//
|
||||
// state=12 3 0 rot180 + transpose
|
||||
// | |
|
||||
// 2--1
|
||||
//
|
||||
static inline Point hilbert_n_to_xy(const size_t n)
|
||||
{
|
||||
static constexpr const int next_state[16] { 4,0,0,12, 0,4,4,8, 12,8,8,4, 8,12,12,0 };
|
||||
static constexpr const int digit_to_x[16] { 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0 };
|
||||
static constexpr const int digit_to_y[16] { 0,0,1,1, 0,1,1,0, 1,1,0,0, 1,0,0,1 };
|
||||
|
||||
// Number of 2 bit digits.
|
||||
size_t ndigits = 0;
|
||||
{
|
||||
size_t nc = n;
|
||||
while(nc > 0) {
|
||||
nc >>= 2;
|
||||
++ ndigits;
|
||||
}
|
||||
}
|
||||
int state = (ndigits & 1) ? 4 : 0;
|
||||
coord_t x = 0;
|
||||
coord_t y = 0;
|
||||
for (int i = (int)ndigits - 1; i >= 0; -- i) {
|
||||
int digit = (n >> (i * 2)) & 3;
|
||||
state += digit;
|
||||
x |= digit_to_x[state] << i;
|
||||
y |= digit_to_y[state] << i;
|
||||
state = next_state[state];
|
||||
}
|
||||
return Point(x, y);
|
||||
}
|
||||
|
||||
Pointfs FillHilbertCurve::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */)
|
||||
{
|
||||
// Minimum power of two square to fit the domain.
|
||||
size_t sz = 2;
|
||||
size_t pw = 1;
|
||||
{
|
||||
size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y);
|
||||
while (sz < sz0) {
|
||||
sz = sz << 1;
|
||||
++ pw;
|
||||
}
|
||||
}
|
||||
|
||||
size_t sz2 = sz * sz;
|
||||
Pointfs line;
|
||||
line.reserve(sz2);
|
||||
for (size_t i = 0; i < sz2; ++ i) {
|
||||
Point p = hilbert_n_to_xy(i);
|
||||
line.emplace_back(p.x() + min_x, p.y() + min_y);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
Pointfs FillOctagramSpiral::_generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */)
|
||||
{
|
||||
// Radius to achieve.
|
||||
coordf_t rmax = std::sqrt(coordf_t(max_x)*coordf_t(max_x)+coordf_t(max_y)*coordf_t(max_y)) * std::sqrt(2.) + 1.5;
|
||||
// Now unwind the spiral.
|
||||
coordf_t r = 0;
|
||||
coordf_t r_inc = sqrt(2.);
|
||||
Pointfs out;
|
||||
out.emplace_back(0., 0.);
|
||||
while (r < rmax) {
|
||||
r += r_inc;
|
||||
coordf_t rx = r / sqrt(2.);
|
||||
coordf_t r2 = r + rx;
|
||||
out.emplace_back( r, 0.);
|
||||
out.emplace_back( r2, rx);
|
||||
out.emplace_back( rx, rx);
|
||||
out.emplace_back( rx, r2);
|
||||
out.emplace_back( 0., r);
|
||||
out.emplace_back(-rx, r2);
|
||||
out.emplace_back(-rx, rx);
|
||||
out.emplace_back(-r2, rx);
|
||||
out.emplace_back(- r, 0.);
|
||||
out.emplace_back(-r2, -rx);
|
||||
out.emplace_back(-rx, -rx);
|
||||
out.emplace_back(-rx, -r2);
|
||||
out.emplace_back( 0., -r);
|
||||
out.emplace_back( rx, -r2);
|
||||
out.emplace_back( rx, -rx);
|
||||
out.emplace_back( r2+r_inc, -rx);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
69
src/libslic3r/Fill/FillPlanePath.hpp
Normal file
69
src/libslic3r/Fill/FillPlanePath.hpp
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef slic3r_FillPlanePath_hpp_
|
||||
#define slic3r_FillPlanePath_hpp_
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// The original Perl code used path generators from Math::PlanePath library:
|
||||
// http://user42.tuxfamily.org/math-planepath/
|
||||
// http://user42.tuxfamily.org/math-planepath/gallery.html
|
||||
|
||||
class FillPlanePath : public Fill
|
||||
{
|
||||
public:
|
||||
~FillPlanePath() override = default;
|
||||
|
||||
protected:
|
||||
void _fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
const std::pair<float, Point> &direction,
|
||||
ExPolygon expolygon,
|
||||
Polylines &polylines_out) override;
|
||||
|
||||
float _layer_angle(size_t idx) const override { return 0.f; }
|
||||
virtual bool _centered() const = 0;
|
||||
virtual Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) = 0;
|
||||
};
|
||||
|
||||
class FillArchimedeanChords : public FillPlanePath
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillArchimedeanChords(*this); };
|
||||
~FillArchimedeanChords() override = default;
|
||||
|
||||
protected:
|
||||
bool _centered() const override { return true; }
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
|
||||
};
|
||||
|
||||
class FillHilbertCurve : public FillPlanePath
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillHilbertCurve(*this); };
|
||||
~FillHilbertCurve() override = default;
|
||||
|
||||
protected:
|
||||
bool _centered() const override { return false; }
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
|
||||
};
|
||||
|
||||
class FillOctagramSpiral : public FillPlanePath
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillOctagramSpiral(*this); };
|
||||
~FillOctagramSpiral() override = default;
|
||||
|
||||
protected:
|
||||
bool _centered() const override { return true; }
|
||||
Pointfs _generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution) override;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillPlanePath_hpp_
|
||||
3248
src/libslic3r/Fill/FillRectilinear.cpp
Normal file
3248
src/libslic3r/Fill/FillRectilinear.cpp
Normal file
File diff suppressed because it is too large
Load diff
141
src/libslic3r/Fill/FillRectilinear.hpp
Normal file
141
src/libslic3r/Fill/FillRectilinear.hpp
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
#ifndef slic3r_FillRectilinear_hpp_
|
||||
#define slic3r_FillRectilinear_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include "FillBase.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Surface;
|
||||
|
||||
class FillRectilinear : public Fill
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillRectilinear(*this); }
|
||||
~FillRectilinear() override = default;
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override;
|
||||
|
||||
protected:
|
||||
// Fill by single directional lines, interconnect the lines along perimeters.
|
||||
bool fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out);
|
||||
|
||||
|
||||
// Fill by multiple sweeps of differing directions.
|
||||
struct SweepParams {
|
||||
float angle_base;
|
||||
float pattern_shift;
|
||||
};
|
||||
bool fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &sweep_params, Polylines &polylines_out);
|
||||
};
|
||||
|
||||
class FillAlignedRectilinear : public FillRectilinear
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillAlignedRectilinear(*this); }
|
||||
~FillAlignedRectilinear() override = default;
|
||||
|
||||
protected:
|
||||
// Always generate infill at the same angle.
|
||||
virtual float _layer_angle(size_t idx) const override { return 0.f; }
|
||||
};
|
||||
|
||||
class FillMonotonic : public FillRectilinear
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillMonotonic(*this); }
|
||||
~FillMonotonic() override = default;
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override;
|
||||
bool no_sort() const override { return true; }
|
||||
};
|
||||
|
||||
class FillMonotonicLine : public FillRectilinear
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillMonotonicLine(*this); }
|
||||
~FillMonotonicLine() override = default;
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override;
|
||||
bool no_sort() const override { return true; }
|
||||
};
|
||||
|
||||
class FillGrid : public FillRectilinear
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillGrid(*this); }
|
||||
~FillGrid() override = default;
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override;
|
||||
|
||||
protected:
|
||||
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
|
||||
float _layer_angle(size_t idx) const override { return 0.f; }
|
||||
};
|
||||
|
||||
class FillTriangles : public FillRectilinear
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillTriangles(*this); }
|
||||
~FillTriangles() override = default;
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override;
|
||||
|
||||
protected:
|
||||
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
|
||||
float _layer_angle(size_t idx) const override { return 0.f; }
|
||||
};
|
||||
|
||||
class FillStars : public FillRectilinear
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillStars(*this); }
|
||||
~FillStars() override = default;
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override;
|
||||
|
||||
protected:
|
||||
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
|
||||
float _layer_angle(size_t idx) const override { return 0.f; }
|
||||
};
|
||||
|
||||
class FillCubic : public FillRectilinear
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillCubic(*this); }
|
||||
~FillCubic() override = default;
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override;
|
||||
|
||||
protected:
|
||||
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
|
||||
float _layer_angle(size_t idx) const override { return 0.f; }
|
||||
};
|
||||
|
||||
class FillSupportBase : public FillRectilinear
|
||||
{
|
||||
public:
|
||||
Fill* clone() const override { return new FillSupportBase(*this); }
|
||||
~FillSupportBase() override = default;
|
||||
Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override;
|
||||
|
||||
protected:
|
||||
// The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill.
|
||||
float _layer_angle(size_t idx) const override { return 0.f; }
|
||||
};
|
||||
|
||||
class FillMonotonicLineWGapFill : public Fill
|
||||
{
|
||||
public:
|
||||
~FillMonotonicLineWGapFill() override = default;
|
||||
void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override;
|
||||
|
||||
protected:
|
||||
Fill* clone() const override { return new FillMonotonicLineWGapFill(*this); };
|
||||
bool no_sort() const override { return true; }
|
||||
|
||||
private:
|
||||
void fill_surface_by_lines(const Surface* surface, const FillParams& params, Polylines& polylines_out);
|
||||
};
|
||||
|
||||
Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing);
|
||||
Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing);
|
||||
Points sample_grid_pattern(const Polygons &polygons, coord_t spacing);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_FillRectilinear_hpp_
|
||||
99
src/libslic3r/Fill/Lightning/DistanceField.cpp
Normal file
99
src/libslic3r/Fill/Lightning/DistanceField.cpp
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "DistanceField.hpp" //Class we're implementing.
|
||||
#include "../FillRectilinear.hpp"
|
||||
#include "../../ClipperUtils.hpp"
|
||||
|
||||
namespace Slic3r::FillLightning
|
||||
{
|
||||
|
||||
constexpr coord_t radius_per_cell_size = 6; // The cell-size should be small compared to the radius, but not so small as to be inefficient.
|
||||
|
||||
DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang) :
|
||||
m_cell_size(radius / radius_per_cell_size),
|
||||
m_supporting_radius(radius)
|
||||
{
|
||||
m_supporting_radius2 = double(radius) * double(radius);
|
||||
// Sample source polygons with a regular grid sampling pattern.
|
||||
for (const ExPolygon &expoly : union_ex(current_outline)) {
|
||||
for (const Point &p : sample_grid_pattern(expoly, m_cell_size)) {
|
||||
// Find a squared distance to the source expolygon boundary.
|
||||
double d2 = std::numeric_limits<double>::max();
|
||||
for (size_t icontour = 0; icontour <= expoly.holes.size(); ++ icontour) {
|
||||
const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1];
|
||||
if (contour.size() > 2) {
|
||||
Point prev = contour.points.back();
|
||||
for (const Point &p2 : contour.points) {
|
||||
d2 = std::min(d2, Line::distance_to_squared(p, prev, p2));
|
||||
prev = p2;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_unsupported_points.emplace_back(p, sqrt(d2));
|
||||
}
|
||||
}
|
||||
m_unsupported_points.sort([&radius](const UnsupportedCell &a, const UnsupportedCell &b) {
|
||||
constexpr coord_t prime_for_hash = 191;
|
||||
return std::abs(b.dist_to_boundary - a.dist_to_boundary) > radius ?
|
||||
a.dist_to_boundary < b.dist_to_boundary :
|
||||
(PointHash{}(a.loc) % prime_for_hash) < (PointHash{}(b.loc) % prime_for_hash);
|
||||
});
|
||||
for (auto it = m_unsupported_points.begin(); it != m_unsupported_points.end(); ++it) {
|
||||
UnsupportedCell& cell = *it;
|
||||
m_unsupported_points_grid.emplace(Point{ cell.loc.x() / m_cell_size, cell.loc.y() / m_cell_size }, it);
|
||||
}
|
||||
}
|
||||
|
||||
void DistanceField::update(const Point& to_node, const Point& added_leaf)
|
||||
{
|
||||
Vec2d v = (added_leaf - to_node).cast<double>();
|
||||
auto l2 = v.squaredNorm();
|
||||
Vec2d extent = Vec2d(-v.y(), v.x()) * m_supporting_radius / sqrt(l2);
|
||||
|
||||
BoundingBox grid;
|
||||
{
|
||||
Point diagonal(m_supporting_radius, m_supporting_radius);
|
||||
Point iextent(extent.cast<coord_t>());
|
||||
grid = BoundingBox(added_leaf - diagonal, added_leaf + diagonal);
|
||||
grid.merge(to_node - iextent);
|
||||
grid.merge(to_node + iextent);
|
||||
grid.merge(added_leaf - iextent);
|
||||
grid.merge(added_leaf + iextent);
|
||||
grid.min /= m_cell_size;
|
||||
grid.max /= m_cell_size;
|
||||
}
|
||||
|
||||
Point grid_loc;
|
||||
for (coord_t row = grid.min.y(); row <= grid.max.y(); ++ row) {
|
||||
grid_loc.y() = row * m_cell_size;
|
||||
for (coord_t col = grid.min.x(); col <= grid.max.y(); ++ col) {
|
||||
grid_loc.x() = col * m_cell_size;
|
||||
// Test inside a circle at the new leaf.
|
||||
if ((grid_loc - added_leaf).cast<double>().squaredNorm() > m_supporting_radius2) {
|
||||
// Not inside a circle at the end of the new leaf.
|
||||
// Test inside a rotated rectangle.
|
||||
Vec2d vx = (grid_loc - to_node).cast<double>();
|
||||
double d = v.dot(vx);
|
||||
if (d >= 0 && d <= l2) {
|
||||
d = extent.dot(vx);
|
||||
if (d < -1. || d > 1.)
|
||||
// Not inside a rotated rectangle.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Inside a circle at the end of the new leaf, or inside a rotated rectangle.
|
||||
// Remove unsupported leafs at this grid location.
|
||||
if (auto it = m_unsupported_points_grid.find(grid_loc); it != m_unsupported_points_grid.end()) {
|
||||
std::list<UnsupportedCell>::iterator& list_it = it->second;
|
||||
UnsupportedCell& cell = *list_it;
|
||||
if ((cell.loc - added_leaf).cast<double>().squaredNorm() <= m_supporting_radius2) {
|
||||
m_unsupported_points.erase(list_it);
|
||||
m_unsupported_points_grid.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
100
src/libslic3r/Fill/Lightning/DistanceField.hpp
Normal file
100
src/libslic3r/Fill/Lightning/DistanceField.hpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef LIGHTNING_DISTANCE_FIELD_H
|
||||
#define LIGHTNING_DISTANCE_FIELD_H
|
||||
|
||||
#include "../../Point.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
|
||||
namespace Slic3r::FillLightning
|
||||
{
|
||||
|
||||
/*!
|
||||
* 2D field that maintains locations which need to be supported for Lightning
|
||||
* Infill.
|
||||
*
|
||||
* This field contains a set of "cells", spaced out in a grid. Each cell
|
||||
* maintains how far it is removed from the edge, which is used to determine
|
||||
* how it gets supported by Lightning Infill.
|
||||
*/
|
||||
class DistanceField
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Construct a new field to calculate Lightning Infill with.
|
||||
* \param radius The radius of influence that an infill line is expected to
|
||||
* support in the layer above.
|
||||
* \param current_outline The total infill area on this layer.
|
||||
* \param current_overhang The overhang that needs to be supported on this
|
||||
* layer.
|
||||
*/
|
||||
DistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang);
|
||||
|
||||
/*!
|
||||
* Gets the next unsupported location to be supported by a new branch.
|
||||
* \param p Output variable for the next point to support.
|
||||
* \return ``true`` if successful, or ``false`` if there are no more points
|
||||
* to consider.
|
||||
*/
|
||||
bool tryGetNextPoint(Point* p) const {
|
||||
if (m_unsupported_points.empty())
|
||||
return false;
|
||||
*p = m_unsupported_points.front().loc;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Update the distance field with a newly added branch.
|
||||
*
|
||||
* The branch is a line extending from \p to_node to \p added_leaf . This
|
||||
* function updates the grid cells so that the distance field knows how far
|
||||
* off it is from being supported by the current pattern. Grid points are
|
||||
* updated with sampling points spaced out by the supporting radius along
|
||||
* the line.
|
||||
* \param to_node The node endpoint of the newly added branch.
|
||||
* \param added_leaf The location of the leaf of the newly added branch,
|
||||
* drawing a straight line to the node.
|
||||
*/
|
||||
void update(const Point& to_node, const Point& added_leaf);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Spacing between grid points to consider supporting.
|
||||
*/
|
||||
coord_t m_cell_size;
|
||||
|
||||
/*!
|
||||
* The radius of the area of the layer above supported by a point on a
|
||||
* branch of a tree.
|
||||
*/
|
||||
coord_t m_supporting_radius;
|
||||
double m_supporting_radius2;
|
||||
|
||||
/*!
|
||||
* Represents a small discrete area of infill that needs to be supported.
|
||||
*/
|
||||
struct UnsupportedCell
|
||||
{
|
||||
UnsupportedCell(Point loc, coord_t dist_to_boundary) : loc(loc), dist_to_boundary(dist_to_boundary) {}
|
||||
// The position of the center of this cell.
|
||||
Point loc;
|
||||
// How far this cell is removed from the ``current_outline`` polygon, the edge of the infill area.
|
||||
coord_t dist_to_boundary;
|
||||
};
|
||||
|
||||
/*!
|
||||
* Cells which still need to be supported at some point.
|
||||
*/
|
||||
std::list<UnsupportedCell> m_unsupported_points;
|
||||
|
||||
/*!
|
||||
* Links the unsupported points to a grid point, so that we can quickly look
|
||||
* up the cell belonging to a certain position in the grid.
|
||||
*/
|
||||
std::unordered_map<Point, std::list<UnsupportedCell>::iterator, PointHash> m_unsupported_points_grid;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
|
||||
#endif //LIGHTNING_DISTANCE_FIELD_H
|
||||
127
src/libslic3r/Fill/Lightning/Generator.cpp
Normal file
127
src/libslic3r/Fill/Lightning/Generator.cpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "Generator.hpp"
|
||||
#include "TreeNode.hpp"
|
||||
|
||||
#include "../../ClipperUtils.hpp"
|
||||
#include "../../Layer.hpp"
|
||||
#include "../../Print.hpp"
|
||||
#include "../../Surface.hpp"
|
||||
|
||||
/* Possible future tasks/optimizations,etc.:
|
||||
* - Improve connecting heuristic to favor connecting to shorter trees
|
||||
* - Change which node of a tree is the root when that would be better in reconnectRoots.
|
||||
* - (For implementation in Infill classes & elsewhere): Outline offset, infill-overlap & perimeter gaps.
|
||||
* - Allow for polylines, i.e. merge Tims PR about polyline fixes
|
||||
* - Unit Tests?
|
||||
* - Optimization: let the square grid store the closest point on boundary
|
||||
* - Optimization: only compute the closest dist to / point on boundary for the outer cells and flood-fill the rest
|
||||
* - Make a pass with Arachne over the output. Somehow.
|
||||
* - Generate all to-be-supported points at once instead of sequentially: See branch interlocking_gen PolygonUtils::spreadDots (Or work with sparse grids.)
|
||||
* - Lots of magic values ... to many to parameterize. But are they the best?
|
||||
* - Move more complex computations from Generator constructor to elsewhere.
|
||||
*/
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
Generator::Generator(const PrintObject &print_object)
|
||||
{
|
||||
const PrintConfig &print_config = print_object.print()->config();
|
||||
const PrintObjectConfig &object_config = print_object.config();
|
||||
const PrintRegionConfig ®ion_config = print_object.shared_regions()->all_regions.front()->config();
|
||||
const std::vector<double> &nozzle_diameters = print_config.nozzle_diameter.values;
|
||||
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
|
||||
// const int sparse_infill_filament = region_config.sparse_infill_filament.value;
|
||||
const double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter));
|
||||
// Note: There's not going to be a layer below the first one, so the 'initial layer height' doesn't have to be taken into account.
|
||||
const double layer_thickness = object_config.layer_height;
|
||||
|
||||
m_infill_extrusion_width = scaled<float>(region_config.sparse_infill_line_width.value);
|
||||
m_supporting_radius = scaled<coord_t>(m_infill_extrusion_width * 0.001 / region_config.sparse_infill_density);
|
||||
|
||||
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
|
||||
const double lightning_infill_straightening_angle = M_PI / 4; // 45 degrees
|
||||
m_wall_supporting_radius = layer_thickness * std::tan(lightning_infill_overhang_angle);
|
||||
m_prune_length = layer_thickness * std::tan(lightning_infill_prune_angle);
|
||||
m_straightening_max_distance = layer_thickness * std::tan(lightning_infill_straightening_angle);
|
||||
|
||||
generateInitialInternalOverhangs(print_object);
|
||||
generateTrees(print_object);
|
||||
}
|
||||
|
||||
void Generator::generateInitialInternalOverhangs(const PrintObject &print_object)
|
||||
{
|
||||
m_overhang_per_layer.resize(print_object.layers().size());
|
||||
const float infill_wall_offset = - m_infill_extrusion_width;
|
||||
|
||||
Polygons infill_area_above;
|
||||
//Iterate from top to bottom, to subtract the overhang areas above from the overhang areas on the layer below, to get only overhang in the top layer where it is overhanging.
|
||||
for (int layer_nr = print_object.layers().size() - 1; layer_nr >= 0; layer_nr--) {
|
||||
Polygons infill_area_here;
|
||||
for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions())
|
||||
for (const Surface& surface : layerm->fill_surfaces.surfaces)
|
||||
if (surface.surface_type == stInternal)
|
||||
append(infill_area_here, offset(surface.expolygon, infill_wall_offset));
|
||||
|
||||
//Remove the part of the infill area that is already supported by the walls.
|
||||
Polygons overhang = diff(offset(infill_area_here, -m_wall_supporting_radius), infill_area_above);
|
||||
|
||||
m_overhang_per_layer[layer_nr] = overhang;
|
||||
infill_area_above = std::move(infill_area_here);
|
||||
}
|
||||
}
|
||||
|
||||
const Layer& Generator::getTreesForLayer(const size_t& layer_id) const
|
||||
{
|
||||
assert(layer_id < m_lightning_layers.size());
|
||||
return m_lightning_layers[layer_id];
|
||||
}
|
||||
|
||||
void Generator::generateTrees(const PrintObject &print_object)
|
||||
{
|
||||
m_lightning_layers.resize(print_object.layers().size());
|
||||
const coord_t infill_wall_offset = - m_infill_extrusion_width;
|
||||
|
||||
std::vector<Polygons> infill_outlines(print_object.layers().size(), Polygons());
|
||||
|
||||
// For-each layer from top to bottom:
|
||||
for (int layer_id = print_object.layers().size() - 1; layer_id >= 0; layer_id--)
|
||||
for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions())
|
||||
for (const Surface &surface : layerm->fill_surfaces.surfaces)
|
||||
if (surface.surface_type == stInternal)
|
||||
append(infill_outlines[layer_id], offset(surface.expolygon, infill_wall_offset));
|
||||
|
||||
// For various operations its beneficial to quickly locate nearby features on the polygon:
|
||||
const size_t top_layer_id = print_object.layers().size() - 1;
|
||||
EdgeGrid::Grid outlines_locator(get_extents(infill_outlines[top_layer_id]).inflated(SCALED_EPSILON));
|
||||
outlines_locator.create(infill_outlines[top_layer_id], locator_cell_size);
|
||||
|
||||
// For-each layer from top to bottom:
|
||||
for (int layer_id = top_layer_id; layer_id >= 0; layer_id--)
|
||||
{
|
||||
Layer& current_lightning_layer = m_lightning_layers[layer_id];
|
||||
Polygons& current_outlines = infill_outlines[layer_id];
|
||||
|
||||
// register all trees propagated from the previous layer as to-be-reconnected
|
||||
std::vector<NodeSPtr> to_be_reconnected_tree_roots = current_lightning_layer.tree_roots;
|
||||
|
||||
current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
|
||||
current_lightning_layer.reconnectRoots(to_be_reconnected_tree_roots, current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
|
||||
|
||||
// Initialize trees for next lower layer from the current one.
|
||||
if (layer_id == 0)
|
||||
return;
|
||||
|
||||
const Polygons& below_outlines = infill_outlines[layer_id - 1];
|
||||
outlines_locator.set_bbox(get_extents(below_outlines).inflated(SCALED_EPSILON));
|
||||
outlines_locator.create(below_outlines, locator_cell_size);
|
||||
|
||||
std::vector<NodeSPtr>& lower_trees = m_lightning_layers[layer_id - 1].tree_roots;
|
||||
for (auto& tree : current_lightning_layer.tree_roots)
|
||||
tree->propagateToNextLayer(lower_trees, below_outlines, outlines_locator, m_prune_length, m_straightening_max_distance, locator_cell_size / 2);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
132
src/libslic3r/Fill/Lightning/Generator.hpp
Normal file
132
src/libslic3r/Fill/Lightning/Generator.hpp
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef LIGHTNING_GENERATOR_H
|
||||
#define LIGHTNING_GENERATOR_H
|
||||
|
||||
#include "Layer.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
class PrintObject;
|
||||
|
||||
namespace FillLightning
|
||||
{
|
||||
|
||||
/*!
|
||||
* Generates the Lightning Infill pattern.
|
||||
*
|
||||
* The lightning infill pattern is designed to use a minimal amount of material
|
||||
* to support the top skin of the print, while still printing with reasonably
|
||||
* consistently flowing lines. It sacrifices strength completely in favour of
|
||||
* top surface quality and reduced print time / material usage.
|
||||
*
|
||||
* Lightning Infill is so named because the patterns it creates resemble a
|
||||
* forked path with one main path and many small lines on the side. These paths
|
||||
* grow out from the sides of the model just below where the top surface needs
|
||||
* to be supported from the inside, so that minimal material is needed.
|
||||
*
|
||||
* This pattern is based on a paper called "Ribbed Support Vaults for 3D
|
||||
* Printing of Hollowed Objects" by Tricard, Claux and Lefebvre:
|
||||
* https://www.researchgate.net/publication/333808588_Ribbed_Support_Vaults_for_3D_Printing_of_Hollowed_Objects
|
||||
*/
|
||||
class Generator // "Just like Nicola used to make!"
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* Create a generator to fill a certain mesh with infill.
|
||||
*
|
||||
* This generator will pre-compute things in preparation of generating
|
||||
* Lightning Infill for the infill areas in that mesh. The infill areas must
|
||||
* already be calculated at this point.
|
||||
* \param mesh The mesh to generate infill for.
|
||||
*/
|
||||
Generator(const PrintObject &print_object);
|
||||
|
||||
/*!
|
||||
* Get a tree of paths generated for a certain layer of the mesh.
|
||||
*
|
||||
* This tree represents the paths that must be traced to print the infill.
|
||||
* \param layer_id The layer number to get the path tree for. This is within
|
||||
* the range of layers of the mesh (not the global layer numbers).
|
||||
* \return A tree structure representing paths to print to create the
|
||||
* Lightning Infill pattern.
|
||||
*/
|
||||
const Layer& getTreesForLayer(const size_t& layer_id) const;
|
||||
|
||||
float infilll_extrusion_width() const { return m_infill_extrusion_width; }
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Calculate the overhangs above the infill areas that need to be supported
|
||||
* by infill.
|
||||
*
|
||||
* Normally, overhangs are only generated for the outside of the model and
|
||||
* only when support is generated. For this pattern, we also need to
|
||||
* generate overhang areas for the inside of the model.
|
||||
*/
|
||||
void generateInitialInternalOverhangs(const PrintObject &print_object);
|
||||
|
||||
/*!
|
||||
* Calculate the tree structure of all layers.
|
||||
*/
|
||||
void generateTrees(const PrintObject &print_object);
|
||||
|
||||
float m_infill_extrusion_width;
|
||||
|
||||
/*!
|
||||
* How far each piece of infill can support skin in the layer above.
|
||||
*/
|
||||
coord_t m_supporting_radius;
|
||||
|
||||
/*!
|
||||
* How far a wall can support the wall above it. If a wall completely
|
||||
* supports the wall above it, no infill needs to support that.
|
||||
*
|
||||
* This is similar to the overhang distance calculated for support. It is
|
||||
* determined by the lightning_infill_overhang_angle setting.
|
||||
*/
|
||||
coord_t m_wall_supporting_radius;
|
||||
|
||||
/*!
|
||||
* How far each piece of infill can support other infill in the layer above.
|
||||
*
|
||||
* This may be different than \ref supporting_radius, because the infill is
|
||||
* printed with one end floating in mid-air. This endpoint will sag more, so
|
||||
* an infill line may need to be supported more than a skin line.
|
||||
*/
|
||||
coord_t m_prune_length;
|
||||
|
||||
/*!
|
||||
* How far a line may be shifted in order to straighten the line out.
|
||||
*
|
||||
* Straightening the line reduces material and time usage and reduces
|
||||
* accelerations needed to print the pattern. However it makes the infill
|
||||
* weak if lines are partially suspended next to the line on the previous
|
||||
* layer.
|
||||
*/
|
||||
coord_t m_straightening_max_distance;
|
||||
|
||||
/*!
|
||||
* For each layer, the overhang that needs to be supported by the pattern.
|
||||
*
|
||||
* This is generated by \ref generateInitialInternalOverhangs.
|
||||
*/
|
||||
std::vector<Polygons> m_overhang_per_layer;
|
||||
|
||||
/*!
|
||||
* For each layer, the generated lightning paths.
|
||||
*
|
||||
* This is generated by \ref generateTrees.
|
||||
*/
|
||||
std::vector<Layer> m_lightning_layers;
|
||||
};
|
||||
|
||||
} // namespace FillLightning
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // LIGHTNING_GENERATOR_H
|
||||
410
src/libslic3r/Fill/Lightning/Layer.cpp
Normal file
410
src/libslic3r/Fill/Lightning/Layer.cpp
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "Layer.hpp" //The class we're implementing.
|
||||
|
||||
#include <iterator> // advance
|
||||
|
||||
#include "DistanceField.hpp"
|
||||
#include "TreeNode.hpp"
|
||||
|
||||
#include "../../Geometry.hpp"
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
coord_t Layer::getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location)
|
||||
{
|
||||
return coord_t((boundary_loc - unsupported_location).cast<double>().norm());
|
||||
}
|
||||
|
||||
Point GroundingLocation::p() const
|
||||
{
|
||||
assert(tree_node || boundary_location);
|
||||
return tree_node ? tree_node->getLocation() : *boundary_location;
|
||||
}
|
||||
|
||||
void Layer::fillLocator(SparseNodeGrid &tree_node_locator)
|
||||
{
|
||||
std::function<void(NodeSPtr)> add_node_to_locator_func = [&tree_node_locator](NodeSPtr node) {
|
||||
tree_node_locator.insert(std::make_pair(Point(node->getLocation().x() / locator_cell_size, node->getLocation().y() / locator_cell_size), node));
|
||||
};
|
||||
for (auto& tree : tree_roots)
|
||||
tree->visitNodes(add_node_to_locator_func);
|
||||
}
|
||||
|
||||
void Layer::generateNewTrees
|
||||
(
|
||||
const Polygons& current_overhang,
|
||||
const Polygons& current_outlines,
|
||||
const EdgeGrid::Grid& outlines_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius
|
||||
)
|
||||
{
|
||||
DistanceField distance_field(supporting_radius, current_outlines, current_overhang);
|
||||
|
||||
SparseNodeGrid tree_node_locator;
|
||||
fillLocator(tree_node_locator);
|
||||
|
||||
// Until no more points need to be added to support all:
|
||||
// Determine next point from tree/outline areas via distance-field
|
||||
Point unsupported_location;
|
||||
while (distance_field.tryGetNextPoint(&unsupported_location)) {
|
||||
GroundingLocation grounding_loc = getBestGroundingLocation(
|
||||
unsupported_location, current_outlines, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator);
|
||||
|
||||
NodeSPtr new_parent;
|
||||
NodeSPtr new_child;
|
||||
this->attach(unsupported_location, grounding_loc, new_child, new_parent);
|
||||
tree_node_locator.insert(std::make_pair(Point(new_child->getLocation().x() / locator_cell_size, new_child->getLocation().y() / locator_cell_size), new_child));
|
||||
if (new_parent)
|
||||
tree_node_locator.insert(std::make_pair(Point(new_parent->getLocation().x() / locator_cell_size, new_parent->getLocation().y() / locator_cell_size), new_parent));
|
||||
// update distance field
|
||||
distance_field.update(grounding_loc.p(), unsupported_location);
|
||||
}
|
||||
}
|
||||
|
||||
static bool polygonCollidesWithLineSegment(const Point from, const Point to, const EdgeGrid::Grid &loc_to_line)
|
||||
{
|
||||
struct Visitor {
|
||||
explicit Visitor(const EdgeGrid::Grid &grid) : grid(grid) {}
|
||||
|
||||
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 = 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.
|
||||
auto segment = grid.segment(*it_contour_and_segment);
|
||||
if (Geometry::segments_intersect(segment.first, segment.second, line.a, line.b)) {
|
||||
this->intersect = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Continue traversing the grid along the edge.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid& grid;
|
||||
Line line;
|
||||
bool intersect = false;
|
||||
} visitor(loc_to_line);
|
||||
|
||||
loc_to_line.visit_cells_intersecting_line(from, to, visitor);
|
||||
return visitor.intersect;
|
||||
}
|
||||
|
||||
GroundingLocation Layer::getBestGroundingLocation
|
||||
(
|
||||
const Point& unsupported_location,
|
||||
const Polygons& current_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius,
|
||||
const SparseNodeGrid& tree_node_locator,
|
||||
const NodeSPtr& exclude_tree
|
||||
)
|
||||
{
|
||||
// Closest point on current_outlines to unsupported_location:
|
||||
Point node_location;
|
||||
{
|
||||
double d2 = std::numeric_limits<double>::max();
|
||||
for (const Polygon &contour : current_outlines)
|
||||
if (contour.size() > 2) {
|
||||
Point prev = contour.points.back();
|
||||
for (const Point &p2 : contour.points) {
|
||||
if (double d = Line::distance_to_squared(unsupported_location, prev, p2); d < d2) {
|
||||
d2 = d;
|
||||
node_location = Geometry::foot_pt({ prev, p2 }, unsupported_location).cast<coord_t>();
|
||||
}
|
||||
prev = p2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto within_dist = coord_t((node_location - unsupported_location).cast<double>().norm());
|
||||
|
||||
NodeSPtr sub_tree{ nullptr };
|
||||
coord_t current_dist = getWeightedDistance(node_location, unsupported_location);
|
||||
if (current_dist >= wall_supporting_radius) { // Only reconnect tree roots to other trees if they are not already close to the outlines.
|
||||
const coord_t search_radius = std::min(current_dist, within_dist);
|
||||
BoundingBox region(unsupported_location - Point(search_radius, search_radius), unsupported_location + Point(search_radius + locator_cell_size, search_radius + locator_cell_size));
|
||||
region.min /= locator_cell_size;
|
||||
region.max /= locator_cell_size;
|
||||
Point grid_addr;
|
||||
for (grid_addr.y() = region.min.y(); grid_addr.y() < region.max.y(); ++ grid_addr.y())
|
||||
for (grid_addr.x() = region.min.x(); grid_addr.x() < region.max.x(); ++ grid_addr.x()) {
|
||||
auto it_range = tree_node_locator.equal_range(grid_addr);
|
||||
for (auto it = it_range.first; it != it_range.second; ++ it) {
|
||||
auto candidate_sub_tree = it->second.lock();
|
||||
if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) &&
|
||||
!(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) &&
|
||||
!polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) {
|
||||
const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius);
|
||||
if (candidate_dist < current_dist) {
|
||||
current_dist = candidate_dist;
|
||||
sub_tree = candidate_sub_tree;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ! sub_tree ?
|
||||
GroundingLocation{ nullptr, node_location } :
|
||||
GroundingLocation{ sub_tree, std::optional<Point>() };
|
||||
}
|
||||
|
||||
bool Layer::attach(
|
||||
const Point& unsupported_location,
|
||||
const GroundingLocation& grounding_loc,
|
||||
NodeSPtr& new_child,
|
||||
NodeSPtr& new_root)
|
||||
{
|
||||
// Update trees & distance fields.
|
||||
if (grounding_loc.boundary_location) {
|
||||
new_root = Node::create(grounding_loc.p(), std::make_optional(grounding_loc.p()));
|
||||
new_child = new_root->addChild(unsupported_location);
|
||||
tree_roots.push_back(new_root);
|
||||
return true;
|
||||
} else {
|
||||
new_child = grounding_loc.tree_node->addChild(unsupported_location);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Layer::reconnectRoots
|
||||
(
|
||||
std::vector<NodeSPtr>& to_be_reconnected_tree_roots,
|
||||
const Polygons& current_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius
|
||||
)
|
||||
{
|
||||
constexpr coord_t tree_connecting_ignore_offset = 100;
|
||||
|
||||
SparseNodeGrid tree_node_locator;
|
||||
fillLocator(tree_node_locator);
|
||||
|
||||
const coord_t within_max_dist = outline_locator.resolution() * 2;
|
||||
for (auto root_ptr : to_be_reconnected_tree_roots)
|
||||
{
|
||||
auto old_root_it = std::find(tree_roots.begin(), tree_roots.end(), root_ptr);
|
||||
|
||||
if (root_ptr->getLastGroundingLocation())
|
||||
{
|
||||
const Point& ground_loc = *root_ptr->getLastGroundingLocation();
|
||||
if (ground_loc != root_ptr->getLocation())
|
||||
{
|
||||
Point new_root_pt;
|
||||
// Find an intersection of the line segment from root_ptr->getLocation() to ground_loc, at within_max_dist from ground_loc.
|
||||
if (lineSegmentPolygonsIntersection(root_ptr->getLocation(), ground_loc, outline_locator, new_root_pt, within_max_dist)) {
|
||||
auto new_root = Node::create(new_root_pt, new_root_pt);
|
||||
root_ptr->addChild(new_root);
|
||||
new_root->reroot();
|
||||
|
||||
tree_node_locator.insert(std::make_pair(Point(new_root->getLocation().x() / locator_cell_size, new_root->getLocation().y() / locator_cell_size), new_root));
|
||||
|
||||
*old_root_it = std::move(new_root); // replace old root with new root
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const coord_t tree_connecting_ignore_width = wall_supporting_radius - tree_connecting_ignore_offset; // Ideally, the boundary size in which the valence rule is ignored would be configurable.
|
||||
GroundingLocation ground =
|
||||
getBestGroundingLocation
|
||||
(
|
||||
root_ptr->getLocation(),
|
||||
current_outlines,
|
||||
outline_locator,
|
||||
supporting_radius,
|
||||
tree_connecting_ignore_width,
|
||||
tree_node_locator,
|
||||
root_ptr
|
||||
);
|
||||
if (ground.boundary_location)
|
||||
{
|
||||
if (*ground.boundary_location == root_ptr->getLocation())
|
||||
continue; // Already on the boundary.
|
||||
|
||||
auto new_root = Node::create(ground.p(), ground.p());
|
||||
auto attach_ptr = root_ptr->closestNode(new_root->getLocation());
|
||||
attach_ptr->reroot();
|
||||
|
||||
new_root->addChild(attach_ptr);
|
||||
tree_node_locator.insert(std::make_pair(new_root->getLocation(), new_root));
|
||||
|
||||
*old_root_it = std::move(new_root); // replace old root with new root
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(ground.tree_node);
|
||||
assert(ground.tree_node != root_ptr);
|
||||
assert(!root_ptr->hasOffspring(ground.tree_node));
|
||||
assert(!ground.tree_node->hasOffspring(root_ptr));
|
||||
|
||||
auto attach_ptr = root_ptr->closestNode(ground.tree_node->getLocation());
|
||||
attach_ptr->reroot();
|
||||
|
||||
ground.tree_node->addChild(attach_ptr);
|
||||
|
||||
// remove old root
|
||||
*old_root_it = std::move(tree_roots.back());
|
||||
tree_roots.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation assumes moving inside, but moving outside should just as well be possible.
|
||||
*/
|
||||
static unsigned int moveInside(const Polygons& polygons, Point& from, int distance, int64_t maxDist2)
|
||||
{
|
||||
Point ret = from;
|
||||
int64_t bestDist2 = std::numeric_limits<int64_t>::max();
|
||||
unsigned int bestPoly = static_cast<unsigned int>(-1);
|
||||
bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
|
||||
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||
{
|
||||
const Polygon &poly = polygons[poly_idx];
|
||||
if (poly.size() < 2)
|
||||
continue;
|
||||
Point p0 = poly[poly.size() - 2];
|
||||
Point p1 = poly.back();
|
||||
// because we compare with vSize2 here (no division by zero), we also need to compare by vSize2 inside the loop
|
||||
// to avoid integer rounding edge cases
|
||||
bool projected_p_beyond_prev_segment = (p1 - p0).cast<int64_t>().dot((from - p0).cast<int64_t>()) >= (p1 - p0).cast<int64_t>().squaredNorm();
|
||||
for (const Point& p2 : poly)
|
||||
{
|
||||
// X = A + Normal(B-A) * (((B-A) dot (P-A)) / VSize(B-A));
|
||||
// = A + (B-A) * ((B-A) dot (P-A)) / VSize2(B-A);
|
||||
// X = P projected on AB
|
||||
const Point& a = p1;
|
||||
const Point& b = p2;
|
||||
const Point& p = from;
|
||||
Point ab = b - a;
|
||||
Point ap = p - a;
|
||||
int64_t ab_length2 = ab.cast<int64_t>().squaredNorm();
|
||||
if (ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other.
|
||||
{
|
||||
p1 = p2; //Skip only one of the points.
|
||||
continue;
|
||||
}
|
||||
int64_t dot_prod = ab.cast<int64_t>().dot(ap.cast<int64_t>());
|
||||
if (dot_prod <= 0) // x is projected to before ab
|
||||
{
|
||||
if (projected_p_beyond_prev_segment)
|
||||
{ // case which looks like: > .
|
||||
projected_p_beyond_prev_segment = false;
|
||||
Point& x = p1;
|
||||
|
||||
int64_t dist2 = (x - p).cast<int64_t>().squaredNorm();
|
||||
if (dist2 < bestDist2)
|
||||
{
|
||||
bestDist2 = dist2;
|
||||
bestPoly = poly_idx;
|
||||
if (distance == 0) {
|
||||
ret = x;
|
||||
} else {
|
||||
// inward direction irrespective of sign of [distance]
|
||||
Point inward_dir = perp((ab.cast<double>().normalized() * scaled<double>(10.0) + (p1 - p0).cast<double>().normalized() * scaled<double>(10.0)).eval()).cast<coord_t>();
|
||||
// MM2INT(10.0) to retain precision for the eventual normalization
|
||||
ret = x + (inward_dir.cast<double>().normalized() * distance).cast<coord_t>();
|
||||
is_already_on_correct_side_of_boundary = inward_dir.cast<int64_t>().dot((p - x).cast<int64_t>()) * distance >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
projected_p_beyond_prev_segment = false;
|
||||
p0 = p1;
|
||||
p1 = p2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (dot_prod >= ab_length2) // x is projected to beyond ab
|
||||
{
|
||||
projected_p_beyond_prev_segment = true;
|
||||
p0 = p1;
|
||||
p1 = p2;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{ // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
|
||||
projected_p_beyond_prev_segment = false;
|
||||
Point x = a + ab * dot_prod / ab_length2;
|
||||
|
||||
int64_t dist2 = (p - x).cast<int64_t>().squaredNorm();
|
||||
if (dist2 < bestDist2)
|
||||
{
|
||||
bestDist2 = dist2;
|
||||
bestPoly = poly_idx;
|
||||
if (distance == 0) { ret = x; }
|
||||
else
|
||||
{
|
||||
// inward or outward depending on the sign of [distance]
|
||||
Vec2d inward_dir = perp((ab.cast<double>().normalized() * distance).eval());
|
||||
ret = x + inward_dir.cast<coord_t>();
|
||||
is_already_on_correct_side_of_boundary = inward_dir.dot((p - x).cast<double>()) >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
p0 = p1;
|
||||
p1 = p2;
|
||||
}
|
||||
}
|
||||
if (is_already_on_correct_side_of_boundary) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside
|
||||
{
|
||||
if (bestDist2 < distance * distance)
|
||||
{
|
||||
from = ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
// from = from; // original point stays unaltered. It is already inside by enough distance
|
||||
}
|
||||
return bestPoly;
|
||||
}
|
||||
else if (bestDist2 < maxDist2)
|
||||
{
|
||||
from = ret;
|
||||
return bestPoly;
|
||||
}
|
||||
return static_cast<unsigned int>(-1);
|
||||
}
|
||||
|
||||
// Returns 'added someting'.
|
||||
Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const
|
||||
{
|
||||
if (tree_roots.empty())
|
||||
return {};
|
||||
|
||||
Polygons result_lines;
|
||||
for (const auto& tree : tree_roots) {
|
||||
// If even the furthest location in the tree is inside the polygon, the entire tree must be inside of the polygon.
|
||||
// (Don't take the root as that may be on the edge and cause rounding errors to register as 'outside'.)
|
||||
constexpr coord_t epsilon = 5;
|
||||
Point should_be_inside = tree->getLocation();
|
||||
moveInside(limit_to_outline, should_be_inside, epsilon, epsilon * epsilon);
|
||||
if (inside(limit_to_outline, should_be_inside))
|
||||
tree->convertToPolylines(result_lines, line_width);
|
||||
}
|
||||
|
||||
// TODO: allow for polylines!
|
||||
Polylines split_lines;
|
||||
for (Polygon &line : result_lines) {
|
||||
if (line.size() <= 1)
|
||||
continue;
|
||||
Point last = line[0];
|
||||
for (size_t point_idx = 1; point_idx < line.size(); point_idx++) {
|
||||
Point here = line[point_idx];
|
||||
split_lines.push_back({ last, here });
|
||||
last = here;
|
||||
}
|
||||
}
|
||||
|
||||
return split_lines;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Lightning
|
||||
88
src/libslic3r/Fill/Lightning/Layer.hpp
Normal file
88
src/libslic3r/Fill/Lightning/Layer.hpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef LIGHTNING_LAYER_H
|
||||
#define LIGHTNING_LAYER_H
|
||||
|
||||
#include "../../EdgeGrid.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r::FillLightning
|
||||
{
|
||||
|
||||
class Node;
|
||||
using NodeSPtr = std::shared_ptr<Node>;
|
||||
using SparseNodeGrid = std::unordered_multimap<Point, std::weak_ptr<Node>, PointHash>;
|
||||
|
||||
struct GroundingLocation
|
||||
{
|
||||
NodeSPtr tree_node; //!< not null if the gounding location is on a tree
|
||||
std::optional<Point> boundary_location; //!< in case the gounding location is on the boundary
|
||||
Point p() const;
|
||||
};
|
||||
|
||||
/*!
|
||||
* A layer of the lightning fill.
|
||||
*
|
||||
* Contains the trees to be printed and propagated to the next layer below.
|
||||
*/
|
||||
class Layer
|
||||
{
|
||||
public:
|
||||
std::vector<NodeSPtr> tree_roots;
|
||||
|
||||
void generateNewTrees
|
||||
(
|
||||
const Polygons& current_overhang,
|
||||
const Polygons& current_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius
|
||||
);
|
||||
|
||||
/*! Determine & connect to connection point in tree/outline.
|
||||
* \param min_dist_from_boundary_for_tree If the unsupported point is closer to the boundary than this then don't consider connecting it to a tree
|
||||
*/
|
||||
GroundingLocation getBestGroundingLocation
|
||||
(
|
||||
const Point& unsupported_location,
|
||||
const Polygons& current_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius,
|
||||
const SparseNodeGrid& tree_node_locator,
|
||||
const NodeSPtr& exclude_tree = nullptr
|
||||
);
|
||||
|
||||
/*!
|
||||
* \param[out] new_child The new child node introduced
|
||||
* \param[out] new_root The new root node if one had been made
|
||||
* \return Whether a new root was added
|
||||
*/
|
||||
bool attach(const Point& unsupported_location, const GroundingLocation& ground, NodeSPtr& new_child, NodeSPtr& new_root);
|
||||
|
||||
void reconnectRoots
|
||||
(
|
||||
std::vector<NodeSPtr>& to_be_reconnected_tree_roots,
|
||||
const Polygons& current_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t supporting_radius,
|
||||
const coord_t wall_supporting_radius
|
||||
);
|
||||
|
||||
Polylines convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const;
|
||||
|
||||
coord_t getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location);
|
||||
|
||||
void fillLocator(SparseNodeGrid& tree_node_locator);
|
||||
};
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
|
||||
#endif // LIGHTNING_LAYER_H
|
||||
411
src/libslic3r/Fill/Lightning/TreeNode.cpp
Normal file
411
src/libslic3r/Fill/Lightning/TreeNode.cpp
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include "TreeNode.hpp"
|
||||
|
||||
#include "../../Geometry.hpp"
|
||||
#include "../../ClipperUtils.hpp"
|
||||
|
||||
namespace Slic3r::FillLightning {
|
||||
|
||||
coord_t Node::getWeightedDistance(const Point& unsupported_location, const coord_t& supporting_radius) const
|
||||
{
|
||||
constexpr coord_t min_valence_for_boost = 0;
|
||||
constexpr coord_t max_valence_for_boost = 4;
|
||||
constexpr coord_t valence_boost_multiplier = 4;
|
||||
|
||||
const size_t valence = (!m_is_root) + m_children.size();
|
||||
const coord_t valence_boost = (min_valence_for_boost < valence && valence < max_valence_for_boost) ? valence_boost_multiplier * supporting_radius : 0;
|
||||
const auto dist_here = coord_t((getLocation() - unsupported_location).cast<double>().norm());
|
||||
return dist_here - valence_boost;
|
||||
}
|
||||
|
||||
bool Node::hasOffspring(const NodeSPtr& to_be_checked) const
|
||||
{
|
||||
if (to_be_checked == shared_from_this())
|
||||
return true;
|
||||
|
||||
for (auto& child_ptr : m_children)
|
||||
if (child_ptr->hasOffspring(to_be_checked))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NodeSPtr Node::addChild(const Point& child_loc)
|
||||
{
|
||||
assert(m_p != child_loc);
|
||||
NodeSPtr child = Node::create(child_loc);
|
||||
return addChild(child);
|
||||
}
|
||||
|
||||
NodeSPtr Node::addChild(NodeSPtr& new_child)
|
||||
{
|
||||
assert(new_child != shared_from_this());
|
||||
//assert(p != new_child->p); // NOTE: No problem for now. Issue to solve later. Maybe even afetr final. Low prio.
|
||||
m_children.push_back(new_child);
|
||||
new_child->m_parent = shared_from_this();
|
||||
new_child->m_is_root = false;
|
||||
return new_child;
|
||||
}
|
||||
|
||||
void Node::propagateToNextLayer(
|
||||
std::vector<NodeSPtr>& next_trees,
|
||||
const Polygons& next_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t prune_distance,
|
||||
const coord_t smooth_magnitude,
|
||||
const coord_t max_remove_colinear_dist) const
|
||||
{
|
||||
auto tree_below = deepCopy();
|
||||
tree_below->prune(prune_distance);
|
||||
tree_below->straighten(smooth_magnitude, max_remove_colinear_dist);
|
||||
if (tree_below->realign(next_outlines, outline_locator, next_trees))
|
||||
next_trees.push_back(tree_below);
|
||||
}
|
||||
|
||||
// NOTE: Depth-first, as currently implemented.
|
||||
// Skips the root (because that has no root itself), but all initial nodes will have the root point anyway.
|
||||
void Node::visitBranches(const std::function<void(const Point&, const Point&)>& visitor) const
|
||||
{
|
||||
for (const auto& node : m_children) {
|
||||
assert(node->m_parent.lock() == shared_from_this());
|
||||
visitor(m_p, node->m_p);
|
||||
node->visitBranches(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Depth-first, as currently implemented.
|
||||
void Node::visitNodes(const std::function<void(NodeSPtr)>& visitor)
|
||||
{
|
||||
visitor(shared_from_this());
|
||||
for (const auto& node : m_children) {
|
||||
assert(node->m_parent.lock() == shared_from_this());
|
||||
node->visitNodes(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
Node::Node(const Point& p, const std::optional<Point>& last_grounding_location /*= std::nullopt*/) :
|
||||
m_is_root(true), m_p(p), m_last_grounding_location(last_grounding_location)
|
||||
{}
|
||||
|
||||
NodeSPtr Node::deepCopy() const
|
||||
{
|
||||
NodeSPtr local_root = Node::create(m_p);
|
||||
local_root->m_is_root = m_is_root;
|
||||
if (m_is_root)
|
||||
{
|
||||
local_root->m_last_grounding_location = m_last_grounding_location.value_or(m_p);
|
||||
}
|
||||
local_root->m_children.reserve(m_children.size());
|
||||
for (const auto& node : m_children)
|
||||
{
|
||||
NodeSPtr child = node->deepCopy();
|
||||
child->m_parent = local_root;
|
||||
local_root->m_children.push_back(child);
|
||||
}
|
||||
return local_root;
|
||||
}
|
||||
|
||||
void Node::reroot(NodeSPtr new_parent /*= nullptr*/)
|
||||
{
|
||||
if (! m_is_root) {
|
||||
auto old_parent = m_parent.lock();
|
||||
old_parent->reroot(shared_from_this());
|
||||
m_children.push_back(old_parent);
|
||||
}
|
||||
|
||||
if (new_parent) {
|
||||
m_children.erase(std::remove(m_children.begin(), m_children.end(), new_parent), m_children.end());
|
||||
m_is_root = false;
|
||||
m_parent = new_parent;
|
||||
} else {
|
||||
m_is_root = true;
|
||||
m_parent.reset();
|
||||
}
|
||||
}
|
||||
|
||||
NodeSPtr Node::closestNode(const Point& loc)
|
||||
{
|
||||
NodeSPtr result = shared_from_this();
|
||||
auto closest_dist2 = coord_t((m_p - loc).cast<double>().norm());
|
||||
|
||||
for (const auto& child : m_children) {
|
||||
NodeSPtr candidate_node = child->closestNode(loc);
|
||||
const auto child_dist2 = coord_t((candidate_node->m_p - loc).cast<double>().norm());
|
||||
if (child_dist2 < closest_dist2) {
|
||||
closest_dist2 = child_dist2;
|
||||
result = candidate_node;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool inside(const Polygons &polygons, const Point p)
|
||||
{
|
||||
int poly_count_inside = 0;
|
||||
for (const Polygon &poly : polygons) {
|
||||
const int is_inside_this_poly = ClipperLib::PointInPolygon(p, poly.points);
|
||||
if (is_inside_this_poly == -1)
|
||||
return true;
|
||||
poly_count_inside += is_inside_this_poly;
|
||||
}
|
||||
return (poly_count_inside % 2) == 1;
|
||||
}
|
||||
|
||||
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist)
|
||||
{
|
||||
struct Visitor {
|
||||
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 = 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.
|
||||
auto segment = grid.segment(*it_contour_and_segment);
|
||||
if (Vec2d ip; Geometry::segment_segment_intersection(segment.first.cast<double>(), segment.second.cast<double>(), this->line_a, this->line_b, ip))
|
||||
if (double d = (this->intersection_pt - this->line_b).squaredNorm(); d < d2min) {
|
||||
this->d2min = d;
|
||||
this->intersection_pt = ip;
|
||||
}
|
||||
}
|
||||
// Continue traversing the grid along the edge.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid& grid;
|
||||
Vec2d line_a;
|
||||
Vec2d line_b;
|
||||
Vec2d intersection_pt;
|
||||
double d2min { std::numeric_limits<double>::max() };
|
||||
} visitor { outline_locator, a.cast<double>(), b.cast<double>() };
|
||||
|
||||
outline_locator.visit_cells_intersecting_line(a, b, visitor);
|
||||
return visitor.d2min < within_max_dist * within_max_dist;
|
||||
}
|
||||
|
||||
bool Node::realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector<NodeSPtr>& rerooted_parts)
|
||||
{
|
||||
if (outlines.empty())
|
||||
return false;
|
||||
|
||||
if (inside(outlines, m_p)) {
|
||||
// Only keep children that have an unbroken connection to here, realign will put the rest in rerooted parts due to recursion:
|
||||
Point coll;
|
||||
bool reground_me = false;
|
||||
m_children.erase(std::remove_if(m_children.begin(), m_children.end(), [&](const NodeSPtr &child) {
|
||||
bool connect_branch = child->realign(outlines, outline_locator, rerooted_parts);
|
||||
// Find an intersection of the line segment from p to child->p, at maximum outline_locator.resolution() * 2 distance from p.
|
||||
if (connect_branch && lineSegmentPolygonsIntersection(child->m_p, m_p, outline_locator, coll, outline_locator.resolution() * 2)) {
|
||||
child->m_last_grounding_location.reset();
|
||||
child->m_parent.reset();
|
||||
child->m_is_root = true;
|
||||
rerooted_parts.push_back(child);
|
||||
reground_me = true;
|
||||
connect_branch = false;
|
||||
}
|
||||
return ! connect_branch;
|
||||
}), m_children.end());
|
||||
if (reground_me)
|
||||
m_last_grounding_location.reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 'Lift' any decendants out of this tree:
|
||||
for (auto& child : m_children)
|
||||
if (child->realign(outlines, outline_locator, rerooted_parts)) {
|
||||
child->m_last_grounding_location = m_p;
|
||||
child->m_parent.reset();
|
||||
child->m_is_root = true;
|
||||
rerooted_parts.push_back(child);
|
||||
}
|
||||
|
||||
m_children.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
void Node::straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist)
|
||||
{
|
||||
straighten(magnitude, m_p, 0, max_remove_colinear_dist * max_remove_colinear_dist);
|
||||
}
|
||||
|
||||
Node::RectilinearJunction Node::straighten(
|
||||
const coord_t magnitude,
|
||||
const Point& junction_above,
|
||||
const coord_t accumulated_dist,
|
||||
const coord_t max_remove_colinear_dist2)
|
||||
{
|
||||
constexpr coord_t junction_magnitude_factor_numerator = 3;
|
||||
constexpr coord_t junction_magnitude_factor_denominator = 4;
|
||||
|
||||
const coord_t junction_magnitude = magnitude * junction_magnitude_factor_numerator / junction_magnitude_factor_denominator;
|
||||
if (m_children.size() == 1)
|
||||
{
|
||||
auto child_p = m_children.front();
|
||||
auto child_dist = coord_t((m_p - child_p->m_p).cast<double>().norm());
|
||||
RectilinearJunction junction_below = child_p->straighten(magnitude, junction_above, accumulated_dist + child_dist, max_remove_colinear_dist2);
|
||||
coord_t total_dist_to_junction_below = junction_below.total_recti_dist;
|
||||
Point a = junction_above;
|
||||
Point b = junction_below.junction_loc;
|
||||
if (a != b) // should always be true!
|
||||
{
|
||||
Point ab = b - a;
|
||||
Point destination = a + ab * accumulated_dist / std::max(coord_t(1), total_dist_to_junction_below);
|
||||
if ((destination - m_p).cast<double>().squaredNorm() <= magnitude * magnitude)
|
||||
m_p = destination;
|
||||
else
|
||||
m_p += ((destination - m_p).cast<double>().normalized() * magnitude).cast<coord_t>();
|
||||
}
|
||||
{ // remove nodes on linear segments
|
||||
constexpr coord_t close_enough = 10;
|
||||
|
||||
child_p = m_children.front(); //recursive call to straighten might have removed the child
|
||||
const NodeSPtr& parent_node = m_parent.lock();
|
||||
if (parent_node &&
|
||||
(child_p->m_p - parent_node->m_p).cast<double>().squaredNorm() < max_remove_colinear_dist2 &&
|
||||
Line::distance_to_squared(m_p, parent_node->m_p, child_p->m_p) < close_enough * close_enough) {
|
||||
child_p->m_parent = m_parent;
|
||||
for (auto& sibling : parent_node->m_children)
|
||||
{ // find this node among siblings
|
||||
if (sibling == shared_from_this())
|
||||
{
|
||||
sibling = child_p; // replace this node by child
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return junction_below;
|
||||
}
|
||||
else
|
||||
{
|
||||
constexpr coord_t weight = 1000;
|
||||
Point junction_moving_dir = ((junction_above - m_p).cast<double>().normalized() * weight).cast<coord_t>();
|
||||
bool prevent_junction_moving = false;
|
||||
for (auto& child_p : m_children)
|
||||
{
|
||||
const auto child_dist = coord_t((m_p - child_p->m_p).cast<double>().norm());
|
||||
RectilinearJunction below = child_p->straighten(magnitude, m_p, child_dist, max_remove_colinear_dist2);
|
||||
|
||||
junction_moving_dir += ((below.junction_loc - m_p).cast<double>().normalized() * weight).cast<coord_t>();
|
||||
if (below.total_recti_dist < magnitude) // TODO: make configurable?
|
||||
{
|
||||
prevent_junction_moving = true; // prevent flipflopping in branches due to straightening and junctoin moving clashing
|
||||
}
|
||||
}
|
||||
if (junction_moving_dir != Point(0, 0) && ! m_children.empty() && ! m_is_root && ! prevent_junction_moving)
|
||||
{
|
||||
auto junction_moving_dir_len = coord_t(junction_moving_dir.norm());
|
||||
if (junction_moving_dir_len > junction_magnitude)
|
||||
{
|
||||
junction_moving_dir = junction_moving_dir * junction_magnitude / junction_moving_dir_len;
|
||||
}
|
||||
m_p += junction_moving_dir;
|
||||
}
|
||||
return RectilinearJunction{ accumulated_dist, m_p };
|
||||
}
|
||||
}
|
||||
|
||||
// Prune the tree from the extremeties (leaf-nodes) until the pruning distance is reached.
|
||||
coord_t Node::prune(const coord_t& pruning_distance)
|
||||
{
|
||||
if (pruning_distance <= 0)
|
||||
return 0;
|
||||
|
||||
coord_t max_distance_pruned = 0;
|
||||
for (auto child_it = m_children.begin(); child_it != m_children.end(); ) {
|
||||
auto& child = *child_it;
|
||||
coord_t dist_pruned_child = child->prune(pruning_distance);
|
||||
if (dist_pruned_child >= pruning_distance)
|
||||
{ // pruning is finished for child; dont modify further
|
||||
max_distance_pruned = std::max(max_distance_pruned, dist_pruned_child);
|
||||
++child_it;
|
||||
} else {
|
||||
const Point a = getLocation();
|
||||
const Point b = child->getLocation();
|
||||
const Point ba = a - b;
|
||||
const auto ab_len = coord_t(ba.cast<double>().norm());
|
||||
if (dist_pruned_child + ab_len <= pruning_distance) {
|
||||
// we're still in the process of pruning
|
||||
assert(child->m_children.empty() && "when pruning away a node all it's children must already have been pruned away");
|
||||
max_distance_pruned = std::max(max_distance_pruned, dist_pruned_child + ab_len);
|
||||
child_it = m_children.erase(child_it);
|
||||
} else {
|
||||
// pruning stops in between this node and the child
|
||||
const Point n = b + (ba.cast<double>().normalized() * (pruning_distance - dist_pruned_child)).cast<coord_t>();
|
||||
assert(std::abs((n - b).cast<double>().norm() + dist_pruned_child - pruning_distance) < 10 && "total pruned distance must be equal to the pruning_distance");
|
||||
max_distance_pruned = std::max(max_distance_pruned, pruning_distance);
|
||||
child->setLocation(n);
|
||||
++child_it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return max_distance_pruned;
|
||||
}
|
||||
|
||||
void Node::convertToPolylines(Polygons& output, const coord_t line_width) const
|
||||
{
|
||||
Polygons result;
|
||||
output.emplace_back();
|
||||
convertToPolylines(0, result);
|
||||
removeJunctionOverlap(result, line_width);
|
||||
append(output, std::move(result));
|
||||
}
|
||||
|
||||
void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const
|
||||
{
|
||||
if (m_children.empty()) {
|
||||
output[long_line_idx].points.push_back(m_p);
|
||||
return;
|
||||
}
|
||||
size_t first_child_idx = rand() % m_children.size();
|
||||
m_children[first_child_idx]->convertToPolylines(long_line_idx, output);
|
||||
output[long_line_idx].points.push_back(m_p);
|
||||
|
||||
for (size_t idx_offset = 1; idx_offset < m_children.size(); idx_offset++) {
|
||||
size_t child_idx = (first_child_idx + idx_offset) % m_children.size();
|
||||
const Node& child = *m_children[child_idx];
|
||||
output.emplace_back();
|
||||
size_t child_line_idx = output.size() - 1;
|
||||
child.convertToPolylines(child_line_idx, output);
|
||||
output[child_line_idx].points.emplace_back(m_p);
|
||||
}
|
||||
}
|
||||
|
||||
void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_width) const
|
||||
{
|
||||
const coord_t reduction = line_width / 2; // TODO make configurable?
|
||||
for (auto poly_it = result_lines.begin(); poly_it != result_lines.end(); ) {
|
||||
Polygon &polyline = *poly_it;
|
||||
if (polyline.size() <= 1) {
|
||||
polyline = std::move(result_lines.back());
|
||||
result_lines.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
coord_t to_be_reduced = reduction;
|
||||
Point a = polyline.back();
|
||||
for (int point_idx = polyline.size() - 2; point_idx >= 0; point_idx--) {
|
||||
const Point b = polyline[point_idx];
|
||||
const Point ab = b - a;
|
||||
const auto ab_len = coord_t(ab.cast<double>().norm());
|
||||
if (ab_len >= to_be_reduced) {
|
||||
polyline.points.back() = a + (ab.cast<double>() * (double(to_be_reduced) / ab_len)).cast<coord_t>();
|
||||
break;
|
||||
} else {
|
||||
to_be_reduced -= ab_len;
|
||||
polyline.points.pop_back();
|
||||
}
|
||||
a = b;
|
||||
}
|
||||
|
||||
if (polyline.size() <= 1) {
|
||||
polyline = std::move(result_lines.back());
|
||||
result_lines.pop_back();
|
||||
} else
|
||||
++ poly_it;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
275
src/libslic3r/Fill/Lightning/TreeNode.hpp
Normal file
275
src/libslic3r/Fill/Lightning/TreeNode.hpp
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#ifndef LIGHTNING_TREE_NODE_H
|
||||
#define LIGHTNING_TREE_NODE_H
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "../../EdgeGrid.hpp"
|
||||
#include "../../Polygon.hpp"
|
||||
|
||||
namespace Slic3r::FillLightning
|
||||
{
|
||||
|
||||
constexpr auto locator_cell_size = scaled<coord_t>(4.);
|
||||
|
||||
class Node;
|
||||
|
||||
using NodeSPtr = std::shared_ptr<Node>;
|
||||
|
||||
// NOTE: As written, this struct will only be valid for a single layer, will have to be updated for the next.
|
||||
// NOTE: Reasons for implementing this with some separate closures:
|
||||
// - keep clear deliniation during development
|
||||
// - possibility of multiple distance field strategies
|
||||
|
||||
/*!
|
||||
* A single vertex of a Lightning Tree, the structure that determines the paths
|
||||
* to be printed to form Lightning Infill.
|
||||
*
|
||||
* In essence these vertices are just a position linked to other positions in
|
||||
* 2D. The nodes have a hierarchical structure of parents and children, forming
|
||||
* a tree. The class also has some helper functions specific to Lightning Infill
|
||||
* e.g. to straighten the paths around this node.
|
||||
*/
|
||||
class Node : public std::enable_shared_from_this<Node>
|
||||
{
|
||||
public:
|
||||
// Workaround for private/protected constructors and 'make_shared': https://stackoverflow.com/a/27832765
|
||||
template<typename ...Arg> NodeSPtr static create(Arg&&...arg)
|
||||
{
|
||||
struct EnableMakeShared : public Node
|
||||
{
|
||||
EnableMakeShared(Arg&&...arg) : Node(std::forward<Arg>(arg)...) {}
|
||||
};
|
||||
return std::make_shared<EnableMakeShared>(std::forward<Arg>(arg)...);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Get the position on this layer that this node represents, a vertex of the
|
||||
* path to print.
|
||||
* \return The position that this node represents.
|
||||
*/
|
||||
const Point& getLocation() const { return m_p; }
|
||||
|
||||
/*!
|
||||
* Change the position on this layer that the node represents.
|
||||
* \param p The position that the node needs to represent.
|
||||
*/
|
||||
void setLocation(const Point& p) { m_p = p; }
|
||||
|
||||
/*!
|
||||
* Construct a new ``Node`` instance and add it as a child of
|
||||
* this node.
|
||||
* \param p The location of the new node.
|
||||
* \return A shared pointer to the new node.
|
||||
*/
|
||||
NodeSPtr addChild(const Point& p);
|
||||
|
||||
/*!
|
||||
* Add an existing ``Node`` as a child of this node.
|
||||
* \param new_child The node that must be added as a child.
|
||||
* \return Always returns \p new_child.
|
||||
*/
|
||||
NodeSPtr addChild(NodeSPtr& new_child);
|
||||
|
||||
/*!
|
||||
* Propagate this node's sub-tree to the next layer.
|
||||
*
|
||||
* Creates a copy of this tree, realign it to the new layer boundaries
|
||||
* \p next_outlines and reduce (i.e. prune and straighten) it. A copy of
|
||||
* this node and all of its descendant nodes will be added to the
|
||||
* \p next_trees vector.
|
||||
* \param next_trees A collection of tree nodes to use for the next layer.
|
||||
* \param next_outlines The shape of the layer below, to make sure that the
|
||||
* tree stays within the bounds of the infill area.
|
||||
* \param prune_distance The maximum distance that a leaf node may be moved
|
||||
* such that it still supports the current node.
|
||||
* \param smooth_magnitude The maximum distance that a line may be shifted
|
||||
* to straighten the tree's paths, such that it still supports the current
|
||||
* paths.
|
||||
* \param max_remove_colinear_dist The maximum distance of a line-segment
|
||||
* from which straightening may remove a colinear point.
|
||||
*/
|
||||
void propagateToNextLayer
|
||||
(
|
||||
std::vector<NodeSPtr>& next_trees,
|
||||
const Polygons& next_outlines,
|
||||
const EdgeGrid::Grid& outline_locator,
|
||||
const coord_t prune_distance,
|
||||
const coord_t smooth_magnitude,
|
||||
const coord_t max_remove_colinear_dist
|
||||
) const;
|
||||
|
||||
/*!
|
||||
* Executes a given function for every line segment in this node's sub-tree.
|
||||
*
|
||||
* The function takes two `Point` arguments. These arguments will be filled
|
||||
* in with the higher-order node (closer to the root) first, and the
|
||||
* downtree node (closer to the leaves) as the second argument. The segment
|
||||
* from this node's parent to this node itself is not included.
|
||||
* The order in which the segments are visited is depth-first.
|
||||
* \param visitor A function to execute for every branch in the node's sub-
|
||||
* tree.
|
||||
*/
|
||||
void visitBranches(const std::function<void(const Point&, const Point&)>& visitor) const;
|
||||
|
||||
/*!
|
||||
* Execute a given function for every node in this node's sub-tree.
|
||||
*
|
||||
* The visitor function takes a node as input. This node is not const, so
|
||||
* this can be used to change the tree.
|
||||
* Nodes are visited in depth-first order. This node itself is visited as
|
||||
* well (pre-order).
|
||||
* \param visitor A function to execute for every node in this node's sub-
|
||||
* tree.
|
||||
*/
|
||||
void visitNodes(const std::function<void(NodeSPtr)>& visitor);
|
||||
|
||||
/*!
|
||||
* Get a weighted distance from an unsupported point to this node (given the current supporting radius).
|
||||
*
|
||||
* When attaching a unsupported location to a node, not all nodes have the same priority.
|
||||
* (Eucludian) closer nodes are prioritised, but that's not the whole story.
|
||||
* For instance, we give some nodes a 'valence boost' depending on the nr. of branches.
|
||||
* \param unsupported_location The (unsuppported) location of which the weighted distance needs to be calculated.
|
||||
* \param supporting_radius The maximum distance which can be bridged without (infill) supporting it.
|
||||
* \return The weighted distance.
|
||||
*/
|
||||
coord_t getWeightedDistance(const Point& unsupported_location, const coord_t& supporting_radius) const;
|
||||
|
||||
/*!
|
||||
* Returns whether this node is the root of a lightning tree. It is the root
|
||||
* if it has no parents.
|
||||
* \return ``true`` if this node is the root (no parents) or ``false`` if it
|
||||
* is a child node of some other node.
|
||||
*/
|
||||
bool isRoot() const { return m_is_root; }
|
||||
|
||||
/*!
|
||||
* Reverse the parent-child relationship all the way to the root, from this node onward.
|
||||
* This has the effect of 're-rooting' the tree at the current node if no immediate parent is given as argument.
|
||||
* That is, the current node will become the root, it's (former) parent if any, will become one of it's children.
|
||||
* This is then recursively bubbled up until it reaches the (former) root, which then will become a leaf.
|
||||
* \param new_parent The (new) parent-node of the root, useful for recursing or immediately attaching the node to another tree.
|
||||
*/
|
||||
void reroot(NodeSPtr new_parent = nullptr);
|
||||
|
||||
/*!
|
||||
* Retrieves the closest node to the specified location.
|
||||
* \param loc The specified location.
|
||||
* \result The branch that starts at the position closest to the location within this tree.
|
||||
*/
|
||||
NodeSPtr closestNode(const Point& loc);
|
||||
|
||||
/*!
|
||||
* Returns whether the given tree node is a descendant of this node.
|
||||
*
|
||||
* If this node itself is given, it is also considered to be a descendant.
|
||||
* \param to_be_checked A node to find out whether it is a descendant of
|
||||
* this node.
|
||||
* \return ``true`` if the given node is a descendant or this node itself,
|
||||
* or ``false`` if it is not in the sub-tree.
|
||||
*/
|
||||
bool hasOffspring(const NodeSPtr& to_be_checked) const;
|
||||
|
||||
protected:
|
||||
Node() = delete; // Don't allow empty contruction
|
||||
|
||||
/*!
|
||||
* Construct a new node, either for insertion in a tree or as root.
|
||||
* \param p The physical location in the 2D layer that this node represents.
|
||||
* Connecting other nodes to this node indicates that a line segment should
|
||||
* be drawn between those two physical positions.
|
||||
*/
|
||||
Node(const Point& p, const std::optional<Point>& last_grounding_location = std::nullopt);
|
||||
|
||||
/*!
|
||||
* Copy this node and its entire sub-tree.
|
||||
* \return The equivalent of this node in the copy (the root of the new sub-
|
||||
* tree).
|
||||
*/
|
||||
NodeSPtr deepCopy() const;
|
||||
|
||||
/*! Reconnect trees from the layer above to the new outlines of the lower layer.
|
||||
* \return Wether or not the root is kept (false is no, true is yes).
|
||||
*/
|
||||
bool realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector<NodeSPtr>& rerooted_parts);
|
||||
|
||||
struct RectilinearJunction
|
||||
{
|
||||
coord_t total_recti_dist; //!< rectilinear distance along the tree from the last junction above to the junction below
|
||||
Point junction_loc; //!< junction location below
|
||||
};
|
||||
|
||||
/*!
|
||||
* Smoothen the tree to make it a bit more printable, while still supporting
|
||||
* the trees above.
|
||||
* \param magnitude The maximum allowed distance to move the node.
|
||||
* \param max_remove_colinear_dist Maximum distance of the (compound) line-segment from which a co-linear point may be removed.
|
||||
*/
|
||||
void straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist);
|
||||
|
||||
/*! Recursive part of \ref straighten(.)
|
||||
* \param junction_above The last seen junction with multiple children above
|
||||
* \param accumulated_dist The distance along the tree from the last seen junction to this node
|
||||
* \param max_remove_colinear_dist2 Maximum distance _squared_ of the (compound) line-segment from which a co-linear point may be removed.
|
||||
* \return the total distance along the tree from the last junction above to the first next junction below and the location of the next junction below
|
||||
*/
|
||||
RectilinearJunction straighten(const coord_t magnitude, const Point& junction_above, const coord_t accumulated_dist, const coord_t max_remove_colinear_dist2);
|
||||
|
||||
/*! Prune the tree from the extremeties (leaf-nodes) until the pruning distance is reached.
|
||||
* \return The distance that has been pruned. If less than \p distance, then the whole tree was puned away.
|
||||
*/
|
||||
coord_t prune(const coord_t& distance);
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Convert the tree into polylines
|
||||
*
|
||||
* At each junction one line is chosen at random to continue
|
||||
*
|
||||
* The lines start at a leaf and end in a junction
|
||||
*
|
||||
* \param output all branches in this tree connected into polylines
|
||||
*/
|
||||
void convertToPolylines(Polygons& output, const coord_t line_width) const;
|
||||
|
||||
/*! If this was ever a direct child of the root, it'll have a previous grounding location.
|
||||
*
|
||||
* This needs to be known when roots are reconnected, so that the last (higher) layer is supported by the next one.
|
||||
*/
|
||||
const std::optional<Point>& getLastGroundingLocation() const { return m_last_grounding_location; }
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Convert the tree into polylines
|
||||
*
|
||||
* At each junction one line is chosen at random to continue
|
||||
*
|
||||
* The lines start at a leaf and end in a junction
|
||||
*
|
||||
* \param long_line a reference to a polyline in \p output which to continue building on in the recursion
|
||||
* \param output all branches in this tree connected into polylines
|
||||
*/
|
||||
void convertToPolylines(size_t long_line_idx, Polygons& output) const;
|
||||
|
||||
void removeJunctionOverlap(Polygons& polylines, const coord_t line_width) const;
|
||||
|
||||
bool m_is_root;
|
||||
Point m_p;
|
||||
std::weak_ptr<Node> m_parent;
|
||||
std::vector<NodeSPtr> m_children;
|
||||
|
||||
std::optional<Point> m_last_grounding_location; //<! The last known grounding location, see 'getLastGroundingLocation()'.
|
||||
};
|
||||
|
||||
bool inside(const Polygons &polygons, const Point p);
|
||||
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist);
|
||||
|
||||
} // namespace Slic3r::FillLightning
|
||||
|
||||
#endif // LIGHTNING_TREE_NODE_H
|
||||
263
src/libslic3r/Flow.cpp
Normal file
263
src/libslic3r/Flow.cpp
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
#include "Flow.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Print.hpp"
|
||||
#include <cmath>
|
||||
#include <assert.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
// Mark string for localization and translate.
|
||||
#define L(s) Slic3r::I18N::translate(s)
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
FlowErrorNegativeSpacing::FlowErrorNegativeSpacing() :
|
||||
FlowError("Flow::spacing() produced negative spacing. Did you set some extrusion width too small?") {}
|
||||
|
||||
FlowErrorNegativeFlow::FlowErrorNegativeFlow() :
|
||||
FlowError("Flow::mm3_per_mm() produced negative flow. Did you set some extrusion width too small?") {}
|
||||
|
||||
// This static method returns a sane extrusion width default.
|
||||
float Flow::auto_extrusion_width(FlowRole role, float nozzle_diameter)
|
||||
{
|
||||
switch (role) {
|
||||
case frSupportMaterial:
|
||||
case frSupportMaterialInterface:
|
||||
case frSupportTransition:
|
||||
case frTopSolidInfill:
|
||||
return nozzle_diameter;
|
||||
default:
|
||||
case frExternalPerimeter:
|
||||
case frPerimeter:
|
||||
case frSolidInfill:
|
||||
case frInfill:
|
||||
return 1.125f * nozzle_diameter;
|
||||
}
|
||||
}
|
||||
|
||||
// Used by the Flow::extrusion_width() funtion to provide hints to the user on default extrusion width values,
|
||||
// and to provide reasonable values to the PlaceholderParser.
|
||||
static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
|
||||
{
|
||||
if (opt_key == "inner_wall_line_width" ||
|
||||
// or all the defaults:
|
||||
opt_key == "line_width" || opt_key == "initial_layer_line_width")
|
||||
return frPerimeter;
|
||||
else if (opt_key == "outer_wall_line_width")
|
||||
return frExternalPerimeter;
|
||||
else if (opt_key == "sparse_infill_line_width")
|
||||
return frInfill;
|
||||
else if (opt_key == "internal_solid_infill_line_width")
|
||||
return frSolidInfill;
|
||||
else if (opt_key == "top_surface_line_width")
|
||||
return frTopSolidInfill;
|
||||
else if (opt_key == "support_line_width")
|
||||
return frSupportMaterial;
|
||||
else
|
||||
throw Slic3r::RuntimeError("opt_key_to_flow_role: invalid argument");
|
||||
};
|
||||
|
||||
static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key)
|
||||
{
|
||||
throw FlowErrorMissingVariable((boost::format(L("Failed to calculate line width of %1%. Can not get value of \"%2%\" ")) % opt_key % dependent_opt_key).str());
|
||||
}
|
||||
|
||||
// Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
|
||||
double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionFloatOrPercent* opt, const ConfigOptionResolver& config, const unsigned int first_printing_extruder)
|
||||
{
|
||||
assert(opt != nullptr);
|
||||
|
||||
bool first_layer = boost::starts_with(opt_key, "initial_layer_");
|
||||
|
||||
#if 0
|
||||
// This is the logic used for skit / brim, but not for the rest of the 1st layer.
|
||||
if (opt->value == 0. && first_layer) {
|
||||
// The "initial_layer_line_width" was set to zero, try a substitute.
|
||||
opt = config.option<ConfigOptionFloatOrPercent>("inner_wall_line_width");
|
||||
if (opt == nullptr)
|
||||
throw_on_missing_variable(opt_key, "inner_wall_line_width");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (opt->value == 0.) {
|
||||
// The role specific extrusion width value was set to zero, try the role non-specific extrusion width.
|
||||
opt = config.option<ConfigOptionFloatOrPercent>("line_width");
|
||||
if (opt == nullptr)
|
||||
throw_on_missing_variable(opt_key, "line_width");
|
||||
// Use the "layer_height" instead of "initial_layer_print_height".
|
||||
first_layer = false;
|
||||
}
|
||||
|
||||
if (opt->percent) {
|
||||
auto opt_key_layer_height = first_layer ? "initial_layer_print_height" : "layer_height";
|
||||
auto opt_layer_height = config.option(opt_key_layer_height);
|
||||
if (opt_layer_height == nullptr)
|
||||
throw_on_missing_variable(opt_key, opt_key_layer_height);
|
||||
assert(! first_layer || ! static_cast<const ConfigOptionFloatOrPercent*>(opt_layer_height)->percent);
|
||||
return opt->get_abs_value(opt_layer_height->getFloat());
|
||||
}
|
||||
|
||||
if (opt->value == 0.) {
|
||||
// If user left option to 0, calculate a sane default width.
|
||||
auto opt_nozzle_diameters = config.option<ConfigOptionFloats>("nozzle_diameter");
|
||||
if (opt_nozzle_diameters == nullptr)
|
||||
throw_on_missing_variable(opt_key, "nozzle_diameter");
|
||||
return auto_extrusion_width(opt_key_to_flow_role(opt_key), float(opt_nozzle_diameters->get_at(first_printing_extruder)));
|
||||
}
|
||||
|
||||
return opt->value;
|
||||
}
|
||||
|
||||
// Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
|
||||
double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResolver &config, const unsigned int first_printing_extruder)
|
||||
{
|
||||
return extrusion_width(opt_key, config.option<ConfigOptionFloatOrPercent>(opt_key), config, first_printing_extruder);
|
||||
}
|
||||
|
||||
// This constructor builds a Flow object from an extrusion width config setting
|
||||
// and other context properties.
|
||||
Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloat &width, float nozzle_diameter, float height)
|
||||
{
|
||||
if (height <= 0)
|
||||
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
|
||||
|
||||
float w;
|
||||
if (width.value == 0.) {
|
||||
// If user left option to 0, calculate a sane default width.
|
||||
w = auto_extrusion_width(role, nozzle_diameter);
|
||||
} else {
|
||||
// If user set a manual value, use it.
|
||||
w = float(width.value);
|
||||
}
|
||||
|
||||
return Flow(w, height, rounded_rectangle_extrusion_spacing(w, height), nozzle_diameter, false);
|
||||
}
|
||||
|
||||
// Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions.
|
||||
Flow Flow::with_spacing(float new_spacing) const
|
||||
{
|
||||
Flow out = *this;
|
||||
if (m_bridge) {
|
||||
// Diameter of the rounded extrusion.
|
||||
assert(m_width == m_height);
|
||||
float gap = m_spacing - m_width;
|
||||
auto new_diameter = new_spacing - gap;
|
||||
out.m_width = out.m_height = new_diameter;
|
||||
} else {
|
||||
assert(m_width >= m_height);
|
||||
out.m_width += new_spacing - m_spacing;
|
||||
if (out.m_width < out.m_height)
|
||||
throw Slic3r::InvalidArgument("Invalid spacing supplied to Flow::with_spacing()");
|
||||
}
|
||||
out.m_spacing = new_spacing;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing.
|
||||
Flow Flow::with_cross_section(float area_new) const
|
||||
{
|
||||
assert(! m_bridge);
|
||||
assert(m_width >= m_height);
|
||||
|
||||
// Adjust for bridge_flow, maintain the extrusion spacing.
|
||||
float area = this->mm3_per_mm();
|
||||
if (area_new > area + EPSILON) {
|
||||
// Increasing the flow rate.
|
||||
float new_full_spacing = area_new / m_height;
|
||||
if (new_full_spacing > m_spacing) {
|
||||
// Filling up the spacing without an air gap. Grow the extrusion in height.
|
||||
float height = area_new / m_spacing;
|
||||
return Flow(rounded_rectangle_extrusion_width_from_spacing(m_spacing, height), height, m_spacing, m_nozzle_diameter, false);
|
||||
} else {
|
||||
return this->with_width(rounded_rectangle_extrusion_width_from_spacing(area / m_height, m_height));
|
||||
}
|
||||
} else if (area_new < area - EPSILON) {
|
||||
// Decreasing the flow rate.
|
||||
float width_new = m_width - (area - area_new) / m_height;
|
||||
assert(width_new > 0);
|
||||
if (width_new > m_height) {
|
||||
// Shrink the extrusion width.
|
||||
return this->with_width(width_new);
|
||||
} else {
|
||||
// Create a rounded extrusion.
|
||||
auto dmr = float(sqrt(area_new / M_PI));
|
||||
return Flow(dmr, dmr, m_spacing, m_nozzle_diameter, false);
|
||||
}
|
||||
} else
|
||||
return *this;
|
||||
}
|
||||
|
||||
float Flow::rounded_rectangle_extrusion_spacing(float width, float height)
|
||||
{
|
||||
auto out = width - height * float(1. - 0.25 * PI);
|
||||
if (out <= 0.f)
|
||||
throw FlowErrorNegativeSpacing();
|
||||
return out;
|
||||
}
|
||||
|
||||
float Flow::rounded_rectangle_extrusion_width_from_spacing(float spacing, float height)
|
||||
{
|
||||
return float(spacing + height * (1. - 0.25 * PI));
|
||||
}
|
||||
|
||||
float Flow::bridge_extrusion_spacing(float dmr)
|
||||
{
|
||||
return dmr + BRIDGE_EXTRA_SPACING;
|
||||
}
|
||||
|
||||
// This method returns extrusion volume per head move unit.
|
||||
double Flow::mm3_per_mm() const
|
||||
{
|
||||
float res = m_bridge ?
|
||||
// Area of a circle with dmr of this->width.
|
||||
float((m_width * m_width) * 0.25 * PI) :
|
||||
// Rectangle with semicircles at the ends. ~ h (w - 0.215 h)
|
||||
float(m_height * (m_width - m_height * (1. - 0.25 * PI)));
|
||||
//assert(res > 0.);
|
||||
if (res <= 0.)
|
||||
throw FlowErrorNegativeFlow();
|
||||
return res;
|
||||
}
|
||||
|
||||
Flow support_material_flow(const PrintObject *object, float layer_height)
|
||||
{
|
||||
return Flow::new_from_config_width(
|
||||
frSupportMaterial,
|
||||
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
|
||||
(object->config().support_line_width.value > 0) ? object->config().support_line_width : object->config().line_width,
|
||||
// if object->config().support_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
|
||||
float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament-1)),
|
||||
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
|
||||
}
|
||||
//BBS
|
||||
Flow support_transition_flow(const PrintObject* object)
|
||||
{
|
||||
//BBS: support transition of tree support is bridge flow
|
||||
float dmr = float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament - 1));
|
||||
return Flow::bridging_flow(dmr, dmr);
|
||||
}
|
||||
|
||||
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
|
||||
{
|
||||
const PrintConfig &print_config = object->print()->config();
|
||||
const auto &width = (print_config.initial_layer_line_width.value > 0) ? print_config.initial_layer_line_width : object->config().support_line_width;
|
||||
return Flow::new_from_config_width(
|
||||
frSupportMaterial,
|
||||
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
|
||||
(width.value > 0) ? width : object->config().line_width,
|
||||
float(print_config.nozzle_diameter.get_at(object->config().support_filament-1)),
|
||||
(layer_height > 0.f) ? layer_height : float(print_config.initial_layer_print_height.value));
|
||||
}
|
||||
|
||||
Flow support_material_interface_flow(const PrintObject *object, float layer_height)
|
||||
{
|
||||
return Flow::new_from_config_width(
|
||||
frSupportMaterialInterface,
|
||||
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
|
||||
(object->config().support_line_width > 0) ? object->config().support_line_width : object->config().line_width,
|
||||
// if object->config().support_interface_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
|
||||
float(object->print()->config().nozzle_diameter.get_at(object->config().support_interface_filament-1)),
|
||||
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
|
||||
}
|
||||
|
||||
}
|
||||
141
src/libslic3r/Flow.hpp
Normal file
141
src/libslic3r/Flow.hpp
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
#ifndef slic3r_Flow_hpp_
|
||||
#define slic3r_Flow_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Config.hpp"
|
||||
#include "Exception.hpp"
|
||||
#include "ExtrusionEntity.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
|
||||
// Extra spacing of bridge threads, in mm.
|
||||
#define BRIDGE_EXTRA_SPACING 0.05
|
||||
|
||||
enum FlowRole {
|
||||
frExternalPerimeter,
|
||||
frPerimeter,
|
||||
frInfill,
|
||||
frSolidInfill,
|
||||
frTopSolidInfill,
|
||||
frSupportMaterial,
|
||||
frSupportMaterialInterface,
|
||||
frSupportTransition, // BBS
|
||||
};
|
||||
|
||||
class FlowError : public Slic3r::InvalidArgument
|
||||
{
|
||||
public:
|
||||
FlowError(const std::string& what_arg) : Slic3r::InvalidArgument(what_arg) {}
|
||||
FlowError(const char* what_arg) : Slic3r::InvalidArgument(what_arg) {}
|
||||
};
|
||||
|
||||
class FlowErrorNegativeSpacing : public FlowError
|
||||
{
|
||||
public:
|
||||
FlowErrorNegativeSpacing();
|
||||
};
|
||||
|
||||
class FlowErrorNegativeFlow : public FlowError
|
||||
{
|
||||
public:
|
||||
FlowErrorNegativeFlow();
|
||||
};
|
||||
|
||||
class FlowErrorMissingVariable : public FlowError
|
||||
{
|
||||
public:
|
||||
FlowErrorMissingVariable(const std::string& what_arg) : FlowError(what_arg) {}
|
||||
};
|
||||
|
||||
class Flow
|
||||
{
|
||||
public:
|
||||
Flow() = default;
|
||||
Flow(float width, float height, float nozzle_diameter) :
|
||||
Flow(width, height, rounded_rectangle_extrusion_spacing(width, height), nozzle_diameter, false) {}
|
||||
|
||||
// Non bridging flow: Maximum width of an extrusion with semicircles at the ends.
|
||||
// Bridging flow: Bridge thread diameter.
|
||||
float width() const { return m_width; }
|
||||
coord_t scaled_width() const { return coord_t(scale_(m_width)); }
|
||||
// Non bridging flow: Layer height.
|
||||
// Bridging flow: Bridge thread diameter = layer height.
|
||||
float height() const { return m_height; }
|
||||
// Spacing between the extrusion centerlines.
|
||||
float spacing() const { return m_spacing; }
|
||||
coord_t scaled_spacing() const { return coord_t(scale_(m_spacing)); }
|
||||
// Nozzle diameter.
|
||||
float nozzle_diameter() const { return m_nozzle_diameter; }
|
||||
// Is it a bridge?
|
||||
bool bridge() const { return m_bridge; }
|
||||
// Cross section area of the extrusion.
|
||||
double mm3_per_mm() const;
|
||||
|
||||
// Elephant foot compensation spacing to be used to detect narrow parts, where the elephant foot compensation cannot be applied.
|
||||
// To be used on frExternalPerimeter only.
|
||||
// Enable some perimeter squish (see INSET_OVERLAP_TOLERANCE).
|
||||
// Here an overlap of 0.2x external perimeter spacing is allowed for by the elephant foot compensation.
|
||||
coord_t scaled_elephant_foot_spacing() const { return coord_t(0.5f * float(this->scaled_width() + 0.6f * this->scaled_spacing())); }
|
||||
|
||||
bool operator==(const Flow &rhs) const { return m_width == rhs.m_width && m_height == rhs.m_height && m_nozzle_diameter == rhs.m_nozzle_diameter && m_bridge == rhs.m_bridge; }
|
||||
|
||||
Flow with_width (float width) const {
|
||||
assert(! m_bridge);
|
||||
return Flow(width, m_height, rounded_rectangle_extrusion_spacing(width, m_height), m_nozzle_diameter, m_bridge);
|
||||
}
|
||||
Flow with_height(float height) const {
|
||||
assert(! m_bridge);
|
||||
return Flow(m_width, height, rounded_rectangle_extrusion_spacing(m_width, height), m_nozzle_diameter, m_bridge);
|
||||
}
|
||||
// Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions.
|
||||
Flow with_spacing(float spacing) const;
|
||||
// Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing.
|
||||
Flow with_cross_section(float area) const;
|
||||
Flow with_flow_ratio(double ratio) const { return this->with_cross_section(this->mm3_per_mm() * ratio); }
|
||||
|
||||
static Flow bridging_flow(float dmr, float nozzle_diameter) { return Flow { dmr, dmr, bridge_extrusion_spacing(dmr), nozzle_diameter, true }; }
|
||||
|
||||
static Flow new_from_config_width(FlowRole role, const ConfigOptionFloat &width, float nozzle_diameter, float height);
|
||||
|
||||
// Spacing of extrusions with rounded extrusion model.
|
||||
static float rounded_rectangle_extrusion_spacing(float width, float height);
|
||||
// Width of extrusions with rounded extrusion model.
|
||||
static float rounded_rectangle_extrusion_width_from_spacing(float spacing, float height);
|
||||
// Spacing of round thread extrusions.
|
||||
static float bridge_extrusion_spacing(float dmr);
|
||||
|
||||
// Sane extrusion width defautl based on nozzle diameter.
|
||||
// The defaults were derived from manual Prusa MK3 profiles.
|
||||
static float auto_extrusion_width(FlowRole role, float nozzle_diameter);
|
||||
|
||||
// Extrusion width from full config, taking into account the defaults (when set to zero) and ratios (percentages).
|
||||
// Precise value depends on layer index (1st layer vs. other layers vs. variable layer height),
|
||||
// on active extruder etc. Therefore the value calculated by this function shall be used as a hint only.
|
||||
static double extrusion_width(const std::string &opt_key, const ConfigOptionFloatOrPercent *opt, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0);
|
||||
static double extrusion_width(const std::string &opt_key, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0);
|
||||
|
||||
private:
|
||||
Flow(float width, float height, float spacing, float nozzle_diameter, bool bridge) :
|
||||
m_width(width), m_height(height), m_spacing(spacing), m_nozzle_diameter(nozzle_diameter), m_bridge(bridge)
|
||||
{
|
||||
// Gap fill violates this condition.
|
||||
//assert(width >= height);
|
||||
}
|
||||
|
||||
float m_width { 0 };
|
||||
float m_height { 0 };
|
||||
float m_spacing { 0 };
|
||||
float m_nozzle_diameter { 0 };
|
||||
bool m_bridge { false };
|
||||
};
|
||||
|
||||
extern Flow support_material_flow(const PrintObject* object, float layer_height = 0.f);
|
||||
extern Flow support_transition_flow(const PrintObject *object); //BBS
|
||||
extern Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height = 0.f);
|
||||
extern Flow support_material_interface_flow(const PrintObject *object, float layer_height = 0.f);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
3142
src/libslic3r/Format/3mf.cpp
Normal file
3142
src/libslic3r/Format/3mf.cpp
Normal file
File diff suppressed because it is too large
Load diff
41
src/libslic3r/Format/3mf.hpp
Normal file
41
src/libslic3r/Format/3mf.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef slic3r_Format_3mf_hpp_
|
||||
#define slic3r_Format_3mf_hpp_
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
/* The format for saving the SLA points was changing in the past. This enum holds the latest version that is being currently used.
|
||||
* Examples of the Slic3r_PE_sla_support_points.txt for historically used versions:
|
||||
|
||||
* version 0 : object_id=1|-12.055421 -2.658771 10.000000
|
||||
object_id=2|-14.051745 -3.570338 5.000000
|
||||
// no header and x,y,z positions of the points)
|
||||
|
||||
* version 1 : ThreeMF_support_points_version=1
|
||||
object_id=1|-12.055421 -2.658771 10.000000 0.4 0.0
|
||||
object_id=2|-14.051745 -3.570338 5.000000 0.6 1.0
|
||||
// introduced header with version number; x,y,z,head_size,is_new_island)
|
||||
*/
|
||||
|
||||
enum {
|
||||
support_points_format_version = 1
|
||||
};
|
||||
|
||||
enum {
|
||||
drain_holes_format_version = 1
|
||||
};
|
||||
|
||||
class Model;
|
||||
struct ConfigSubstitutionContext;
|
||||
class DynamicPrintConfig;
|
||||
struct ThumbnailData;
|
||||
|
||||
// Load the content of a 3mf file into the given model and preset bundle.
|
||||
extern bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version);
|
||||
|
||||
// Save the given model and the config data contained in the given Print into a 3mf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr, bool zip64 = true);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Format_3mf_hpp_ */
|
||||
1394
src/libslic3r/Format/AMF.cpp
Normal file
1394
src/libslic3r/Format/AMF.cpp
Normal file
File diff suppressed because it is too large
Load diff
19
src/libslic3r/Format/AMF.hpp
Normal file
19
src/libslic3r/Format/AMF.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef slic3r_Format_AMF_hpp_
|
||||
#define slic3r_Format_AMF_hpp_
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
class DynamicPrintConfig;
|
||||
|
||||
// Load the content of an amf file into the given model and configuration.
|
||||
extern bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool* use_inches);
|
||||
|
||||
//BBS: remove amf export
|
||||
// Save the given model and the config data into an amf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
//extern bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Format_AMF_hpp_ */
|
||||
140
src/libslic3r/Format/OBJ.cpp
Normal file
140
src/libslic3r/Format/OBJ.cpp
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
#include "../TriangleMesh.hpp"
|
||||
|
||||
#include "OBJ.hpp"
|
||||
#include "objparser.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DIR_SEPARATOR '\\'
|
||||
#else
|
||||
#define DIR_SEPARATOR '/'
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool load_obj(const char *path, TriangleMesh *meshptr)
|
||||
{
|
||||
if (meshptr == nullptr)
|
||||
return false;
|
||||
|
||||
// Parse the OBJ file.
|
||||
ObjParser::ObjData data;
|
||||
if (! ObjParser::objparse(path, data)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Count the faces and verify, that all faces are triangular.
|
||||
size_t num_faces = 0;
|
||||
size_t num_quads = 0;
|
||||
for (size_t i = 0; i < data.vertices.size(); ++ i) {
|
||||
// Find the end of face.
|
||||
size_t j = i;
|
||||
for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ;
|
||||
if (size_t num_face_vertices = j - i; num_face_vertices > 0) {
|
||||
if (num_face_vertices > 4) {
|
||||
// Non-triangular and non-quad faces are not supported as of now.
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains polygons with more than 4 vertices.";
|
||||
return false;
|
||||
} else if (num_face_vertices < 3) {
|
||||
// Non-triangular and non-quad faces are not supported as of now.
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains polygons with less than 2 vertices.";
|
||||
return false;
|
||||
}
|
||||
if (num_face_vertices == 4)
|
||||
++ num_quads;
|
||||
++ num_faces;
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert ObjData into indexed triangle set.
|
||||
indexed_triangle_set its;
|
||||
size_t num_vertices = data.coordinates.size() / 4;
|
||||
its.vertices.reserve(num_vertices);
|
||||
its.indices.reserve(num_faces + num_quads);
|
||||
for (size_t i = 0; i < num_vertices; ++ i) {
|
||||
size_t j = i << 2;
|
||||
its.vertices.emplace_back(data.coordinates[j], data.coordinates[j + 1], data.coordinates[j + 2]);
|
||||
}
|
||||
int indices[4];
|
||||
for (size_t i = 0; i < data.vertices.size();)
|
||||
if (data.vertices[i].coordIdx == -1)
|
||||
++ i;
|
||||
else {
|
||||
int cnt = 0;
|
||||
while (i < data.vertices.size())
|
||||
if (const ObjParser::ObjVertex &vertex = data.vertices[i ++]; vertex.coordIdx == -1) {
|
||||
break;
|
||||
} else {
|
||||
assert(cnt < 4);
|
||||
if (vertex.coordIdx < 0 || vertex.coordIdx >= int(its.vertices.size())) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains invalid vertex index.";
|
||||
return false;
|
||||
}
|
||||
indices[cnt ++] = vertex.coordIdx;
|
||||
}
|
||||
if (cnt) {
|
||||
assert(cnt == 3 || cnt == 4);
|
||||
// Insert one or two faces (triangulate a quad).
|
||||
its.indices.emplace_back(indices[0], indices[1], indices[2]);
|
||||
if (cnt == 4)
|
||||
its.indices.emplace_back(indices[0], indices[2], indices[3]);
|
||||
}
|
||||
}
|
||||
|
||||
*meshptr = TriangleMesh(std::move(its));
|
||||
if (meshptr->empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: This OBJ file couldn't be read because it's empty. " << path;
|
||||
return false;
|
||||
}
|
||||
if (meshptr->volume() < 0)
|
||||
meshptr->flip_triangles();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_obj(const char *path, Model *model, const char *object_name_in)
|
||||
{
|
||||
TriangleMesh mesh;
|
||||
|
||||
bool ret = load_obj(path, &mesh);
|
||||
|
||||
if (ret) {
|
||||
std::string object_name;
|
||||
if (object_name_in == nullptr) {
|
||||
const char *last_slash = strrchr(path, DIR_SEPARATOR);
|
||||
object_name.assign((last_slash == nullptr) ? path : last_slash + 1);
|
||||
} else
|
||||
object_name.assign(object_name_in);
|
||||
|
||||
model->add_object(object_name.c_str(), path, std::move(mesh));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool store_obj(const char *path, TriangleMesh *mesh)
|
||||
{
|
||||
//FIXME returning false even if write failed.
|
||||
mesh->WriteOBJFile(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool store_obj(const char *path, ModelObject *model_object)
|
||||
{
|
||||
TriangleMesh mesh = model_object->mesh();
|
||||
return store_obj(path, &mesh);
|
||||
}
|
||||
|
||||
bool store_obj(const char *path, Model *model)
|
||||
{
|
||||
TriangleMesh mesh = model->mesh();
|
||||
return store_obj(path, &mesh);
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
20
src/libslic3r/Format/OBJ.hpp
Normal file
20
src/libslic3r/Format/OBJ.hpp
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#ifndef slic3r_Format_OBJ_hpp_
|
||||
#define slic3r_Format_OBJ_hpp_
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
class Model;
|
||||
class ModelObject;
|
||||
|
||||
// Load an OBJ file into a provided model.
|
||||
extern bool load_obj(const char *path, TriangleMesh *mesh);
|
||||
extern bool load_obj(const char *path, Model *model, const char *object_name = nullptr);
|
||||
|
||||
extern bool store_obj(const char *path, TriangleMesh *mesh);
|
||||
extern bool store_obj(const char *path, ModelObject *model);
|
||||
extern bool store_obj(const char *path, Model *model);
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Format_OBJ_hpp_ */
|
||||
518
src/libslic3r/Format/SL1.cpp
Normal file
518
src/libslic3r/Format/SL1.cpp
Normal file
|
|
@ -0,0 +1,518 @@
|
|||
#include "SL1.hpp"
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#include "libslic3r/Time.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "libslic3r/Zipper.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "libslic3r/Exception.hpp"
|
||||
#include "libslic3r/SlicesToTriangleMesh.hpp"
|
||||
#include "libslic3r/MarchingSquares.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/SLA/RasterBase.hpp"
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
#include "libslic3r/PNGReadWrite.hpp"
|
||||
#include "libslic3r/LocalesUtils.hpp"
|
||||
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace marchsq {
|
||||
|
||||
template<> struct _RasterTraits<Slic3r::png::ImageGreyscale> {
|
||||
using Rst = Slic3r::png::ImageGreyscale;
|
||||
|
||||
// The type of pixel cell in the raster
|
||||
using ValueType = uint8_t;
|
||||
|
||||
// Value at a given position
|
||||
static uint8_t get(const Rst &rst, size_t row, size_t col)
|
||||
{
|
||||
return rst.get(row, col);
|
||||
}
|
||||
|
||||
// Number of rows and cols of the raster
|
||||
static size_t rows(const Rst &rst) { return rst.rows; }
|
||||
static size_t cols(const Rst &rst) { return rst.cols; }
|
||||
};
|
||||
|
||||
} // namespace marchsq
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
struct PNGBuffer { std::vector<uint8_t> buf; std::string fname; };
|
||||
struct ArchiveData {
|
||||
boost::property_tree::ptree profile, config;
|
||||
std::vector<PNGBuffer> images;
|
||||
};
|
||||
|
||||
static const constexpr char *CONFIG_FNAME = "config.ini";
|
||||
static const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
|
||||
|
||||
boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
|
||||
MZ_Archive & zip)
|
||||
{
|
||||
std::string buf(size_t(entry.m_uncomp_size), '\0');
|
||||
|
||||
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
|
||||
buf.data(), buf.size(), 0))
|
||||
throw Slic3r::FileIOError(zip.get_errorstr());
|
||||
|
||||
boost::property_tree::ptree tree;
|
||||
std::stringstream ss(buf);
|
||||
boost::property_tree::read_ini(ss, tree);
|
||||
return tree;
|
||||
}
|
||||
|
||||
PNGBuffer read_png(const mz_zip_archive_file_stat &entry,
|
||||
MZ_Archive & zip,
|
||||
const std::string & name)
|
||||
{
|
||||
std::vector<uint8_t> buf(entry.m_uncomp_size);
|
||||
|
||||
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
|
||||
buf.data(), buf.size(), 0))
|
||||
throw Slic3r::FileIOError(zip.get_errorstr());
|
||||
|
||||
return {std::move(buf), (name.empty() ? entry.m_filename : name)};
|
||||
}
|
||||
|
||||
ArchiveData extract_sla_archive(const std::string &zipfname,
|
||||
const std::string &exclude)
|
||||
{
|
||||
ArchiveData arch;
|
||||
|
||||
// Little RAII
|
||||
struct Arch: public MZ_Archive {
|
||||
Arch(const std::string &fname) {
|
||||
if (!open_zip_reader(&arch, fname))
|
||||
throw Slic3r::FileIOError(get_errorstr());
|
||||
}
|
||||
|
||||
~Arch() { close_zip_reader(&arch); }
|
||||
} zip (zipfname);
|
||||
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch);
|
||||
|
||||
for (mz_uint i = 0; i < num_entries; ++i)
|
||||
{
|
||||
mz_zip_archive_file_stat entry;
|
||||
|
||||
if (mz_zip_reader_file_stat(&zip.arch, i, &entry))
|
||||
{
|
||||
std::string name = entry.m_filename;
|
||||
boost::algorithm::to_lower(name);
|
||||
|
||||
if (boost::algorithm::contains(name, exclude)) continue;
|
||||
|
||||
if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip);
|
||||
if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip);
|
||||
|
||||
if (boost::filesystem::path(name).extension().string() == ".png") {
|
||||
auto it = std::lower_bound(
|
||||
arch.images.begin(), arch.images.end(), PNGBuffer{{}, name},
|
||||
[](const PNGBuffer &r1, const PNGBuffer &r2) {
|
||||
return std::less<std::string>()(r1.fname, r2.fname);
|
||||
});
|
||||
|
||||
arch.images.insert(it, read_png(entry, zip, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return arch;
|
||||
}
|
||||
|
||||
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
|
||||
double px_w, double px_h)
|
||||
{
|
||||
auto polys = reserve_vector<ExPolygon>(rings.size());
|
||||
|
||||
for (const marchsq::Ring &ring : rings) {
|
||||
Polygon poly; Points &pts = poly.points;
|
||||
pts.reserve(ring.size());
|
||||
|
||||
for (const marchsq::Coord &crd : ring)
|
||||
pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h));
|
||||
|
||||
polys.emplace_back(poly);
|
||||
}
|
||||
|
||||
// TODO: Is a union necessary?
|
||||
return union_ex(polys);
|
||||
}
|
||||
|
||||
template<class Fn> void foreach_vertex(ExPolygon &poly, Fn &&fn)
|
||||
{
|
||||
for (auto &p : poly.contour.points) fn(p);
|
||||
for (auto &h : poly.holes)
|
||||
for (auto &p : h.points) fn(p);
|
||||
}
|
||||
|
||||
void invert_raster_trafo(ExPolygons & expolys,
|
||||
const sla::RasterBase::Trafo &trafo,
|
||||
coord_t width,
|
||||
coord_t height)
|
||||
{
|
||||
if (trafo.flipXY) std::swap(height, width);
|
||||
|
||||
for (auto &expoly : expolys) {
|
||||
if (trafo.mirror_y)
|
||||
foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); });
|
||||
|
||||
if (trafo.mirror_x)
|
||||
foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); });
|
||||
|
||||
expoly.translate(-trafo.center_x, -trafo.center_y);
|
||||
|
||||
if (trafo.flipXY)
|
||||
foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); });
|
||||
|
||||
if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) {
|
||||
expoly.contour.reverse();
|
||||
for (auto &h : expoly.holes) h.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RasterParams {
|
||||
sla::RasterBase::Trafo trafo; // Raster transformations
|
||||
coord_t width, height; // scaled raster dimensions (not resolution)
|
||||
double px_h, px_w; // pixel dimesions
|
||||
marchsq::Coord win; // marching squares window size
|
||||
};
|
||||
|
||||
RasterParams get_raster_params(const DynamicPrintConfig &cfg)
|
||||
{
|
||||
auto *opt_disp_cols = cfg.option<ConfigOptionInt>("display_pixels_x");
|
||||
auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
|
||||
auto *opt_disp_w = cfg.option<ConfigOptionFloat>("display_width");
|
||||
auto *opt_disp_h = cfg.option<ConfigOptionFloat>("display_height");
|
||||
auto *opt_mirror_x = cfg.option<ConfigOptionBool>("display_mirror_x");
|
||||
auto *opt_mirror_y = cfg.option<ConfigOptionBool>("display_mirror_y");
|
||||
auto *opt_orient = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("display_orientation");
|
||||
|
||||
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
|
||||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
|
||||
throw MissingProfileError("Invalid SL1 / SL1S file");
|
||||
|
||||
RasterParams rstp;
|
||||
|
||||
rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1);
|
||||
rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1);
|
||||
|
||||
rstp.trafo = sla::RasterBase::Trafo{opt_orient->value == sladoLandscape ?
|
||||
sla::RasterBase::roLandscape :
|
||||
sla::RasterBase::roPortrait,
|
||||
{opt_mirror_x->value, opt_mirror_y->value}};
|
||||
|
||||
rstp.height = scaled(opt_disp_h->value);
|
||||
rstp.width = scaled(opt_disp_w->value);
|
||||
|
||||
return rstp;
|
||||
}
|
||||
|
||||
struct SliceParams { double layerh = 0., initial_layerh = 0.; };
|
||||
|
||||
SliceParams get_slice_params(const DynamicPrintConfig &cfg)
|
||||
{
|
||||
auto *opt_layerh = cfg.option<ConfigOptionFloat>("layer_height");
|
||||
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
|
||||
|
||||
if (!opt_layerh || !opt_init_layerh)
|
||||
throw MissingProfileError("Invalid SL1 / SL1S file");
|
||||
|
||||
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> extract_slices_from_sla_archive(
|
||||
ArchiveData & arch,
|
||||
const RasterParams & rstp,
|
||||
std::function<bool(int)> progr)
|
||||
{
|
||||
auto jobdir = arch.config.get<std::string>("jobDir");
|
||||
for (auto &c : jobdir) c = std::tolower(c);
|
||||
|
||||
std::vector<ExPolygons> slices(arch.images.size());
|
||||
|
||||
struct Status
|
||||
{
|
||||
double incr, val, prev;
|
||||
bool stop = false;
|
||||
tbb::spin_mutex mutex = {};
|
||||
} st {100. / slices.size(), 0., 0.};
|
||||
|
||||
tbb::parallel_for(size_t(0), arch.images.size(),
|
||||
[&arch, &slices, &st, &rstp, progr](size_t i) {
|
||||
// Status indication guarded with the spinlock
|
||||
{
|
||||
std::lock_guard<tbb::spin_mutex> lck(st.mutex);
|
||||
if (st.stop) return;
|
||||
|
||||
st.val += st.incr;
|
||||
double curr = std::round(st.val);
|
||||
if (curr > st.prev) {
|
||||
st.prev = curr;
|
||||
st.stop = !progr(int(curr));
|
||||
}
|
||||
}
|
||||
|
||||
png::ImageGreyscale img;
|
||||
png::ReadBuf rb{arch.images[i].buf.data(), arch.images[i].buf.size()};
|
||||
if (!png::decode_png(rb, img)) return;
|
||||
|
||||
uint8_t isoval = 128;
|
||||
auto rings = marchsq::execute(img, isoval, rstp.win);
|
||||
ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h);
|
||||
|
||||
// Invert the raster transformations indicated in the profile metadata
|
||||
invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
|
||||
|
||||
slices[i] = std::move(expolys);
|
||||
});
|
||||
|
||||
if (st.stop) slices = {};
|
||||
|
||||
return slices;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
|
||||
{
|
||||
ArchiveData arch = extract_sla_archive(zipfname, "png");
|
||||
return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
}
|
||||
|
||||
// If the profile is missing from the archive (older PS versions did not have
|
||||
// it), profile_out's initial value will be used as fallback. profile_out will be empty on
|
||||
// function return if the archive did not contain any profile.
|
||||
ConfigSubstitutions import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
indexed_triangle_set & out,
|
||||
DynamicPrintConfig & profile_out,
|
||||
std::function<bool(int)> progr)
|
||||
{
|
||||
// Ensure minimum window size for marching squares
|
||||
windowsize.x() = std::max(2, windowsize.x());
|
||||
windowsize.y() = std::max(2, windowsize.y());
|
||||
|
||||
std::string exclude_entries{"thumbnail"};
|
||||
ArchiveData arch = extract_sla_archive(zipfname, exclude_entries);
|
||||
DynamicPrintConfig profile_in, profile_use;
|
||||
ConfigSubstitutions config_substitutions =
|
||||
profile_in.load(arch.profile,
|
||||
ForwardCompatibilitySubstitutionRule::Enable);
|
||||
|
||||
if (profile_in.empty()) { // missing profile... do guess work
|
||||
// try to recover the layer height from the config.ini which was
|
||||
// present in all versions of sl1 files.
|
||||
if (auto lh_opt = arch.config.find("layerHeight");
|
||||
lh_opt != arch.config.not_found())
|
||||
{
|
||||
auto lh_str = lh_opt->second.data();
|
||||
|
||||
size_t pos;
|
||||
double lh = string_to_double_decimal_point(lh_str, &pos);
|
||||
if (pos) { // TODO: verify that pos is 0 when parsing fails
|
||||
profile_out.set("layer_height", lh);
|
||||
profile_out.set("initial_layer_height", lh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the archive contains an empty profile, use the one that was passed as output argument
|
||||
// then replace it with the readed profile to report that it was empty.
|
||||
profile_use = profile_in.empty() ? profile_out : profile_in;
|
||||
profile_out = profile_in;
|
||||
|
||||
RasterParams rstp = get_raster_params(profile_use);
|
||||
rstp.win = {windowsize.y(), windowsize.x()};
|
||||
|
||||
SliceParams slicp = get_slice_params(profile_use);
|
||||
|
||||
std::vector<ExPolygons> slices =
|
||||
extract_slices_from_sla_archive(arch, rstp, progr);
|
||||
|
||||
if (!slices.empty())
|
||||
out = slices_to_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
|
||||
|
||||
return config_substitutions;
|
||||
}
|
||||
|
||||
using ConfMap = std::map<std::string, std::string>;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string to_ini(const ConfMap &m)
|
||||
{
|
||||
std::string ret;
|
||||
for (auto ¶m : m) ret += param.first + " = " + param.second + "\n";
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key)
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
if (cfg.has(key)) {
|
||||
auto opt = cfg.option(key);
|
||||
if (opt) ret = opt->serialize();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void fill_iniconf(ConfMap &m, const SLAPrint &print)
|
||||
{
|
||||
CNumericLocalesSetter locales_setter; // for to_string
|
||||
auto &cfg = print.full_print_config();
|
||||
m["layerHeight"] = get_cfg_value(cfg, "layer_height");
|
||||
m["expTime"] = get_cfg_value(cfg, "exposure_time");
|
||||
m["expTimeFirst"] = get_cfg_value(cfg, "initial_exposure_time");
|
||||
m["expUserProfile"] = get_cfg_value(cfg, "material_print_speed") == "slow" ? "1" : "0";
|
||||
m["materialName"] = get_cfg_value(cfg, "sla_material_settings_id");
|
||||
m["printerModel"] = get_cfg_value(cfg, "printer_model");
|
||||
m["printerVariant"] = get_cfg_value(cfg, "printer_variant");
|
||||
m["printerProfile"] = get_cfg_value(cfg, "printer_settings_id");
|
||||
m["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id");
|
||||
m["fileCreationTimestamp"] = Utils::utc_timestamp();
|
||||
m["prusaSlicerVersion"] = SLIC3R_BUILD_ID;
|
||||
|
||||
SLAPrintStatistics stats = print.print_statistics();
|
||||
// Set statistics values to the printer
|
||||
|
||||
double used_material = (stats.objects_used_material +
|
||||
stats.support_used_material) / 1000;
|
||||
|
||||
int num_fade = print.default_object_config().faded_layers.getInt();
|
||||
num_fade = num_fade >= 0 ? num_fade : 0;
|
||||
|
||||
m["usedMaterial"] = std::to_string(used_material);
|
||||
m["numFade"] = std::to_string(num_fade);
|
||||
m["numSlow"] = std::to_string(stats.slow_layers_count);
|
||||
m["numFast"] = std::to_string(stats.fast_layers_count);
|
||||
m["printTime"] = std::to_string(stats.estimated_print_time);
|
||||
|
||||
bool hollow_en = false;
|
||||
auto it = print.objects().begin();
|
||||
while (!hollow_en && it != print.objects().end())
|
||||
hollow_en = (*it++)->config().hollowing_enable;
|
||||
|
||||
m["hollow"] = hollow_en ? "1" : "0";
|
||||
|
||||
m["action"] = "print";
|
||||
}
|
||||
|
||||
void fill_slicerconf(ConfMap &m, const SLAPrint &print)
|
||||
{
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
// Sorted list of config keys, which shall not be stored into the ini.
|
||||
static constexpr auto banned_keys = {
|
||||
"compatible_printers"sv,
|
||||
"compatible_prints"sv
|
||||
};
|
||||
|
||||
assert(std::is_sorted(banned_keys.begin(), banned_keys.end()));
|
||||
auto is_banned = [](const std::string &key) {
|
||||
return std::binary_search(banned_keys.begin(), banned_keys.end(), key);
|
||||
};
|
||||
|
||||
auto &cfg = print.full_print_config();
|
||||
for (const std::string &key : cfg.keys())
|
||||
if (! is_banned(key) && ! cfg.option(key)->is_nil())
|
||||
m[key] = cfg.opt_serialize(key);
|
||||
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<sla::RasterBase> SL1Archive::create_raster() const
|
||||
{
|
||||
sla::RasterBase::Resolution res;
|
||||
sla::RasterBase::PixelDim pxdim;
|
||||
std::array<bool, 2> mirror;
|
||||
|
||||
double w = m_cfg.display_width.getFloat();
|
||||
double h = m_cfg.display_height.getFloat();
|
||||
auto pw = size_t(m_cfg.display_pixels_x.getInt());
|
||||
auto ph = size_t(m_cfg.display_pixels_y.getInt());
|
||||
|
||||
mirror[X] = m_cfg.display_mirror_x.getBool();
|
||||
mirror[Y] = m_cfg.display_mirror_y.getBool();
|
||||
|
||||
auto ro = m_cfg.display_orientation.getInt();
|
||||
sla::RasterBase::Orientation orientation =
|
||||
ro == sla::RasterBase::roPortrait ? sla::RasterBase::roPortrait :
|
||||
sla::RasterBase::roLandscape;
|
||||
|
||||
if (orientation == sla::RasterBase::roPortrait) {
|
||||
std::swap(w, h);
|
||||
std::swap(pw, ph);
|
||||
}
|
||||
|
||||
res = sla::RasterBase::Resolution{pw, ph};
|
||||
pxdim = sla::RasterBase::PixelDim{w / pw, h / ph};
|
||||
sla::RasterBase::Trafo tr{orientation, mirror};
|
||||
|
||||
double gamma = m_cfg.gamma_correction.getFloat();
|
||||
|
||||
return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr);
|
||||
}
|
||||
|
||||
sla::RasterEncoder SL1Archive::get_encoder() const
|
||||
{
|
||||
return sla::PNGRasterEncoder{};
|
||||
}
|
||||
|
||||
void SL1Archive::export_print(Zipper& zipper,
|
||||
const SLAPrint &print,
|
||||
const std::string &prjname)
|
||||
{
|
||||
std::string project =
|
||||
prjname.empty() ?
|
||||
boost::filesystem::path(zipper.get_filename()).stem().string() :
|
||||
prjname;
|
||||
|
||||
ConfMap iniconf, slicerconf;
|
||||
fill_iniconf(iniconf, print);
|
||||
|
||||
iniconf["jobDir"] = project;
|
||||
|
||||
fill_slicerconf(slicerconf, print);
|
||||
|
||||
try {
|
||||
zipper.add_entry("config.ini");
|
||||
zipper << to_ini(iniconf);
|
||||
zipper.add_entry("prusaslicer.ini");
|
||||
zipper << to_ini(slicerconf);
|
||||
|
||||
size_t i = 0;
|
||||
for (const sla::EncodedRaster &rst : m_layers) {
|
||||
|
||||
std::string imgname = project + string_printf("%.5d", i++) + "." +
|
||||
rst.extension();
|
||||
|
||||
zipper.add_entry(imgname.c_str(), rst.data(), rst.size());
|
||||
}
|
||||
} catch(std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||
// Rethrow the exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
64
src/libslic3r/Format/SL1.hpp
Normal file
64
src/libslic3r/Format/SL1.hpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#ifndef ARCHIVETRAITS_HPP
|
||||
#define ARCHIVETRAITS_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "libslic3r/Zipper.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SL1Archive: public SLAArchive {
|
||||
SLAPrinterConfig m_cfg;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<sla::RasterBase> create_raster() const override;
|
||||
sla::RasterEncoder get_encoder() const override;
|
||||
|
||||
public:
|
||||
|
||||
SL1Archive() = default;
|
||||
explicit SL1Archive(const SLAPrinterConfig &cfg): m_cfg(cfg) {}
|
||||
explicit SL1Archive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {}
|
||||
|
||||
void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = "");
|
||||
void export_print(const std::string &fname, const SLAPrint &print, const std::string &projectname = "")
|
||||
{
|
||||
Zipper zipper(fname);
|
||||
export_print(zipper, print, projectname);
|
||||
}
|
||||
|
||||
void apply(const SLAPrinterConfig &cfg) override
|
||||
{
|
||||
auto diff = m_cfg.diff(cfg);
|
||||
if (!diff.empty()) {
|
||||
m_cfg.apply_only(cfg, diff);
|
||||
m_layers = {};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
|
||||
|
||||
ConfigSubstitutions import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
indexed_triangle_set & out,
|
||||
DynamicPrintConfig & profile,
|
||||
std::function<bool(int)> progr = [](int) { return true; });
|
||||
|
||||
inline ConfigSubstitutions import_sla_archive(
|
||||
const std::string & zipfname,
|
||||
Vec2i windowsize,
|
||||
indexed_triangle_set & out,
|
||||
std::function<bool(int)> progr = [](int) { return true; })
|
||||
{
|
||||
DynamicPrintConfig profile;
|
||||
return import_sla_archive(zipfname, windowsize, out, profile, progr);
|
||||
}
|
||||
|
||||
class MissingProfileError : public RuntimeError { using RuntimeError::RuntimeError; };
|
||||
|
||||
} // namespace Slic3r::sla
|
||||
|
||||
#endif // ARCHIVETRAITS_HPP
|
||||
378
src/libslic3r/Format/STEP.cpp
Normal file
378
src/libslic3r/Format/STEP.cpp
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
#include "../TriangleMesh.hpp"
|
||||
|
||||
#include "STEP.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DIR_SEPARATOR '\\'
|
||||
#else
|
||||
#define DIR_SEPARATOR '/'
|
||||
#endif
|
||||
|
||||
#include "STEPCAFControl_Reader.hxx"
|
||||
#include "BRepMesh_IncrementalMesh.hxx"
|
||||
#include "Interface_Static.hxx"
|
||||
#include "XCAFDoc_DocumentTool.hxx"
|
||||
#include "XCAFDoc_ShapeTool.hxx"
|
||||
#include "XCAFApp_Application.hxx"
|
||||
#include "TopoDS_Solid.hxx"
|
||||
#include "TopoDS_Compound.hxx"
|
||||
#include "TopoDS_Builder.hxx"
|
||||
#include "TopoDS.hxx"
|
||||
#include "TDataStd_Name.hxx"
|
||||
#include "BRepBuilderAPI_Transform.hxx"
|
||||
#include "TopExp_Explorer.hxx"
|
||||
#include "TopExp_Explorer.hxx"
|
||||
#include "BRep_Tool.hxx"
|
||||
|
||||
const double STEP_TRANS_CHORD_ERROR = 0.005;
|
||||
const double STEP_TRANS_ANGLE_RES = 1;
|
||||
|
||||
const int LOAD_STEP_STAGE_READ_FILE = 0;
|
||||
const int LOAD_STEP_STAGE_GET_SOLID = 1;
|
||||
const int LOAD_STEP_STAGE_GET_MESH = 2;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool StepPreProcessor::preprocess(const char* path, std::string &output_path)
|
||||
{
|
||||
boost::nowide::ifstream infile(path);
|
||||
if (!infile.good()) {
|
||||
throw Slic3r::RuntimeError(std::string("Load step file failed.\nCannot open file for reading.\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
boost::filesystem::path temp_path(temporary_dir());
|
||||
std::string temp_step_path = temp_path.string() + "/temp.step";
|
||||
boost::nowide::remove(temp_step_path.c_str());
|
||||
boost::nowide::ofstream temp_file(temp_step_path, std::ios::app);
|
||||
std::string temp_line;
|
||||
while (std::getline(infile, temp_line)) {
|
||||
if (m_encode_type == EncodedType::UTF8) {
|
||||
//BBS: continue to judge whether is other type
|
||||
if (isUtf8(temp_line)) {
|
||||
//BBS: do nothing, but must be checked before checking whether is GBK
|
||||
}
|
||||
//BBS: not utf8, then maybe GBK
|
||||
else if (isGBK(temp_line)) {
|
||||
m_encode_type = EncodedType::GBK;
|
||||
}
|
||||
//BBS: not UTF8 and not GBK, then maybe some kind of special encoded type which we can't handle
|
||||
// Load the step as UTF and user will see garbage characters in slicer but we have no solution at the moment
|
||||
else {
|
||||
m_encode_type = EncodedType::OTHER;
|
||||
}
|
||||
}
|
||||
if (m_encode_type == EncodedType::GBK)
|
||||
//BBS: transform to UTF8 format if is GBK
|
||||
//todo: use gbkToUtf8 function to replace
|
||||
temp_file << decode_path(temp_line.c_str()) << std::endl;
|
||||
else
|
||||
temp_file << temp_line.c_str() << std::endl;
|
||||
}
|
||||
temp_file.close();
|
||||
infile.close();
|
||||
if (m_encode_type == EncodedType::GBK) {
|
||||
output_path = temp_step_path;
|
||||
} else {
|
||||
boost::nowide::remove(temp_step_path.c_str());
|
||||
output_path = std::string(path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StepPreProcessor::isUtf8File(const char* path)
|
||||
{
|
||||
boost::nowide::ifstream infile(path);
|
||||
if (!infile.good()) {
|
||||
throw Slic3r::RuntimeError(std::string("Load step file failed.\nCannot open file for reading.\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string temp_line;
|
||||
while (std::getline(infile, temp_line)) {
|
||||
if (!isUtf8(temp_line)) {
|
||||
infile.close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
infile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StepPreProcessor::isUtf8(const std::string str)
|
||||
{
|
||||
size_t num = 0;
|
||||
int i = 0;
|
||||
while (i < str.length()) {
|
||||
if ((str[i] & 0x80) == 0x00) {
|
||||
i++;
|
||||
} else if ((num = preNum(str[i])) > 2) {
|
||||
i++;
|
||||
for (int j = 0; j < num - 1; j++) {
|
||||
if ((str[i] & 0xc0) != 0x80)
|
||||
return false;
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StepPreProcessor::isGBK(const std::string str) {
|
||||
size_t i = 0;
|
||||
while (i < str.length()) {
|
||||
if (str[i] <= 0x7f) {
|
||||
i++;
|
||||
continue;
|
||||
} else {
|
||||
if (str[i] >= 0x81 &&
|
||||
str[i] <= 0xfe &&
|
||||
str[i + 1] >= 0x40 &&
|
||||
str[i + 1] <= 0xfe &&
|
||||
str[i + 1] != 0xf7) {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int StepPreProcessor::preNum(const unsigned char byte) {
|
||||
unsigned char mask = 0x80;
|
||||
int num = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((byte & mask) == mask) {
|
||||
mask = mask >> 1;
|
||||
num++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
struct NamedSolid {
|
||||
NamedSolid(const TopoDS_Shape& s,
|
||||
const std::string& n) : solid{s}, name{n} {}
|
||||
const TopoDS_Shape solid;
|
||||
const std::string name;
|
||||
};
|
||||
|
||||
static void getNamedSolids(const TopLoc_Location& location, const std::string& prefix,
|
||||
unsigned int& id, const Handle(XCAFDoc_ShapeTool) shapeTool,
|
||||
const TDF_Label label, std::vector<NamedSolid>& namedSolids) {
|
||||
TDF_Label referredLabel{label};
|
||||
if (shapeTool->IsReference(label))
|
||||
shapeTool->GetReferredShape(label, referredLabel);
|
||||
|
||||
std::string name;
|
||||
Handle(TDataStd_Name) shapeName;
|
||||
if (referredLabel.FindAttribute(TDataStd_Name::GetID(), shapeName))
|
||||
name = TCollection_AsciiString(shapeName->Get()).ToCString();
|
||||
|
||||
if (name == "")
|
||||
name = std::to_string(id++);
|
||||
std::string fullName{name};
|
||||
|
||||
TopLoc_Location localLocation = location * shapeTool->GetLocation(label);
|
||||
TDF_LabelSequence components;
|
||||
if (shapeTool->GetComponents(referredLabel, components)) {
|
||||
for (Standard_Integer compIndex = 1; compIndex <= components.Length(); ++compIndex) {
|
||||
getNamedSolids(localLocation, fullName, id, shapeTool, components.Value(compIndex), namedSolids);
|
||||
}
|
||||
} else {
|
||||
TopoDS_Shape shape;
|
||||
shapeTool->GetShape(referredLabel, shape);
|
||||
TopAbs_ShapeEnum shape_type = shape.ShapeType();
|
||||
BRepBuilderAPI_Transform transform(shape, localLocation, Standard_True);
|
||||
switch (shape_type) {
|
||||
case TopAbs_COMPOUND:
|
||||
namedSolids.emplace_back(TopoDS::Compound(transform.Shape()), fullName);
|
||||
break;
|
||||
case TopAbs_COMPSOLID:
|
||||
namedSolids.emplace_back(TopoDS::CompSolid(transform.Shape()), fullName);
|
||||
break;
|
||||
case TopAbs_SOLID:
|
||||
namedSolids.emplace_back(TopoDS::Solid(transform.Shape()), fullName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool load_step(const char *path, Model *model, ImportStepProgressFn proFn, StepIsUtf8Fn isUtf8Fn)
|
||||
{
|
||||
bool cb_cancel = false;
|
||||
if (proFn) {
|
||||
proFn(LOAD_STEP_STAGE_READ_FILE, 0, 1, cb_cancel);
|
||||
if (cb_cancel)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StepPreProcessor::isUtf8File(path) && isUtf8Fn)
|
||||
isUtf8Fn(false);
|
||||
std::string file_after_preprocess = std::string(path);
|
||||
|
||||
std::vector<NamedSolid> namedSolids;
|
||||
Handle(TDocStd_Document) document;
|
||||
Handle(XCAFApp_Application) application = XCAFApp_Application::GetApplication();
|
||||
application->NewDocument(file_after_preprocess.c_str(), document);
|
||||
STEPCAFControl_Reader reader;
|
||||
reader.SetNameMode(true);
|
||||
//BBS: Todo, read file is slow which cause the progress_bar no update and gui no response
|
||||
IFSelect_ReturnStatus stat = reader.ReadFile(file_after_preprocess.c_str());
|
||||
if (stat != IFSelect_RetDone || !reader.Transfer(document)) {
|
||||
application->Close(document);
|
||||
throw std::logic_error{ std::string{"Could not read '"} + path + "'" };
|
||||
return false;
|
||||
}
|
||||
Handle(XCAFDoc_ShapeTool) shapeTool = XCAFDoc_DocumentTool::ShapeTool(document->Main());
|
||||
TDF_LabelSequence topLevelShapes;
|
||||
shapeTool->GetFreeShapes(topLevelShapes);
|
||||
|
||||
unsigned int id{1};
|
||||
Standard_Integer topShapeLength = topLevelShapes.Length() + 1;
|
||||
for (Standard_Integer iLabel = 1; iLabel < topShapeLength; ++iLabel) {
|
||||
if (proFn) {
|
||||
proFn(LOAD_STEP_STAGE_GET_SOLID, iLabel, topShapeLength, cb_cancel);
|
||||
if (cb_cancel) {
|
||||
shapeTool.reset(nullptr);
|
||||
application->Close(document);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
getNamedSolids(TopLoc_Location{}, "", id, shapeTool, topLevelShapes.Value(iLabel), namedSolids);
|
||||
}
|
||||
|
||||
ModelObject* new_object = model->add_object();
|
||||
const char *last_slash = strrchr(path, DIR_SEPARATOR);
|
||||
new_object->name.assign((last_slash == nullptr) ? path : last_slash + 1);
|
||||
new_object->input_file = path;
|
||||
|
||||
for (size_t i = 0; i < namedSolids.size(); ++i) {
|
||||
if (proFn) {
|
||||
proFn(LOAD_STEP_STAGE_GET_MESH, i, namedSolids.size(), cb_cancel);
|
||||
if (cb_cancel) {
|
||||
model->delete_object(new_object);
|
||||
shapeTool.reset(nullptr);
|
||||
application->Close(document);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
BRepMesh_IncrementalMesh mesh(namedSolids[i].solid, STEP_TRANS_CHORD_ERROR, false, STEP_TRANS_ANGLE_RES, true);
|
||||
//BBS: calculate total number of the nodes and triangles
|
||||
int aNbNodes = 0;
|
||||
int aNbTriangles = 0;
|
||||
for (TopExp_Explorer anExpSF(namedSolids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) {
|
||||
TopLoc_Location aLoc;
|
||||
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(anExpSF.Current()), aLoc);
|
||||
if (!aTriangulation.IsNull()) {
|
||||
aNbNodes += aTriangulation->NbNodes();
|
||||
aNbTriangles += aTriangulation->NbTriangles();
|
||||
}
|
||||
}
|
||||
|
||||
if (aNbTriangles == 0) {
|
||||
//BBS: No triangulation on the shape.
|
||||
continue;
|
||||
}
|
||||
|
||||
stl_file stl;
|
||||
stl.stats.type = inmemory;
|
||||
stl.stats.number_of_facets = (uint32_t)aNbTriangles;
|
||||
stl.stats.original_num_facets = stl.stats.number_of_facets;
|
||||
stl_allocate(&stl);
|
||||
|
||||
std::vector<Vec3f> points;
|
||||
points.reserve(aNbNodes);
|
||||
//BBS: count faces missing triangulation
|
||||
Standard_Integer aNbFacesNoTri = 0;
|
||||
//BBS: fill temporary triangulation
|
||||
Standard_Integer aNodeOffset = 0;
|
||||
Standard_Integer aTriangleOffet = 0;
|
||||
for (TopExp_Explorer anExpSF(namedSolids[i].solid, TopAbs_FACE); anExpSF.More(); anExpSF.Next()) {
|
||||
const TopoDS_Shape& aFace = anExpSF.Current();
|
||||
TopLoc_Location aLoc;
|
||||
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation(TopoDS::Face(aFace), aLoc);
|
||||
if (aTriangulation.IsNull()) {
|
||||
++aNbFacesNoTri;
|
||||
continue;
|
||||
}
|
||||
//BBS: copy nodes
|
||||
gp_Trsf aTrsf = aLoc.Transformation();
|
||||
for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) {
|
||||
gp_Pnt aPnt = aTriangulation->Node(aNodeIter);
|
||||
aPnt.Transform(aTrsf);
|
||||
points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z())));
|
||||
}
|
||||
//BBS: copy triangles
|
||||
const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
|
||||
for (Standard_Integer aTriIter = 1; aTriIter <= aTriangulation->NbTriangles(); ++aTriIter) {
|
||||
Poly_Triangle aTri = aTriangulation->Triangle(aTriIter);
|
||||
|
||||
Standard_Integer anId[3];
|
||||
aTri.Get(anId[0], anId[1], anId[2]);
|
||||
if (anOrientation == TopAbs_REVERSED) {
|
||||
//BBS: swap 1, 2.
|
||||
Standard_Integer aTmpIdx = anId[1];
|
||||
anId[1] = anId[2];
|
||||
anId[2] = aTmpIdx;
|
||||
}
|
||||
//BBS: Update nodes according to the offset.
|
||||
anId[0] += aNodeOffset;
|
||||
anId[1] += aNodeOffset;
|
||||
anId[2] += aNodeOffset;
|
||||
//BBS: save triangles facets
|
||||
stl_facet facet;
|
||||
facet.vertex[0] = points[anId[0] - 1].cast<float>();
|
||||
facet.vertex[1] = points[anId[1] - 1].cast<float>();
|
||||
facet.vertex[2] = points[anId[2] - 1].cast<float>();
|
||||
facet.extra[0] = 0;
|
||||
facet.extra[1] = 0;
|
||||
stl_normal normal;
|
||||
stl_calculate_normal(normal, &facet);
|
||||
stl_normalize_vector(normal);
|
||||
facet.normal = normal;
|
||||
stl.facet_start[aTriangleOffet + aTriIter - 1] = facet;
|
||||
}
|
||||
|
||||
aNodeOffset += aTriangulation->NbNodes();
|
||||
aTriangleOffet += aTriangulation->NbTriangles();
|
||||
}
|
||||
|
||||
TriangleMesh triangle_mesh;
|
||||
triangle_mesh.from_stl(stl);
|
||||
ModelVolume* new_volume = new_object->add_volume(std::move(triangle_mesh));
|
||||
new_volume->name = namedSolids[i].name;
|
||||
new_volume->source.input_file = path;
|
||||
new_volume->source.object_idx = (int)model->objects.size() - 1;
|
||||
new_volume->source.volume_idx = (int)new_object->volumes.size() - 1;
|
||||
}
|
||||
|
||||
shapeTool.reset(nullptr);
|
||||
application->Close(document);
|
||||
|
||||
//BBS: no valid shape from the step, delete the new object as well
|
||||
if (new_object->volumes.size() == 0) {
|
||||
model->delete_object(new_object);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
42
src/libslic3r/Format/STEP.hpp
Normal file
42
src/libslic3r/Format/STEP.hpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#ifndef slic3r_Format_STEP_hpp_
|
||||
#define slic3r_Format_STEP_hpp_
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
class ModelObject;
|
||||
|
||||
typedef std::function<void(int load_stage, int current, int total, bool& cancel)> ImportStepProgressFn;
|
||||
typedef std::function<void(bool isUtf8)> StepIsUtf8Fn;
|
||||
|
||||
//BBS: Load an step file into a provided model.
|
||||
extern bool load_step(const char *path, Model *model, ImportStepProgressFn proFn = nullptr, StepIsUtf8Fn isUtf8Fn = nullptr);
|
||||
|
||||
//BBS: Used to detect what kind of encoded type is used in name field of step
|
||||
// If is encoded in UTF8, the file don't need to be handled, then return the original path directly.
|
||||
// If is encoded in GBK, then translate to UTF8 and generate a new temporary step file.
|
||||
// If is encoded in Other type, we can't handled, then treat as UTF8. In this case, the name is garbage
|
||||
// characters.
|
||||
// By preprocessing, at least we can avoid garbage characters if the name field is encoded by GBK.
|
||||
class StepPreProcessor {
|
||||
enum class EncodedType : unsigned char
|
||||
{
|
||||
UTF8,
|
||||
GBK,
|
||||
OTHER
|
||||
};
|
||||
|
||||
public:
|
||||
bool preprocess(const char* path, std::string &output_path);
|
||||
static bool isUtf8File(const char* path);
|
||||
private:
|
||||
static bool isUtf8(const std::string str);
|
||||
static bool isGBK(const std::string str);
|
||||
static int preNum(const unsigned char byte);
|
||||
//BBS: default is UTF8 for most step file.
|
||||
EncodedType m_encode_type = EncodedType::UTF8;
|
||||
};
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Format_STEP_hpp_ */
|
||||
62
src/libslic3r/Format/STL.cpp
Normal file
62
src/libslic3r/Format/STL.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#include "../libslic3r.h"
|
||||
#include "../Model.hpp"
|
||||
#include "../TriangleMesh.hpp"
|
||||
|
||||
#include "STL.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DIR_SEPARATOR '\\'
|
||||
#else
|
||||
#define DIR_SEPARATOR '/'
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool load_stl(const char *path, Model *model, const char *object_name_in)
|
||||
{
|
||||
TriangleMesh mesh;
|
||||
if (! mesh.ReadSTLFile(path)) {
|
||||
// die "Failed to open $file\n" if !-e $path;
|
||||
return false;
|
||||
}
|
||||
if (mesh.empty()) {
|
||||
// die "This STL file couldn't be read because it's empty.\n"
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string object_name;
|
||||
if (object_name_in == nullptr) {
|
||||
const char *last_slash = strrchr(path, DIR_SEPARATOR);
|
||||
object_name.assign((last_slash == nullptr) ? path : last_slash + 1);
|
||||
} else
|
||||
object_name.assign(object_name_in);
|
||||
|
||||
model->add_object(object_name.c_str(), path, std::move(mesh));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool store_stl(const char *path, TriangleMesh *mesh, bool binary)
|
||||
{
|
||||
if (binary)
|
||||
mesh->write_binary(path);
|
||||
else
|
||||
mesh->write_ascii(path);
|
||||
//FIXME returning false even if write failed.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool store_stl(const char *path, ModelObject *model_object, bool binary)
|
||||
{
|
||||
TriangleMesh mesh = model_object->mesh();
|
||||
return store_stl(path, &mesh, binary);
|
||||
}
|
||||
|
||||
bool store_stl(const char *path, Model *model, bool binary)
|
||||
{
|
||||
TriangleMesh mesh = model->mesh();
|
||||
return store_stl(path, &mesh, binary);
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
18
src/libslic3r/Format/STL.hpp
Normal file
18
src/libslic3r/Format/STL.hpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef slic3r_Format_STL_hpp_
|
||||
#define slic3r_Format_STL_hpp_
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
class ModelObject;
|
||||
|
||||
// Load an STL file into a provided model.
|
||||
extern bool load_stl(const char *path, Model *model, const char *object_name = nullptr);
|
||||
|
||||
extern bool store_stl(const char *path, TriangleMesh *mesh, bool binary);
|
||||
extern bool store_stl(const char *path, ModelObject *model_object, bool binary);
|
||||
extern bool store_stl(const char *path, Model *model, bool binary);
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Format_STL_hpp_ */
|
||||
6161
src/libslic3r/Format/bbs_3mf.cpp
Normal file
6161
src/libslic3r/Format/bbs_3mf.cpp
Normal file
File diff suppressed because it is too large
Load diff
258
src/libslic3r/Format/bbs_3mf.hpp
Normal file
258
src/libslic3r/Format/bbs_3mf.hpp
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
#ifndef BBS_3MF_hpp_
|
||||
#define BBS_3MF_hpp_
|
||||
|
||||
#include "../GCode/ThumbnailData.hpp"
|
||||
#include "libslic3r/ProjectTask.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include <functional>
|
||||
|
||||
namespace Slic3r {
|
||||
class Model;
|
||||
struct ConfigSubstitutionContext;
|
||||
class DynamicPrintConfig;
|
||||
class Preset;
|
||||
struct FilamentInfo;
|
||||
struct ThumbnailData;
|
||||
|
||||
#define GCODE_FILE_FORMAT "Metadata/plate_%1%.gcode"
|
||||
#define THUMBNAIL_FILE_FORMAT "Metadata/plate_%1%.png"
|
||||
#define PATTERN_FILE_FORMAT "Metadata/plate_%1%_pattern_layer_0.png"
|
||||
#define PATTERN_CONFIG_FILE_FORMAT "Metadata/plate_%1%.json"
|
||||
#define EMBEDDED_PRINT_FILE_FORMAT "Metadata/process_settings_%1%.config"
|
||||
#define EMBEDDED_FILAMENT_FILE_FORMAT "Metadata/filament_settings_%1%.config"
|
||||
#define EMBEDDED_PRINTER_FILE_FORMAT "Metadata/machine_settings_%1%.config"
|
||||
|
||||
|
||||
//BBS: define assistant struct to store temporary variable during exporting 3mf
|
||||
class PackingTemporaryData
|
||||
{
|
||||
public:
|
||||
std::string _3mf_thumbnail;
|
||||
std::string _3mf_printer_thumbnail_middle;
|
||||
std::string _3mf_printer_thumbnail_small;
|
||||
|
||||
PackingTemporaryData() {}
|
||||
};
|
||||
|
||||
|
||||
//BBS: define plate data list related structures
|
||||
struct PlateData
|
||||
{
|
||||
PlateData(int plate_id, std::set<std::pair<int, int>> &obj_to_inst_list, bool lock_state) : plate_index(plate_id), locked(lock_state)
|
||||
{
|
||||
objects_and_instances.clear();
|
||||
for (std::set<std::pair<int, int>>::iterator it = obj_to_inst_list.begin(); it != obj_to_inst_list.end(); ++it)
|
||||
objects_and_instances.emplace_back(it->first, it->second);
|
||||
}
|
||||
PlateData() : plate_index(-1), locked(false)
|
||||
{
|
||||
objects_and_instances.clear();
|
||||
}
|
||||
~PlateData()
|
||||
{
|
||||
objects_and_instances.clear();
|
||||
}
|
||||
|
||||
void parse_filament_info(GCodeProcessorResult *result);
|
||||
|
||||
int plate_index;
|
||||
std::vector<std::pair<int, int>> objects_and_instances;
|
||||
std::string gcode_file;
|
||||
std::string gcode_file_md5;
|
||||
std::string thumbnail_file;
|
||||
ThumbnailData plate_thumbnail;
|
||||
ThumbnailData pattern_thumbnail;
|
||||
std::string pattern_file;
|
||||
std::string gcode_prediction;
|
||||
std::string gcode_weight;
|
||||
std::vector<FilamentInfo> slice_flaments_info;
|
||||
bool is_sliced_valid = false;
|
||||
bool toolpath_outside {false};
|
||||
|
||||
std::string get_gcode_prediction_str() {
|
||||
return gcode_prediction;
|
||||
}
|
||||
|
||||
std::string get_gcode_weight_str() {
|
||||
return gcode_weight;
|
||||
}
|
||||
bool locked;
|
||||
};
|
||||
|
||||
// BBS: encrypt
|
||||
enum class SaveStrategy
|
||||
{
|
||||
Default = 0,
|
||||
FullPathSources = 1,
|
||||
Zip64 = 1 << 1,
|
||||
ProductionExt = 1 << 2,
|
||||
SecureContentExt = 1 << 3,
|
||||
WithGcode = 1 << 4,
|
||||
Silence = 1 << 5,
|
||||
SkipStatic = 1 << 6,
|
||||
SkipModel = 1 << 7,
|
||||
WithSliceInfo = 1 << 8,
|
||||
|
||||
SplitModel = 0x1000 | ProductionExt,
|
||||
Encrypted = SecureContentExt | SplitModel,
|
||||
Backup = 0x10000 | WithGcode | Silence | SkipStatic | SplitModel,
|
||||
};
|
||||
|
||||
inline SaveStrategy operator | (SaveStrategy lhs, SaveStrategy rhs)
|
||||
{
|
||||
using T = std::underlying_type_t <SaveStrategy>;
|
||||
return static_cast<SaveStrategy>(static_cast<T>(lhs) | static_cast<T>(rhs));
|
||||
}
|
||||
|
||||
inline bool operator & (SaveStrategy & lhs, SaveStrategy rhs)
|
||||
{
|
||||
using T = std::underlying_type_t <SaveStrategy>;
|
||||
return ((static_cast<T>(lhs) & static_cast<T>(rhs))) == static_cast<T>(rhs);
|
||||
}
|
||||
|
||||
enum class LoadStrategy
|
||||
{
|
||||
Default = 0,
|
||||
AddDefaultInstances = 1,
|
||||
CheckVersion = 2,
|
||||
LoadModel = 4,
|
||||
LoadConfig = 8,
|
||||
LoadAuxiliary = 16,
|
||||
Silence = 32,
|
||||
ImperialUnits = 64,
|
||||
|
||||
Restore = 0x10000 | LoadModel | LoadConfig | LoadAuxiliary | Silence,
|
||||
};
|
||||
|
||||
inline LoadStrategy operator | (LoadStrategy lhs, LoadStrategy rhs)
|
||||
{
|
||||
using T = std::underlying_type_t <LoadStrategy>;
|
||||
return static_cast<LoadStrategy>(static_cast<T>(lhs) | static_cast<T>(rhs));
|
||||
}
|
||||
|
||||
inline bool operator & (LoadStrategy & lhs, LoadStrategy rhs)
|
||||
{
|
||||
using T = std::underlying_type_t <LoadStrategy>;
|
||||
return (static_cast<T>(lhs) & static_cast<T>(rhs)) == static_cast<T>(rhs);
|
||||
}
|
||||
|
||||
const int EXPORT_STAGE_OPEN_3MF = 0;
|
||||
const int EXPORT_STAGE_CONTENT_TYPES = 1;
|
||||
const int EXPORT_STAGE_ADD_THUMBNAILS = 2;
|
||||
const int EXPORT_STAGE_ADD_RELATIONS = 3;
|
||||
const int EXPORT_STAGE_ADD_MODELS = 4;
|
||||
const int EXPORT_STAGE_ADD_LAYER_RANGE = 5;
|
||||
const int EXPORT_STAGE_ADD_SUPPORT = 6;
|
||||
const int EXPORT_STAGE_ADD_CUSTOM_GCODE = 7;
|
||||
const int EXPORT_STAGE_ADD_PRINT_CONFIG = 8;
|
||||
const int EXPORT_STAGE_ADD_PROJECT_CONFIG = 9;
|
||||
const int EXPORT_STAGE_ADD_CONFIG_FILE = 10;
|
||||
const int EXPORT_STAGE_ADD_SLICE_INFO = 11;
|
||||
const int EXPORT_STAGE_ADD_GCODE = 12;
|
||||
const int EXPORT_STAGE_ADD_AUXILIARIES = 13;
|
||||
const int EXPORT_STAGE_FINISH = 14;
|
||||
|
||||
const int IMPORT_STAGE_RESTORE = 0;
|
||||
const int IMPORT_STAGE_OPEN = 1;
|
||||
const int IMPORT_STAGE_READ_FILES = 2;
|
||||
const int IMPORT_STAGE_EXTRACT = 3;
|
||||
const int IMPORT_STAGE_LOADING_OBJECTS = 4;
|
||||
const int IMPORT_STAGE_LOADING_PLATES = 5;
|
||||
const int IMPORT_STAGE_FINISH = 6;
|
||||
const int IMPORT_STAGE_ADD_INSTANCE = 7;
|
||||
const int IMPORT_STAGE_UPDATE_GCODE = 8;
|
||||
const int IMPORT_STAGE_CHECK_MODE_GCODE = 9;
|
||||
const int UPDATE_GCODE_RESULT = 10;
|
||||
const int IMPORT_LOAD_CONFIG = 11;
|
||||
const int IMPORT_LOAD_MODEL_OBJECTS = 12;
|
||||
|
||||
//BBS export 3mf progress
|
||||
typedef std::function<void(int export_stage, int current, int total, bool& cancel)> Export3mfProgressFn;
|
||||
typedef std::function<void(int import_stage, int current, int total, bool& cancel)> Import3mfProgressFn;
|
||||
|
||||
typedef std::vector<PlateData*> PlateDataPtrs;
|
||||
|
||||
typedef std::map<int, PlateData*> PlateDataMaps;
|
||||
|
||||
struct StoreParams
|
||||
{
|
||||
const char* path;
|
||||
Model* model = nullptr;
|
||||
PlateDataPtrs plate_data_list;
|
||||
int export_plate_idx = -1;
|
||||
std::vector<Preset*> project_presets;
|
||||
DynamicPrintConfig* config;
|
||||
std::vector<ThumbnailData*> thumbnail_data;
|
||||
std::vector<ThumbnailData*> calibration_thumbnail_data;
|
||||
SaveStrategy strategy = SaveStrategy::Zip64;
|
||||
Export3mfProgressFn proFn = nullptr;
|
||||
std::vector<PlateBBoxData*> id_bboxes;
|
||||
BBLProject* project = nullptr;
|
||||
BBLProfile* profile = nullptr;
|
||||
|
||||
StoreParams() {}
|
||||
};
|
||||
|
||||
|
||||
//BBS: add plate data list related logic
|
||||
// add restore logic
|
||||
// Load the content of a 3mf file into the given model and preset bundle.
|
||||
extern bool load_bbs_3mf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, PlateDataPtrs* plate_data_list, std::vector<Preset*>* project_presets, bool* is_bbl_3mf, Semver* file_version, Import3mfProgressFn proFn = nullptr, LoadStrategy strategy = LoadStrategy::Default, BBLProject *project = nullptr);
|
||||
|
||||
extern std::string bbs_3mf_get_thumbnail(const char * path);
|
||||
|
||||
//BBS: add plate data list related logic
|
||||
// add backup logic
|
||||
// Save the given model and the config data contained in the given Print into a 3mf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
/*
|
||||
extern bool store_bbs_3mf(const char* path,
|
||||
Model* model,
|
||||
PlateDataPtrs& plate_data_list,
|
||||
std::vector<Preset*>& project_presets,
|
||||
const DynamicPrintConfig* config,
|
||||
bool fullpath_sources,
|
||||
const std::vector<ThumbnailData*>& thumbnail_data,
|
||||
bool zip64 = true,
|
||||
bool skip_static = false,
|
||||
Export3mfProgressFn proFn = nullptr,
|
||||
bool silence = true);
|
||||
*/
|
||||
|
||||
extern bool store_bbs_3mf(StoreParams& store_params);
|
||||
|
||||
extern void release_PlateData_list(PlateDataPtrs& plate_data_list);
|
||||
|
||||
// backup & restore project
|
||||
|
||||
extern void save_object_mesh(ModelObject& object);
|
||||
|
||||
extern void delete_object_mesh(ModelObject& object);
|
||||
|
||||
extern void backup_soon();
|
||||
|
||||
extern void remove_backup(Model& model, bool removeAll);
|
||||
|
||||
extern void set_backup_interval(long interval);
|
||||
|
||||
extern void set_backup_callback(std::function<void(int)> callback);
|
||||
|
||||
extern void run_backup_ui_tasks();
|
||||
|
||||
extern bool has_restore_data(std::string & path, std::string & origin);
|
||||
|
||||
extern void put_other_changes();
|
||||
|
||||
extern void clear_other_changes(bool backup);
|
||||
|
||||
extern bool has_other_changes(bool backup);
|
||||
|
||||
class SaveObjectGaurd {
|
||||
public:
|
||||
SaveObjectGaurd(ModelObject& object);
|
||||
~SaveObjectGaurd();
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* BBS_3MF_hpp_ */
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue