mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-12-05 16:51:07 -07:00
Merge some BS1.7 changes:
internal_solid_infill_pattern
This commit is contained in:
parent
7ece35931e
commit
bcbbbf35db
95 changed files with 44122 additions and 693 deletions
322
src/libslic3r/AABBMesh.cpp
Normal file
322
src/libslic3r/AABBMesh.cpp
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
#include "AABBMesh.hpp"
|
||||
#include <Execution/ExecutionTBB.hpp>
|
||||
|
||||
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
#include <numeric>
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AABBMesh::AABBImpl {
|
||||
private:
|
||||
AABBTreeIndirect::Tree3f m_tree;
|
||||
double m_triangle_ray_epsilon;
|
||||
|
||||
public:
|
||||
void init(const indexed_triangle_set &its, bool calculate_epsilon)
|
||||
{
|
||||
m_triangle_ray_epsilon = 0.000001;
|
||||
if (calculate_epsilon) {
|
||||
// Calculate epsilon from average triangle edge length.
|
||||
double l = its_average_edge_length(its);
|
||||
if (l > 0)
|
||||
m_triangle_ray_epsilon = 0.000001 * l * l;
|
||||
}
|
||||
m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||
its.vertices, its.indices);
|
||||
}
|
||||
|
||||
void intersect_ray(const indexed_triangle_set &its,
|
||||
const Vec3d & s,
|
||||
const Vec3d & dir,
|
||||
igl::Hit & hit)
|
||||
{
|
||||
AABBTreeIndirect::intersect_ray_first_hit(its.vertices, its.indices,
|
||||
m_tree, s, dir, hit, m_triangle_ray_epsilon);
|
||||
}
|
||||
|
||||
void intersect_ray(const indexed_triangle_set &its,
|
||||
const Vec3d & s,
|
||||
const Vec3d & dir,
|
||||
std::vector<igl::Hit> & hits)
|
||||
{
|
||||
AABBTreeIndirect::intersect_ray_all_hits(its.vertices, its.indices,
|
||||
m_tree, s, dir, hits, m_triangle_ray_epsilon);
|
||||
}
|
||||
|
||||
double squared_distance(const indexed_triangle_set & its,
|
||||
const Vec3d & point,
|
||||
int & i,
|
||||
Eigen::Matrix<double, 1, 3> &closest)
|
||||
{
|
||||
size_t idx_unsigned = 0;
|
||||
Vec3d closest_vec3d(closest);
|
||||
double dist =
|
||||
AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
its.vertices, its.indices, m_tree, point, idx_unsigned,
|
||||
closest_vec3d);
|
||||
i = int(idx_unsigned);
|
||||
closest = closest_vec3d;
|
||||
return dist;
|
||||
}
|
||||
};
|
||||
|
||||
template<class M> void AABBMesh::init(const M &mesh, bool calculate_epsilon)
|
||||
{
|
||||
// Build the AABB accelaration tree
|
||||
m_aabb->init(*m_tm, calculate_epsilon);
|
||||
}
|
||||
|
||||
AABBMesh::AABBMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon)
|
||||
: m_tm(&tmesh)
|
||||
, m_aabb(new AABBImpl())
|
||||
, m_vfidx{tmesh}
|
||||
, m_fnidx{its_face_neighbors(tmesh)}
|
||||
{
|
||||
init(tmesh, calculate_epsilon);
|
||||
}
|
||||
|
||||
AABBMesh::AABBMesh(const TriangleMesh &mesh, bool calculate_epsilon)
|
||||
: m_tm(&mesh.its)
|
||||
, m_aabb(new AABBImpl())
|
||||
, m_vfidx{mesh.its}
|
||||
, m_fnidx{its_face_neighbors(mesh.its)}
|
||||
{
|
||||
init(mesh, calculate_epsilon);
|
||||
}
|
||||
|
||||
AABBMesh::~AABBMesh() {}
|
||||
|
||||
AABBMesh::AABBMesh(const AABBMesh &other)
|
||||
: m_tm(other.m_tm)
|
||||
, m_aabb(new AABBImpl(*other.m_aabb))
|
||||
, m_vfidx{other.m_vfidx}
|
||||
, m_fnidx{other.m_fnidx}
|
||||
{}
|
||||
|
||||
AABBMesh &AABBMesh::operator=(const AABBMesh &other)
|
||||
{
|
||||
m_tm = other.m_tm;
|
||||
m_aabb.reset(new AABBImpl(*other.m_aabb));
|
||||
m_vfidx = other.m_vfidx;
|
||||
m_fnidx = other.m_fnidx;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
AABBMesh &AABBMesh::operator=(AABBMesh &&other) = default;
|
||||
|
||||
AABBMesh::AABBMesh(AABBMesh &&other) = default;
|
||||
|
||||
|
||||
|
||||
const std::vector<Vec3f>& AABBMesh::vertices() const
|
||||
{
|
||||
return m_tm->vertices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const std::vector<Vec3i>& AABBMesh::indices() const
|
||||
{
|
||||
return m_tm->indices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Vec3f& AABBMesh::vertices(size_t idx) const
|
||||
{
|
||||
return m_tm->vertices[idx];
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Vec3i& AABBMesh::indices(size_t idx) const
|
||||
{
|
||||
return m_tm->indices[idx];
|
||||
}
|
||||
|
||||
|
||||
Vec3d AABBMesh::normal_by_face_id(int face_id) const {
|
||||
|
||||
return its_unnormalized_normal(*m_tm, face_id).cast<double>().normalized();
|
||||
}
|
||||
|
||||
|
||||
AABBMesh::hit_result
|
||||
AABBMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
assert(is_approx(dir.norm(), 1.));
|
||||
igl::Hit hit{-1, -1, 0.f, 0.f, 0.f};
|
||||
hit.t = std::numeric_limits<float>::infinity();
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
if (! m_holes.empty()) {
|
||||
|
||||
// If there are holes, the hit_results will be made by
|
||||
// query_ray_hits (object) and filter_hits (holes):
|
||||
return filter_hits(query_ray_hits(s, dir));
|
||||
}
|
||||
#endif
|
||||
|
||||
m_aabb->intersect_ray(*m_tm, s, dir, hit);
|
||||
hit_result ret(*this);
|
||||
ret.m_t = double(hit.t);
|
||||
ret.m_dir = dir;
|
||||
ret.m_source = s;
|
||||
if(!std::isinf(hit.t) && !std::isnan(hit.t)) {
|
||||
ret.m_normal = this->normal_by_face_id(hit.id);
|
||||
ret.m_face_id = hit.id;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<AABBMesh::hit_result>
|
||||
AABBMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
std::vector<AABBMesh::hit_result> outs;
|
||||
std::vector<igl::Hit> hits;
|
||||
m_aabb->intersect_ray(*m_tm, s, dir, hits);
|
||||
|
||||
// The sort is necessary, the hits are not always sorted.
|
||||
std::sort(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
||||
|
||||
// Remove duplicates. They sometimes appear, for example when the ray is cast
|
||||
// along an axis of a cube due to floating-point approximations in igl (?)
|
||||
hits.erase(std::unique(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b)
|
||||
{ return a.t == b.t; }),
|
||||
hits.end());
|
||||
|
||||
// Convert the igl::Hit into hit_result
|
||||
outs.reserve(hits.size());
|
||||
for (const igl::Hit& hit : hits) {
|
||||
outs.emplace_back(AABBMesh::hit_result(*this));
|
||||
outs.back().m_t = double(hit.t);
|
||||
outs.back().m_dir = dir;
|
||||
outs.back().m_source = s;
|
||||
if(!std::isinf(hit.t) && !std::isnan(hit.t)) {
|
||||
outs.back().m_normal = this->normal_by_face_id(hit.id);
|
||||
outs.back().m_face_id = hit.id;
|
||||
}
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
AABBMesh::hit_result IndexedMesh::filter_hits(
|
||||
const std::vector<AABBMesh::hit_result>& object_hits) const
|
||||
{
|
||||
assert(! m_holes.empty());
|
||||
hit_result out(*this);
|
||||
|
||||
if (object_hits.empty())
|
||||
return out;
|
||||
|
||||
const Vec3d& s = object_hits.front().source();
|
||||
const Vec3d& dir = object_hits.front().direction();
|
||||
|
||||
// A helper struct to save an intersetion with a hole
|
||||
struct HoleHit {
|
||||
HoleHit(float t_p, const Vec3d& normal_p, bool entry_p) :
|
||||
t(t_p), normal(normal_p), entry(entry_p) {}
|
||||
float t;
|
||||
Vec3d normal;
|
||||
bool entry;
|
||||
};
|
||||
std::vector<HoleHit> hole_isects;
|
||||
hole_isects.reserve(m_holes.size());
|
||||
|
||||
auto sf = s.cast<float>();
|
||||
auto dirf = dir.cast<float>();
|
||||
|
||||
// Collect hits on all holes, preserve information about entry/exit
|
||||
for (const sla::DrainHole& hole : m_holes) {
|
||||
std::array<std::pair<float, Vec3d>, 2> isects;
|
||||
if (hole.get_intersections(sf, dirf, isects)) {
|
||||
// Ignore hole hits behind the source
|
||||
if (isects[0].first > 0.f) hole_isects.emplace_back(isects[0].first, isects[0].second, true);
|
||||
if (isects[1].first > 0.f) hole_isects.emplace_back(isects[1].first, isects[1].second, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Holes can intersect each other, sort the hits by t
|
||||
std::sort(hole_isects.begin(), hole_isects.end(),
|
||||
[](const HoleHit& a, const HoleHit& b) { return a.t < b.t; });
|
||||
|
||||
// Now inspect the intersections with object and holes, in the order of
|
||||
// increasing distance. Keep track how deep are we nested in mesh/holes and
|
||||
// pick the correct intersection.
|
||||
// This needs to be done twice - first to find out how deep in the structure
|
||||
// the source is, then to pick the correct intersection.
|
||||
int hole_nested = 0;
|
||||
int object_nested = 0;
|
||||
for (int dry_run=1; dry_run>=0; --dry_run) {
|
||||
hole_nested = -hole_nested;
|
||||
object_nested = -object_nested;
|
||||
|
||||
bool is_hole = false;
|
||||
bool is_entry = false;
|
||||
const HoleHit* next_hole_hit = hole_isects.empty() ? nullptr : &hole_isects.front();
|
||||
const hit_result* next_mesh_hit = &object_hits.front();
|
||||
|
||||
while (next_hole_hit || next_mesh_hit) {
|
||||
if (next_hole_hit && next_mesh_hit) // still have hole and obj hits
|
||||
is_hole = (next_hole_hit->t < next_mesh_hit->m_t);
|
||||
else
|
||||
is_hole = next_hole_hit; // one or the other ran out
|
||||
|
||||
// Is this entry or exit hit?
|
||||
is_entry = is_hole ? next_hole_hit->entry : ! next_mesh_hit->is_inside();
|
||||
|
||||
if (! dry_run) {
|
||||
if (! is_hole && hole_nested == 0) {
|
||||
// This is a valid object hit
|
||||
return *next_mesh_hit;
|
||||
}
|
||||
if (is_hole && ! is_entry && object_nested != 0) {
|
||||
// This holehit is the one we seek
|
||||
out.m_t = next_hole_hit->t;
|
||||
out.m_normal = next_hole_hit->normal;
|
||||
out.m_source = s;
|
||||
out.m_dir = dir;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
// Increase/decrease the counter
|
||||
(is_hole ? hole_nested : object_nested) += (is_entry ? 1 : -1);
|
||||
|
||||
// Advance the respective pointer
|
||||
if (is_hole && next_hole_hit++ == &hole_isects.back())
|
||||
next_hole_hit = nullptr;
|
||||
if (! is_hole && next_mesh_hit++ == &object_hits.back())
|
||||
next_mesh_hit = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here, the ray ended up in infinity
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
double AABBMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
||||
double sqdst = 0;
|
||||
Eigen::Matrix<double, 1, 3> pp = p;
|
||||
Eigen::Matrix<double, 1, 3> cc;
|
||||
sqdst = m_aabb->squared_distance(*m_tm, pp, i, cc);
|
||||
c = cc;
|
||||
return sqdst;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
142
src/libslic3r/AABBMesh.hpp
Normal file
142
src/libslic3r/AABBMesh.hpp
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
#ifndef PRUSASLICER_AABBMESH_H
|
||||
#define PRUSASLICER_AABBMESH_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
// There is an implementation of a hole-aware raycaster that was eventually
|
||||
// not used in production version. It is now hidden under following define
|
||||
// for possible future use.
|
||||
// #define SLIC3R_HOLE_RAYCASTER
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
#endif
|
||||
|
||||
struct indexed_triangle_set;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
// An index-triangle structure coupled with an AABB index to support ray
|
||||
// casting and other higher level operations.
|
||||
class AABBMesh {
|
||||
class AABBImpl;
|
||||
|
||||
const indexed_triangle_set* m_tm;
|
||||
|
||||
std::unique_ptr<AABBImpl> m_aabb;
|
||||
VertexFaceIndex m_vfidx; // vertex-face index
|
||||
std::vector<Vec3i> m_fnidx; // face-neighbor index
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
// This holds a copy of holes in the mesh. Initialized externally
|
||||
// by load_mesh setter.
|
||||
std::vector<sla::DrainHole> m_holes;
|
||||
#endif
|
||||
|
||||
template<class M> void init(const M &mesh, bool calculate_epsilon);
|
||||
|
||||
public:
|
||||
|
||||
// calculate_epsilon ... calculate epsilon for triangle-ray intersection from an average triangle edge length.
|
||||
// If set to false, a default epsilon is used, which works for "reasonable" meshes.
|
||||
explicit AABBMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon = false);
|
||||
explicit AABBMesh(const TriangleMesh &mesh, bool calculate_epsilon = false);
|
||||
|
||||
AABBMesh(const AABBMesh& other);
|
||||
AABBMesh& operator=(const AABBMesh&);
|
||||
|
||||
AABBMesh(AABBMesh &&other);
|
||||
AABBMesh& operator=(AABBMesh &&other);
|
||||
|
||||
~AABBMesh();
|
||||
|
||||
const std::vector<Vec3f>& vertices() const;
|
||||
const std::vector<Vec3i>& indices() const;
|
||||
const Vec3f& vertices(size_t idx) const;
|
||||
const Vec3i& indices(size_t idx) const;
|
||||
|
||||
// Result of a raycast
|
||||
class hit_result {
|
||||
// m_t holds a distance from m_source to the intersection.
|
||||
double m_t = infty();
|
||||
int m_face_id = -1;
|
||||
const AABBMesh *m_mesh = nullptr;
|
||||
Vec3d m_dir = Vec3d::Zero();
|
||||
Vec3d m_source = Vec3d::Zero();
|
||||
Vec3d m_normal = Vec3d::Zero();
|
||||
friend class AABBMesh;
|
||||
|
||||
// A valid object of this class can only be obtained from
|
||||
// IndexedMesh::query_ray_hit method.
|
||||
explicit inline hit_result(const AABBMesh& em): m_mesh(&em) {}
|
||||
public:
|
||||
// This denotes no hit on the mesh.
|
||||
static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); }
|
||||
|
||||
explicit inline hit_result(double val = infty()) : m_t(val) {}
|
||||
|
||||
inline double distance() const { return m_t; }
|
||||
inline const Vec3d& direction() const { return m_dir; }
|
||||
inline const Vec3d& source() const { return m_source; }
|
||||
inline Vec3d position() const { return m_source + m_dir * m_t; }
|
||||
inline int face() const { return m_face_id; }
|
||||
inline bool is_valid() const { return m_mesh != nullptr; }
|
||||
inline bool is_hit() const { return m_face_id >= 0 && !std::isinf(m_t); }
|
||||
|
||||
inline const Vec3d& normal() const {
|
||||
assert(is_valid());
|
||||
return m_normal;
|
||||
}
|
||||
|
||||
inline bool is_inside() const {
|
||||
return is_hit() && normal().dot(m_dir) > 0;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
// Inform the object about location of holes
|
||||
// creates internal copy of the vector
|
||||
void load_holes(const std::vector<sla::DrainHole>& holes) {
|
||||
m_holes = holes;
|
||||
}
|
||||
|
||||
// Iterates over hits and holes and returns the true hit, possibly
|
||||
// on the inside of a hole.
|
||||
// This function is currently not used anywhere, it was written when the
|
||||
// holes were subtracted on slices, that is, before we started using CGAL
|
||||
// to actually cut the holes into the mesh.
|
||||
hit_result filter_hits(const std::vector<AABBMesh::hit_result>& obj_hits) const;
|
||||
#endif
|
||||
|
||||
// Casting a ray on the mesh, returns the distance where the hit occures.
|
||||
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
|
||||
|
||||
// Casts a ray on the mesh and returns all hits
|
||||
std::vector<hit_result> query_ray_hits(const Vec3d &s, const Vec3d &dir) const;
|
||||
|
||||
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
|
||||
inline double squared_distance(const Vec3d &p) const
|
||||
{
|
||||
int i;
|
||||
Vec3d c;
|
||||
return squared_distance(p, i, c);
|
||||
}
|
||||
|
||||
Vec3d normal_by_face_id(int face_id) const;
|
||||
|
||||
const indexed_triangle_set * get_triangle_mesh() const { return m_tm; }
|
||||
|
||||
const VertexFaceIndex &vertex_face_index() const { return m_vfidx; }
|
||||
const std::vector<Vec3i> &face_neighbor_index() const { return m_fnidx; }
|
||||
};
|
||||
|
||||
|
||||
} // namespace Slic3r::sla
|
||||
|
||||
#endif // INDEXEDMESH_H
|
||||
130
src/libslic3r/AnyPtr.hpp
Normal file
130
src/libslic3r/AnyPtr.hpp
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#ifndef ANYPTR_HPP
|
||||
#define ANYPTR_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// A general purpose pointer holder that can hold any type of smart pointer
|
||||
// or raw pointer which can own or not own any object they point to.
|
||||
// In case a raw pointer is stored, it is not destructed so ownership is
|
||||
// assumed to be foreign.
|
||||
//
|
||||
// The stored pointer is not checked for being null when dereferenced.
|
||||
//
|
||||
// This is a movable only object due to the fact that it can possibly hold
|
||||
// a unique_ptr which a non-copy.
|
||||
template<class T>
|
||||
class AnyPtr {
|
||||
enum { RawPtr, UPtr, ShPtr, WkPtr };
|
||||
|
||||
boost::variant<T*, std::unique_ptr<T>, std::shared_ptr<T>, std::weak_ptr<T>> ptr;
|
||||
|
||||
template<class Self> static T *get_ptr(Self &&s)
|
||||
{
|
||||
switch (s.ptr.which()) {
|
||||
case RawPtr: return boost::get<T *>(s.ptr);
|
||||
case UPtr: return boost::get<std::unique_ptr<T>>(s.ptr).get();
|
||||
case ShPtr: return boost::get<std::shared_ptr<T>>(s.ptr).get();
|
||||
case WkPtr: {
|
||||
auto shptr = boost::get<std::weak_ptr<T>>(s.ptr).lock();
|
||||
return shptr.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
public:
|
||||
template<class TT = T, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(TT *p = nullptr) : ptr{p}
|
||||
{}
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(std::unique_ptr<TT> p) : ptr{std::unique_ptr<T>(std::move(p))}
|
||||
{}
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(std::shared_ptr<TT> p) : ptr{std::shared_ptr<T>(std::move(p))}
|
||||
{}
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr(std::weak_ptr<TT> p) : ptr{std::weak_ptr<T>(std::move(p))}
|
||||
{}
|
||||
|
||||
~AnyPtr() = default;
|
||||
|
||||
AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {}
|
||||
AnyPtr(const AnyPtr &other) = delete;
|
||||
|
||||
AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; }
|
||||
AnyPtr &operator=(const AnyPtr &other) = delete;
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(TT *p) { ptr = p; return *this; }
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(std::unique_ptr<TT> p) { ptr = std::move(p); return *this; }
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(std::shared_ptr<TT> p) { ptr = p; return *this; }
|
||||
|
||||
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
|
||||
AnyPtr &operator=(std::weak_ptr<TT> p) { ptr = std::move(p); return *this; }
|
||||
|
||||
const T &operator*() const { return *get_ptr(*this); }
|
||||
T &operator*() { return *get_ptr(*this); }
|
||||
|
||||
T *operator->() { return get_ptr(*this); }
|
||||
const T *operator->() const { return get_ptr(*this); }
|
||||
|
||||
T *get() { return get_ptr(*this); }
|
||||
const T *get() const { return get_ptr(*this); }
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
switch (ptr.which()) {
|
||||
case RawPtr: return bool(boost::get<T *>(ptr));
|
||||
case UPtr: return bool(boost::get<std::unique_ptr<T>>(ptr));
|
||||
case ShPtr: return bool(boost::get<std::shared_ptr<T>>(ptr));
|
||||
case WkPtr: {
|
||||
auto shptr = boost::get<std::weak_ptr<T>>(ptr).lock();
|
||||
return bool(shptr);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the stored pointer is a shared or weak pointer, returns a reference
|
||||
// counted copy. Empty shared pointer is returned otherwise.
|
||||
std::shared_ptr<T> get_shared_cpy() const
|
||||
{
|
||||
std::shared_ptr<T> ret;
|
||||
|
||||
switch (ptr.which()) {
|
||||
case ShPtr: ret = boost::get<std::shared_ptr<T>>(ptr); break;
|
||||
case WkPtr: ret = boost::get<std::weak_ptr<T>>(ptr).lock(); break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// If the underlying pointer is unique, convert to shared pointer
|
||||
void convert_unique_to_shared()
|
||||
{
|
||||
if (ptr.which() == UPtr)
|
||||
ptr = std::shared_ptr<T>{std::move(boost::get<std::unique_ptr<T>>(ptr))};
|
||||
}
|
||||
|
||||
// Returns true if the data is owned by this AnyPtr instance
|
||||
bool is_owned() const noexcept
|
||||
{
|
||||
return ptr.which() == UPtr || ptr.which() == ShPtr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // ANYPTR_HPP
|
||||
|
|
@ -207,11 +207,17 @@ void AppConfig::set_defaults()
|
|||
if (get("developer_mode").empty())
|
||||
set_bool("developer_mode", false);
|
||||
|
||||
if (get("enable_ssl_for_mqtt").empty())
|
||||
set_bool("enable_ssl_for_mqtt", true);
|
||||
|
||||
if (get("enable_ssl_for_ftp").empty())
|
||||
set_bool("enable_ssl_for_ftp", true);
|
||||
|
||||
if (get("severity_level").empty())
|
||||
set("severity_level", "info");
|
||||
|
||||
if (get("dump_video").empty())
|
||||
set_bool("dump_video", false);
|
||||
if (get("internal_developer_mode").empty())
|
||||
set_bool("internal_developer_mode", false);
|
||||
|
||||
// BBS
|
||||
if (get("preset_folder").empty())
|
||||
|
|
@ -571,14 +577,8 @@ std::string AppConfig::load()
|
|||
|
||||
void AppConfig::save()
|
||||
{
|
||||
{
|
||||
// Returns "undefined" if the thread naming functionality is not supported by the operating system.
|
||||
std::optional<std::string> current_thread_name = get_current_thread_name();
|
||||
if (current_thread_name && *current_thread_name != "bambustu_main" && *current_thread_name != "main") {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<<", current_thread_name is " << *current_thread_name;
|
||||
throw CriticalException("Calling AppConfig::save() from a worker thread, thread name: " + *current_thread_name);
|
||||
}
|
||||
}
|
||||
if (! is_main_thread_active())
|
||||
throw CriticalException("Calling AppConfig::save() from a worker thread!");
|
||||
|
||||
// The config is first written to a file with a PID suffix and then moved
|
||||
// to avoid race conditions with multiple instances of Slic3r
|
||||
|
|
@ -838,12 +838,8 @@ std::string AppConfig::load()
|
|||
|
||||
void AppConfig::save()
|
||||
{
|
||||
{
|
||||
// Returns "undefined" if the thread naming functionality is not supported by the operating system.
|
||||
std::optional<std::string> current_thread_name = get_current_thread_name();
|
||||
if (current_thread_name && *current_thread_name != "bambustu_main")
|
||||
throw CriticalException("Calling AppConfig::save() from a worker thread!");
|
||||
}
|
||||
if (! is_main_thread_active())
|
||||
throw CriticalException("Calling AppConfig::save() from a worker thread!");
|
||||
|
||||
// The config is first written to a file with a PID suffix and then moved
|
||||
// to avoid race conditions with multiple instances of Slic3r
|
||||
|
|
|
|||
|
|
@ -26,25 +26,29 @@ namespace Slic3r {
|
|||
static void append_and_translate(ExPolygons &dst, const ExPolygons &src, const PrintInstance &instance) {
|
||||
size_t dst_idx = dst.size();
|
||||
expolygons_append(dst, src);
|
||||
|
||||
Point instance_shift = instance.shift_without_plate_offset();
|
||||
for (; dst_idx < dst.size(); ++dst_idx)
|
||||
dst[dst_idx].translate(instance.shift.x(), instance.shift.y());
|
||||
dst[dst_idx].translate(instance_shift);
|
||||
}
|
||||
// BBS: generate brim area by objs
|
||||
static void append_and_translate(ExPolygons& dst, const ExPolygons& src,
|
||||
const PrintInstance& instance, const Print& print, std::map<ObjectID, ExPolygons>& brimAreaMap) {
|
||||
ExPolygons srcShifted = src;
|
||||
for (size_t src_idx = 0; src_idx < src.size(); ++src_idx)
|
||||
srcShifted[src_idx].translate(instance.shift.x(), instance.shift.y());
|
||||
srcShifted = diff_ex(std::move(srcShifted), dst);
|
||||
Point instance_shift = instance.shift_without_plate_offset();
|
||||
for (size_t src_idx = 0; src_idx < srcShifted.size(); ++src_idx)
|
||||
srcShifted[src_idx].translate(instance_shift);
|
||||
srcShifted = diff_ex(srcShifted, dst);
|
||||
//expolygons_append(dst, temp2);
|
||||
expolygons_append(brimAreaMap[instance.print_object->id()], srcShifted);
|
||||
expolygons_append(brimAreaMap[instance.print_object->id()], std::move(srcShifted));
|
||||
}
|
||||
|
||||
static void append_and_translate(Polygons &dst, const Polygons &src, const PrintInstance &instance) {
|
||||
size_t dst_idx = dst.size();
|
||||
polygons_append(dst, src);
|
||||
Point instance_shift = instance.shift_without_plate_offset();
|
||||
for (; dst_idx < dst.size(); ++dst_idx)
|
||||
dst[dst_idx].translate(instance.shift.x(), instance.shift.y());
|
||||
dst[dst_idx].translate(instance_shift);
|
||||
}
|
||||
|
||||
static float max_brim_width(const ConstPrintObjectPtrsAdaptor &objects)
|
||||
|
|
@ -89,12 +93,14 @@ static ConstPrintObjectPtrs get_top_level_objects_with_brim(const Print &print,
|
|||
islands_object.emplace_back(ex_poly.contour);
|
||||
|
||||
islands.reserve(islands.size() + object->instances().size() * islands_object.size());
|
||||
for (const PrintInstance &instance : object->instances())
|
||||
for (Polygon &poly : islands_object) {
|
||||
for (const PrintInstance& instance : object->instances()) {
|
||||
Point instance_shift = instance.shift_without_plate_offset();
|
||||
for (Polygon& poly : islands_object) {
|
||||
islands.emplace_back(poly);
|
||||
islands.back().translate(instance.shift);
|
||||
islands.back().translate(instance_shift);
|
||||
island_to_object.emplace_back(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(islands.size() == island_to_object.size());
|
||||
|
||||
|
|
@ -982,8 +988,6 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
|||
}
|
||||
}
|
||||
if (!bedExPoly.empty()){
|
||||
auto plateOffset = print.get_plate_origin();
|
||||
bedExPoly.front().translate(scale_(plateOffset.x()), scale_(plateOffset.y()));
|
||||
no_brim_area.push_back(bedExPoly.front());
|
||||
}
|
||||
for (const PrintObject* object : print.objects()) {
|
||||
|
|
@ -1224,8 +1228,9 @@ static void make_inner_island_brim(const Print& print, const ConstPrintObjectPtr
|
|||
for (const PrintInstance& instance : object->instances()) {
|
||||
size_t dst_idx = final_loops.size();
|
||||
final_loops.insert(final_loops.end(), all_loops_object.begin(), all_loops_object.end());
|
||||
Point instance_shift = instance.shift_without_plate_offset();
|
||||
for (; dst_idx < final_loops.size(); ++dst_idx)
|
||||
final_loops[dst_idx].translate(instance.shift.x(), instance.shift.y());
|
||||
final_loops[dst_idx].translate(instance_shift);
|
||||
|
||||
}
|
||||
extrusion_entities_append_loops_and_paths(brim.entities, std::move(final_loops),
|
||||
|
|
@ -1561,6 +1566,12 @@ ExtrusionEntityCollection makeBrimInfill(const ExPolygons& singleBrimArea, const
|
|||
optimize_polylines_by_reversing(&all_loops);
|
||||
all_loops = connect_brim_lines(std::move(all_loops), offset(singleBrimArea, float(SCALED_EPSILON)), float(flow.scaled_spacing()) * 2.f);
|
||||
|
||||
//BBS: finally apply the plate offset which may very large
|
||||
auto plate_offset = print.get_plate_origin();
|
||||
Point scaled_plate_offset = Point(scaled(plate_offset.x()), scaled(plate_offset.y()));
|
||||
for (Polyline& one_loop : all_loops)
|
||||
one_loop.translate(scaled_plate_offset);
|
||||
|
||||
extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erBrim, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
|
||||
return brim;
|
||||
}
|
||||
|
|
@ -1589,14 +1600,14 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
|||
for (const ExPolygon& ex_poly : object->layers().front()->lslices)
|
||||
for (const PrintInstance& instance : object->instances()) {
|
||||
auto ex_poly_translated = ex_poly;
|
||||
ex_poly_translated.translate(instance.shift.x(), instance.shift.y());
|
||||
ex_poly_translated.translate(instance.shift_without_plate_offset());
|
||||
bbx.merge(get_extents(ex_poly_translated.contour));
|
||||
}
|
||||
if (!object->support_layers().empty())
|
||||
for (const Polygon& support_contour : object->support_layers().front()->support_fills.polygons_covered_by_spacing())
|
||||
for (const PrintInstance& instance : object->instances()) {
|
||||
auto ex_poly_translated = support_contour;
|
||||
ex_poly_translated.translate(instance.shift.x(), instance.shift.y());
|
||||
ex_poly_translated.translate(instance.shift_without_plate_offset());
|
||||
bbx.merge(get_extents(ex_poly_translated));
|
||||
}
|
||||
if (supportBrimAreaMap.find(printObjID) != supportBrimAreaMap.end()) {
|
||||
|
|
@ -1611,6 +1622,13 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
|||
}
|
||||
|
||||
islands_area = to_polygons(islands_area_ex);
|
||||
|
||||
// BBS: plate offset is applied
|
||||
const Vec3d plate_offset = print.get_plate_origin();
|
||||
Point plate_shift = Point(scaled(plate_offset.x()), scaled(plate_offset.y()));
|
||||
for (size_t iia = 0; iia < islands_area.size(); ++iia)
|
||||
islands_area[iia].translate(plate_shift);
|
||||
|
||||
for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) {
|
||||
if (!iter->second.empty()) {
|
||||
brimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area)));
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ set(lisbslic3r_sources
|
|||
pchheader.hpp
|
||||
AABBTreeIndirect.hpp
|
||||
AABBTreeLines.hpp
|
||||
AABBMesh.hpp
|
||||
AABBMesh.cpp
|
||||
AnyPtr.hpp
|
||||
BoundingBox.cpp
|
||||
BoundingBox.hpp
|
||||
BridgeDetector.cpp
|
||||
|
|
@ -96,6 +99,8 @@ set(lisbslic3r_sources
|
|||
Fill/FillRectilinear.hpp
|
||||
Flow.cpp
|
||||
Flow.hpp
|
||||
FlushVolCalc.cpp
|
||||
FlushVolCalc.hpp
|
||||
format.hpp
|
||||
Format/3mf.cpp
|
||||
Format/3mf.hpp
|
||||
|
|
@ -430,7 +435,7 @@ if (_opts)
|
|||
target_compile_options(libslic3r_cgal PRIVATE "${_opts_bad}")
|
||||
endif()
|
||||
|
||||
target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} libigl)
|
||||
target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} libigl mcut)
|
||||
|
||||
if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround
|
||||
target_compile_definitions(libslic3r_cgal PRIVATE CGAL_DO_NOT_USE_MPZF)
|
||||
|
|
@ -497,6 +502,7 @@ target_link_libraries(libslic3r
|
|||
ZLIB::ZLIB
|
||||
${OCCT_LIBS}
|
||||
Clipper2
|
||||
mcut
|
||||
)
|
||||
|
||||
if(NOT WIN32)
|
||||
|
|
|
|||
86
src/libslic3r/CSGMesh/CSGMesh.hpp
Normal file
86
src/libslic3r/CSGMesh/CSGMesh.hpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#ifndef CSGMESH_HPP
|
||||
#define CSGMESH_HPP
|
||||
|
||||
#include <libslic3r/AnyPtr.hpp>
|
||||
#include <admesh/stl.h>
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// A CSGPartT should be an object that can provide at least a mesh + trafo and an
|
||||
// associated csg operation. A collection of CSGPartT objects can then
|
||||
// be interpreted as one model and used in various contexts. It can be assembled
|
||||
// with CGAL or OpenVDB, rendered with OpenCSG or provided to a ray-tracer to
|
||||
// deal with various parts of it according to the supported CSG types...
|
||||
//
|
||||
// A few simple templated interface functions are provided here and a default
|
||||
// CSGPart class that implements the necessary means to be usable as a
|
||||
// CSGPartT object.
|
||||
|
||||
// Supported CSG operation types
|
||||
enum class CSGType { Union, Difference, Intersection };
|
||||
|
||||
// A CSG part can instruct the processing to push the sub-result until a new
|
||||
// csg part with a pop instruction appears. This can be used to implement
|
||||
// parentheses in a CSG expression represented by the collection of csg parts.
|
||||
// A CSG part can not contain another CSG collection, only a mesh, this is why
|
||||
// its easier to do this stacking instead of recursion in the data definition.
|
||||
// CSGStackOp::Continue means no stack operation required.
|
||||
// When a CSG part contains a Push instruction, it is expected that the CSG
|
||||
// operation it contains refers to the whole collection spanning to the nearest
|
||||
// part with a Pop instruction.
|
||||
// e.g.:
|
||||
// {
|
||||
// CUBE1: { mesh: cube, op: Union, stack op: Continue },
|
||||
// CUBE2: { mesh: cube, op: Difference, stack op: Push},
|
||||
// CUBE3: { mesh: cube, op: Union, stack op: Pop}
|
||||
// }
|
||||
// is a collection of csg parts representing the expression CUBE1 - (CUBE2 + CUBE3)
|
||||
enum class CSGStackOp { Push, Continue, Pop };
|
||||
|
||||
// Get the CSG operation of the part. Can be overriden for any type
|
||||
template<class CSGPartT> CSGType get_operation(const CSGPartT &part)
|
||||
{
|
||||
return part.operation;
|
||||
}
|
||||
|
||||
// Get the stack operation required by the CSG part.
|
||||
template<class CSGPartT> CSGStackOp get_stack_operation(const CSGPartT &part)
|
||||
{
|
||||
return part.stack_operation;
|
||||
}
|
||||
|
||||
// Get the mesh for the part. Can be overriden for any type
|
||||
template<class CSGPartT>
|
||||
const indexed_triangle_set *get_mesh(const CSGPartT &part)
|
||||
{
|
||||
return part.its_ptr.get();
|
||||
}
|
||||
|
||||
// Get the transformation associated with the mesh inside a CSGPartT object.
|
||||
// Can be overriden for any type.
|
||||
template<class CSGPartT>
|
||||
Transform3f get_transform(const CSGPartT &part)
|
||||
{
|
||||
return part.trafo;
|
||||
}
|
||||
|
||||
// Default implementation
|
||||
struct CSGPart {
|
||||
AnyPtr<const indexed_triangle_set> its_ptr;
|
||||
Transform3f trafo;
|
||||
CSGType operation;
|
||||
CSGStackOp stack_operation;
|
||||
|
||||
CSGPart(AnyPtr<const indexed_triangle_set> ptr = {},
|
||||
CSGType op = CSGType::Union,
|
||||
const Transform3f &tr = Transform3f::Identity())
|
||||
: its_ptr{std::move(ptr)}
|
||||
, operation{op}
|
||||
, stack_operation{CSGStackOp::Continue}
|
||||
, trafo{tr}
|
||||
{}
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // CSGMESH_HPP
|
||||
80
src/libslic3r/CSGMesh/CSGMeshCopy.hpp
Normal file
80
src/libslic3r/CSGMesh/CSGMeshCopy.hpp
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#ifndef CSGMESHCOPY_HPP
|
||||
#define CSGMESHCOPY_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// Copy a csg range but for the meshes, only copy the pointers. If the copy
|
||||
// is made from a CSGPart compatible object, and the pointer is a shared one,
|
||||
// it will be copied with reference counting.
|
||||
template<class It, class OutIt>
|
||||
void copy_csgrange_shallow(const Range<It> &csgrange, OutIt out)
|
||||
{
|
||||
for (const auto &part : csgrange) {
|
||||
CSGPart cpy{{},
|
||||
get_operation(part),
|
||||
get_transform(part)};
|
||||
|
||||
cpy.stack_operation = get_stack_operation(part);
|
||||
|
||||
if constexpr (std::is_convertible_v<decltype(part), const CSGPart&>) {
|
||||
if (auto shptr = part.its_ptr.get_shared_cpy()) {
|
||||
cpy.its_ptr = shptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cpy.its_ptr)
|
||||
cpy.its_ptr = AnyPtr<const indexed_triangle_set>{get_mesh(part)};
|
||||
|
||||
*out = std::move(cpy);
|
||||
++out;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the csg range, allocating new meshes
|
||||
template<class It, class OutIt>
|
||||
void copy_csgrange_deep(const Range<It> &csgrange, OutIt out)
|
||||
{
|
||||
for (const auto &part : csgrange) {
|
||||
|
||||
CSGPart cpy{{}, get_operation(part), get_transform(part)};
|
||||
|
||||
if (auto meshptr = get_mesh(part)) {
|
||||
cpy.its_ptr = std::make_unique<const indexed_triangle_set>(*meshptr);
|
||||
}
|
||||
|
||||
cpy.stack_operation = get_stack_operation(part);
|
||||
|
||||
*out = std::move(cpy);
|
||||
++out;
|
||||
}
|
||||
}
|
||||
|
||||
template<class ItA, class ItB>
|
||||
bool is_same(const Range<ItA> &A, const Range<ItB> &B)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
size_t s = A.size();
|
||||
|
||||
if (B.size() != s)
|
||||
ret = false;
|
||||
|
||||
size_t i = 0;
|
||||
auto itA = A.begin();
|
||||
auto itB = B.begin();
|
||||
for (; ret && i < s; ++itA, ++itB, ++i) {
|
||||
ret = ret &&
|
||||
get_mesh(*itA) == get_mesh(*itB) &&
|
||||
get_operation(*itA) == get_operation(*itB) &&
|
||||
get_stack_operation(*itA) == get_stack_operation(*itB) &&
|
||||
get_transform(*itA).isApprox(get_transform(*itB));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // CSGCOPY_HPP
|
||||
92
src/libslic3r/CSGMesh/ModelToCSGMesh.hpp
Normal file
92
src/libslic3r/CSGMesh/ModelToCSGMesh.hpp
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#ifndef MODELTOCSGMESH_HPP
|
||||
#define MODELTOCSGMESH_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
#include "libslic3r/MeshSplitImpl.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// Flags to select which parts to export from Model into a csg part collection.
|
||||
// These flags can be chained with the | operator
|
||||
enum ModelParts {
|
||||
mpartsPositive = 1, // Include positive parts
|
||||
mpartsNegative = 2, // Include negative parts
|
||||
mpartsDrillHoles = 4, // Include drill holes
|
||||
mpartsDoSplits = 8, // Split each splitable mesh and export as a union of csg parts
|
||||
};
|
||||
|
||||
template<class OutIt>
|
||||
bool model_to_csgmesh(const ModelObject &mo,
|
||||
const Transform3d &trafo, // Applies to all exported parts
|
||||
OutIt out, // Output iterator
|
||||
// values of ModelParts OR-ed
|
||||
int parts_to_include = mpartsPositive
|
||||
)
|
||||
{
|
||||
bool do_positives = parts_to_include & mpartsPositive;
|
||||
bool do_negatives = parts_to_include & mpartsNegative;
|
||||
bool do_drillholes = parts_to_include & mpartsDrillHoles;
|
||||
bool do_splits = parts_to_include & mpartsDoSplits;
|
||||
bool has_splitable_volume = false;
|
||||
|
||||
for (const ModelVolume *vol : mo.volumes) {
|
||||
if (vol && vol->mesh_ptr() &&
|
||||
((do_positives && vol->is_model_part()) ||
|
||||
(do_negatives && vol->is_negative_volume()))) {
|
||||
|
||||
if (do_splits && its_is_splittable(vol->mesh().its)) {
|
||||
CSGPart part_begin{{}, vol->is_model_part() ? CSGType::Union : CSGType::Difference};
|
||||
part_begin.stack_operation = CSGStackOp::Push;
|
||||
*out = std::move(part_begin);
|
||||
++out;
|
||||
|
||||
its_split(vol->mesh().its, SplitOutputFn{[&out, &vol, &trafo](indexed_triangle_set &&its) {
|
||||
if (its.empty())
|
||||
return;
|
||||
|
||||
CSGPart part{std::make_unique<indexed_triangle_set>(std::move(its)),
|
||||
CSGType::Union,
|
||||
(trafo * vol->get_matrix()).cast<float>()};
|
||||
|
||||
*out = std::move(part);
|
||||
++out;
|
||||
}});
|
||||
|
||||
CSGPart part_end{{}};
|
||||
part_end.stack_operation = CSGStackOp::Pop;
|
||||
*out = std::move(part_end);
|
||||
++out;
|
||||
has_splitable_volume = true;
|
||||
} else {
|
||||
CSGPart part{&(vol->mesh().its),
|
||||
vol->is_model_part() ? CSGType::Union : CSGType::Difference,
|
||||
(trafo * vol->get_matrix()).cast<float>()};
|
||||
|
||||
*out = std::move(part);
|
||||
++out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if (do_drillholes) {
|
||||
// sla::DrainHoles drainholes = sla::transformed_drainhole_points(mo, trafo);
|
||||
|
||||
// for (const sla::DrainHole &dhole : drainholes) {
|
||||
// CSGPart part{std::make_unique<const indexed_triangle_set>(
|
||||
// dhole.to_mesh()),
|
||||
// CSGType::Difference};
|
||||
|
||||
// *out = std::move(part);
|
||||
// ++out;
|
||||
// }
|
||||
//}
|
||||
|
||||
return has_splitable_volume;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // MODELTOCSGMESH_HPP
|
||||
375
src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp
Normal file
375
src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
#ifndef PERFORMCSGMESHBOOLEANS_HPP
|
||||
#define PERFORMCSGMESHBOOLEANS_HPP
|
||||
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
||||
//#include "libslic3r/Execution/ExecutionSeq.hpp"
|
||||
#include "libslic3r/MeshBoolean.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// This method can be overriden when a specific CSGPart type supports caching
|
||||
// of the voxel grid
|
||||
template<class CSGPartT>
|
||||
MeshBoolean::cgal::CGALMeshPtr get_cgalmesh(const CSGPartT &csgpart)
|
||||
{
|
||||
const indexed_triangle_set *its = csg::get_mesh(csgpart);
|
||||
indexed_triangle_set dummy;
|
||||
|
||||
if (!its)
|
||||
its = &dummy;
|
||||
|
||||
MeshBoolean::cgal::CGALMeshPtr ret;
|
||||
|
||||
indexed_triangle_set m = *its;
|
||||
its_transform(m, get_transform(csgpart), true);
|
||||
|
||||
try {
|
||||
ret = MeshBoolean::cgal::triangle_mesh_to_cgal(m);
|
||||
} catch (...) {
|
||||
// errors are ignored, simply return null
|
||||
ret = nullptr;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// This method can be overriden when a specific CSGPart type supports caching
|
||||
// of the voxel grid
|
||||
template<class CSGPartT>
|
||||
MeshBoolean::mcut::McutMeshPtr get_mcutmesh(const CSGPartT& csgpart)
|
||||
{
|
||||
const indexed_triangle_set* its = csg::get_mesh(csgpart);
|
||||
indexed_triangle_set dummy;
|
||||
|
||||
if (!its)
|
||||
its = &dummy;
|
||||
|
||||
MeshBoolean::mcut::McutMeshPtr ret;
|
||||
|
||||
indexed_triangle_set m = *its;
|
||||
its_transform(m, get_transform(csgpart), true);
|
||||
|
||||
try {
|
||||
ret = MeshBoolean::mcut::triangle_mesh_to_mcut(m);
|
||||
}
|
||||
catch (...) {
|
||||
// errors are ignored, simply return null
|
||||
ret = nullptr;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace detail_cgal {
|
||||
|
||||
using MeshBoolean::cgal::CGALMeshPtr;
|
||||
|
||||
inline void perform_csg(CSGType op, CGALMeshPtr &dst, CGALMeshPtr &src)
|
||||
{
|
||||
if (!dst && op == CSGType::Union && src) {
|
||||
dst = std::move(src);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dst || !src)
|
||||
return;
|
||||
|
||||
switch (op) {
|
||||
case CSGType::Union:
|
||||
MeshBoolean::cgal::plus(*dst, *src);
|
||||
break;
|
||||
case CSGType::Difference:
|
||||
MeshBoolean::cgal::minus(*dst, *src);
|
||||
break;
|
||||
case CSGType::Intersection:
|
||||
MeshBoolean::cgal::intersect(*dst, *src);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Ex, class It>
|
||||
std::vector<CGALMeshPtr> get_cgalptrs(Ex policy, const Range<It> &csgrange)
|
||||
{
|
||||
std::vector<CGALMeshPtr> ret(csgrange.size());
|
||||
execution::for_each(policy, size_t(0), csgrange.size(),
|
||||
[&csgrange, &ret](size_t i) {
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
auto &csgpart = *it;
|
||||
ret[i] = get_cgalmesh(csgpart);
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
namespace detail_mcut {
|
||||
|
||||
using MeshBoolean::mcut::McutMeshPtr;
|
||||
|
||||
inline void perform_csg(CSGType op, McutMeshPtr& dst, McutMeshPtr& src)
|
||||
{
|
||||
if (!dst && op == CSGType::Union && src) {
|
||||
dst = std::move(src);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dst || !src)
|
||||
return;
|
||||
|
||||
switch (op) {
|
||||
case CSGType::Union:
|
||||
MeshBoolean::mcut::do_boolean(*dst, *src,"UNION");
|
||||
break;
|
||||
case CSGType::Difference:
|
||||
MeshBoolean::mcut::do_boolean(*dst, *src,"A_NOT_B");
|
||||
break;
|
||||
case CSGType::Intersection:
|
||||
MeshBoolean::mcut::do_boolean(*dst, *src,"INTERSECTION");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Ex, class It>
|
||||
std::vector<McutMeshPtr> get_mcutptrs(Ex policy, const Range<It>& csgrange)
|
||||
{
|
||||
std::vector<McutMeshPtr> ret(csgrange.size());
|
||||
execution::for_each(policy, size_t(0), csgrange.size(),
|
||||
[&csgrange, &ret](size_t i) {
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
auto& csgpart = *it;
|
||||
ret[i] = get_mcutmesh(csgpart);
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace mcut_detail
|
||||
|
||||
// Process the sequence of CSG parts with CGAL.
|
||||
template<class It>
|
||||
void perform_csgmesh_booleans_cgal(MeshBoolean::cgal::CGALMeshPtr &cgalm,
|
||||
const Range<It> &csgrange)
|
||||
{
|
||||
using MeshBoolean::cgal::CGALMesh;
|
||||
using MeshBoolean::cgal::CGALMeshPtr;
|
||||
using namespace detail_cgal;
|
||||
|
||||
struct Frame {
|
||||
CSGType op; CGALMeshPtr cgalptr;
|
||||
explicit Frame(CSGType csgop = CSGType::Union)
|
||||
: op{ csgop }
|
||||
, cgalptr{ MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{}) }
|
||||
{}
|
||||
};
|
||||
|
||||
std::stack opstack{ std::vector<Frame>{} };
|
||||
|
||||
opstack.push(Frame{});
|
||||
|
||||
std::vector<CGALMeshPtr> cgalmeshes = get_cgalptrs(ex_tbb, csgrange);
|
||||
|
||||
size_t csgidx = 0;
|
||||
for (auto& csgpart : csgrange) {
|
||||
|
||||
auto op = get_operation(csgpart);
|
||||
CGALMeshPtr& cgalptr = cgalmeshes[csgidx++];
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
|
||||
opstack.push(Frame{ op });
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame* top = &opstack.top();
|
||||
|
||||
perform_csg(get_operation(csgpart), top->cgalptr, cgalptr);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
|
||||
CGALMeshPtr src = std::move(top->cgalptr);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
CGALMeshPtr& dst = opstack.top().cgalptr;
|
||||
perform_csg(popop, dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
cgalm = std::move(opstack.top().cgalptr);
|
||||
}
|
||||
|
||||
// Process the sequence of CSG parts with mcut.
|
||||
template<class It>
|
||||
void perform_csgmesh_booleans_mcut(MeshBoolean::mcut::McutMeshPtr& mcutm,
|
||||
const Range<It>& csgrange)
|
||||
{
|
||||
using MeshBoolean::mcut::McutMesh;
|
||||
using MeshBoolean::mcut::McutMeshPtr;
|
||||
using namespace detail_mcut;
|
||||
|
||||
struct Frame {
|
||||
CSGType op; McutMeshPtr mcutptr;
|
||||
explicit Frame(CSGType csgop = CSGType::Union)
|
||||
: op{ csgop }
|
||||
, mcutptr{ MeshBoolean::mcut::triangle_mesh_to_mcut(indexed_triangle_set{}) }
|
||||
{}
|
||||
};
|
||||
|
||||
std::stack opstack{ std::vector<Frame>{} };
|
||||
|
||||
opstack.push(Frame{});
|
||||
|
||||
std::vector<McutMeshPtr> McutMeshes = get_mcutptrs(ex_tbb, csgrange);
|
||||
|
||||
size_t csgidx = 0;
|
||||
for (auto& csgpart : csgrange) {
|
||||
|
||||
auto op = get_operation(csgpart);
|
||||
McutMeshPtr& mcutptr = McutMeshes[csgidx++];
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
|
||||
opstack.push(Frame{ op });
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame* top = &opstack.top();
|
||||
|
||||
perform_csg(get_operation(csgpart), top->mcutptr, mcutptr);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
|
||||
McutMeshPtr src = std::move(top->mcutptr);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
McutMeshPtr& dst = opstack.top().mcutptr;
|
||||
perform_csg(popop, dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
mcutm = std::move(opstack.top().mcutptr);
|
||||
|
||||
}
|
||||
|
||||
|
||||
template<class It, class Visitor>
|
||||
It check_csgmesh_booleans(const Range<It> &csgrange, Visitor &&vfn)
|
||||
{
|
||||
using namespace detail_cgal;
|
||||
|
||||
std::vector<CGALMeshPtr> cgalmeshes(csgrange.size());
|
||||
auto check_part = [&csgrange, &cgalmeshes](size_t i)
|
||||
{
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
auto &csgpart = *it;
|
||||
auto m = get_cgalmesh(csgpart);
|
||||
|
||||
// mesh can be nullptr if this is a stack push or pull
|
||||
if (!get_mesh(csgpart) && get_stack_operation(csgpart) != CSGStackOp::Continue) {
|
||||
cgalmeshes[i] = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!m || MeshBoolean::cgal::empty(*m))
|
||||
return;
|
||||
|
||||
if (!MeshBoolean::cgal::does_bound_a_volume(*m))
|
||||
return;
|
||||
|
||||
if (MeshBoolean::cgal::does_self_intersect(*m))
|
||||
return;
|
||||
}
|
||||
catch (...) { return; }
|
||||
|
||||
cgalmeshes[i] = std::move(m);
|
||||
};
|
||||
execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part);
|
||||
|
||||
It ret = csgrange.end();
|
||||
for (size_t i = 0; i < csgrange.size(); ++i) {
|
||||
if (!cgalmeshes[i]) {
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
vfn(it);
|
||||
|
||||
if (ret == csgrange.end())
|
||||
ret = it;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class It>
|
||||
It check_csgmesh_booleans(const Range<It> &csgrange, bool use_mcut=false)
|
||||
{
|
||||
if(!use_mcut)
|
||||
return check_csgmesh_booleans(csgrange, [](auto &) {});
|
||||
else {
|
||||
using namespace detail_mcut;
|
||||
|
||||
std::vector<McutMeshPtr> McutMeshes(csgrange.size());
|
||||
auto check_part = [&csgrange, &McutMeshes](size_t i) {
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
auto& csgpart = *it;
|
||||
auto m = get_mcutmesh(csgpart);
|
||||
|
||||
// mesh can be nullptr if this is a stack push or pull
|
||||
if (!get_mesh(csgpart) && get_stack_operation(csgpart) != CSGStackOp::Continue) {
|
||||
McutMeshes[i] = MeshBoolean::mcut::triangle_mesh_to_mcut(indexed_triangle_set{});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!m || MeshBoolean::mcut::empty(*m))
|
||||
return;
|
||||
}
|
||||
catch (...) { return; }
|
||||
|
||||
McutMeshes[i] = std::move(m);
|
||||
};
|
||||
execution::for_each(ex_tbb, size_t(0), csgrange.size(), check_part);
|
||||
|
||||
It ret = csgrange.end();
|
||||
for (size_t i = 0; i < csgrange.size(); ++i) {
|
||||
if (!McutMeshes[i]) {
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, i);
|
||||
|
||||
if (ret == csgrange.end())
|
||||
ret = it;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
template<class It>
|
||||
MeshBoolean::cgal::CGALMeshPtr perform_csgmesh_booleans(const Range<It> &csgparts)
|
||||
{
|
||||
auto ret = MeshBoolean::cgal::triangle_mesh_to_cgal(indexed_triangle_set{});
|
||||
if (ret)
|
||||
perform_csgmesh_booleans_cgal(ret, csgparts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class It>
|
||||
MeshBoolean::mcut::McutMeshPtr perform_csgmesh_booleans_mcut(const Range<It>& csgparts)
|
||||
{
|
||||
auto ret = MeshBoolean::mcut::triangle_mesh_to_mcut(indexed_triangle_set{});
|
||||
if (ret)
|
||||
perform_csgmesh_booleans_mcut(ret, csgparts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace csg
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // PERFORMCSGMESHBOOLEANS_HPP
|
||||
131
src/libslic3r/CSGMesh/SliceCSGMesh.hpp
Normal file
131
src/libslic3r/CSGMesh/SliceCSGMesh.hpp
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
#ifndef SLICECSGMESH_HPP
|
||||
#define SLICECSGMESH_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include "libslic3r/TriangleMeshSlicer.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline void merge_slices(csg::CSGType op, size_t i,
|
||||
std::vector<ExPolygons> &target,
|
||||
std::vector<ExPolygons> &source)
|
||||
{
|
||||
switch(op) {
|
||||
case CSGType::Union:
|
||||
for (ExPolygon &expoly : source[i])
|
||||
target[i].emplace_back(std::move(expoly));
|
||||
break;
|
||||
case CSGType::Difference:
|
||||
target[i] = diff_ex(target[i], source[i]);
|
||||
break;
|
||||
case CSGType::Intersection:
|
||||
target[i] = intersection_ex(target[i], source[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline void collect_nonempty_indices(csg::CSGType op,
|
||||
const std::vector<float> &slicegrid,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
std::vector<size_t> &indices)
|
||||
{
|
||||
indices.clear();
|
||||
for (size_t i = 0; i < slicegrid.size(); ++i) {
|
||||
if (op == CSGType::Intersection || !slices[i].empty())
|
||||
indices.emplace_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template<class ItCSG>
|
||||
std::vector<ExPolygons> slice_csgmesh_ex(
|
||||
const Range<ItCSG> &csgrange,
|
||||
const std::vector<float> &slicegrid,
|
||||
const MeshSlicingParamsEx ¶ms,
|
||||
const std::function<void()> &throw_on_cancel = [] {})
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
struct Frame { CSGType op; std::vector<ExPolygons> slices; };
|
||||
|
||||
std::stack opstack{std::vector<Frame>{}};
|
||||
|
||||
MeshSlicingParamsEx params_cpy = params;
|
||||
auto trafo = params.trafo;
|
||||
auto nonempty_indices = reserve_vector<size_t>(slicegrid.size());
|
||||
|
||||
opstack.push({CSGType::Union, std::vector<ExPolygons>(slicegrid.size())});
|
||||
|
||||
for (const auto &csgpart : csgrange) {
|
||||
const indexed_triangle_set *its = csg::get_mesh(csgpart);
|
||||
|
||||
auto op = get_operation(csgpart);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
|
||||
opstack.push({op, std::vector<ExPolygons>(slicegrid.size())});
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame *top = &opstack.top();
|
||||
|
||||
if (its) {
|
||||
params_cpy.trafo = trafo * csg::get_transform(csgpart).template cast<double>();
|
||||
std::vector<ExPolygons> slices = slice_mesh_ex(*its,
|
||||
slicegrid, params_cpy,
|
||||
throw_on_cancel);
|
||||
|
||||
assert(slices.size() == slicegrid.size());
|
||||
|
||||
collect_nonempty_indices(op, slicegrid, slices, nonempty_indices);
|
||||
|
||||
execution::for_each(
|
||||
ex_tbb, nonempty_indices.begin(), nonempty_indices.end(),
|
||||
[op, &slices, &top](size_t i) {
|
||||
merge_slices(op, i, top->slices, slices);
|
||||
}, execution::max_concurrency(ex_tbb));
|
||||
}
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
|
||||
std::vector<ExPolygons> popslices = std::move(top->slices);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
std::vector<ExPolygons> &prev_slices = opstack.top().slices;
|
||||
|
||||
collect_nonempty_indices(popop, slicegrid, popslices, nonempty_indices);
|
||||
|
||||
execution::for_each(
|
||||
ex_tbb, nonempty_indices.begin(), nonempty_indices.end(),
|
||||
[&popslices, &prev_slices, popop](size_t i) {
|
||||
merge_slices(popop, i, prev_slices, popslices);
|
||||
}, execution::max_concurrency(ex_tbb));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> ret = std::move(opstack.top().slices);
|
||||
|
||||
// TODO: verify if this part can be omitted or not.
|
||||
execution::for_each(ex_tbb, ret.begin(), ret.end(), [](ExPolygons &slice) {
|
||||
auto it = std::remove_if(slice.begin(), slice.end(), [](const ExPolygon &p){
|
||||
return p.area() < double(SCALED_EPSILON) * double(SCALED_EPSILON);
|
||||
});
|
||||
|
||||
// Hopefully, ExPolygons are moved, not copied to new positions
|
||||
// and that is cheap for expolygons
|
||||
slice.erase(it, slice.end());
|
||||
slice = union_ex(slice);
|
||||
}, execution::max_concurrency(ex_tbb));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // SLICECSGMESH_HPP
|
||||
95
src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp
Normal file
95
src/libslic3r/CSGMesh/TriangleMeshAdapter.hpp
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
#ifndef TRIANGLEMESHADAPTER_HPP
|
||||
#define TRIANGLEMESHADAPTER_HPP
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
// Provide default overloads for indexed_triangle_set to be usable as a plain
|
||||
// CSGPart with an implicit union operation
|
||||
|
||||
inline CSGType get_operation(const indexed_triangle_set &part)
|
||||
{
|
||||
return CSGType::Union;
|
||||
}
|
||||
|
||||
inline CSGStackOp get_stack_operation(const indexed_triangle_set &part)
|
||||
{
|
||||
return CSGStackOp::Continue;
|
||||
}
|
||||
|
||||
inline const indexed_triangle_set * get_mesh(const indexed_triangle_set &part)
|
||||
{
|
||||
return ∂
|
||||
}
|
||||
|
||||
inline Transform3f get_transform(const indexed_triangle_set &part)
|
||||
{
|
||||
return Transform3f::Identity();
|
||||
}
|
||||
|
||||
inline CSGType get_operation(const indexed_triangle_set *const part)
|
||||
{
|
||||
return CSGType::Union;
|
||||
}
|
||||
|
||||
inline CSGStackOp get_stack_operation(const indexed_triangle_set *const part)
|
||||
{
|
||||
return CSGStackOp::Continue;
|
||||
}
|
||||
|
||||
inline const indexed_triangle_set * get_mesh(const indexed_triangle_set *const part)
|
||||
{
|
||||
return part;
|
||||
}
|
||||
|
||||
inline Transform3f get_transform(const indexed_triangle_set *const part)
|
||||
{
|
||||
return Transform3f::Identity();
|
||||
}
|
||||
|
||||
inline CSGType get_operation(const TriangleMesh &part)
|
||||
{
|
||||
return CSGType::Union;
|
||||
}
|
||||
|
||||
inline CSGStackOp get_stack_operation(const TriangleMesh &part)
|
||||
{
|
||||
return CSGStackOp::Continue;
|
||||
}
|
||||
|
||||
inline const indexed_triangle_set * get_mesh(const TriangleMesh &part)
|
||||
{
|
||||
return &part.its;
|
||||
}
|
||||
|
||||
inline Transform3f get_transform(const TriangleMesh &part)
|
||||
{
|
||||
return Transform3f::Identity();
|
||||
}
|
||||
|
||||
inline CSGType get_operation(const TriangleMesh * const part)
|
||||
{
|
||||
return CSGType::Union;
|
||||
}
|
||||
|
||||
inline CSGStackOp get_stack_operation(const TriangleMesh * const part)
|
||||
{
|
||||
return CSGStackOp::Continue;
|
||||
}
|
||||
|
||||
inline const indexed_triangle_set * get_mesh(const TriangleMesh * const part)
|
||||
{
|
||||
return &part->its;
|
||||
}
|
||||
|
||||
inline Transform3f get_transform(const TriangleMesh * const part)
|
||||
{
|
||||
return Transform3f::Identity();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // TRIANGLEMESHADAPTER_HPP
|
||||
116
src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp
Normal file
116
src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#ifndef VOXELIZECSGMESH_HPP
|
||||
#define VOXELIZECSGMESH_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <stack>
|
||||
|
||||
#include "CSGMesh.hpp"
|
||||
#include "libslic3r/OpenVDBUtils.hpp"
|
||||
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
||||
|
||||
namespace Slic3r { namespace csg {
|
||||
|
||||
using VoxelizeParams = MeshToGridParams;
|
||||
|
||||
// This method can be overriden when a specific CSGPart type supports caching
|
||||
// of the voxel grid
|
||||
template<class CSGPartT>
|
||||
VoxelGridPtr get_voxelgrid(const CSGPartT &csgpart, VoxelizeParams params)
|
||||
{
|
||||
const indexed_triangle_set *its = csg::get_mesh(csgpart);
|
||||
VoxelGridPtr ret;
|
||||
|
||||
params.trafo(params.trafo() * csg::get_transform(csgpart));
|
||||
|
||||
if (its)
|
||||
ret = mesh_to_grid(*its, params);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline void perform_csg(CSGType op, VoxelGridPtr &dst, VoxelGridPtr &src)
|
||||
{
|
||||
if (!dst || !src)
|
||||
return;
|
||||
|
||||
switch (op) {
|
||||
case CSGType::Union:
|
||||
if (is_grid_empty(*dst) && !is_grid_empty(*src))
|
||||
dst = clone(*src);
|
||||
else
|
||||
grid_union(*dst, *src);
|
||||
|
||||
break;
|
||||
case CSGType::Difference:
|
||||
grid_difference(*dst, *src);
|
||||
break;
|
||||
case CSGType::Intersection:
|
||||
grid_intersection(*dst, *src);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template<class It>
|
||||
VoxelGridPtr voxelize_csgmesh(const Range<It> &csgrange,
|
||||
const VoxelizeParams ¶ms = {})
|
||||
{
|
||||
using namespace detail;
|
||||
|
||||
VoxelGridPtr ret;
|
||||
|
||||
std::vector<VoxelGridPtr> grids (csgrange.size());
|
||||
|
||||
execution::for_each(ex_tbb, size_t(0), csgrange.size(), [&](size_t csgidx) {
|
||||
if (params.statusfn() && params.statusfn()(-1))
|
||||
return;
|
||||
|
||||
auto it = csgrange.begin();
|
||||
std::advance(it, csgidx);
|
||||
auto &csgpart = *it;
|
||||
grids[csgidx] = get_voxelgrid(csgpart, params);
|
||||
}, execution::max_concurrency(ex_tbb));
|
||||
|
||||
size_t csgidx = 0;
|
||||
struct Frame { CSGType op = CSGType::Union; VoxelGridPtr grid; };
|
||||
std::stack opstack{std::vector<Frame>{}};
|
||||
|
||||
opstack.push({CSGType::Union, mesh_to_grid({}, params)});
|
||||
|
||||
for (auto &csgpart : csgrange) {
|
||||
if (params.statusfn() && params.statusfn()(-1))
|
||||
break;
|
||||
|
||||
auto &partgrid = grids[csgidx++];
|
||||
|
||||
auto op = get_operation(csgpart);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
|
||||
opstack.push({op, mesh_to_grid({}, params)});
|
||||
op = CSGType::Union;
|
||||
}
|
||||
|
||||
Frame *top = &opstack.top();
|
||||
|
||||
perform_csg(get_operation(csgpart), top->grid, partgrid);
|
||||
|
||||
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
|
||||
VoxelGridPtr popgrid = std::move(top->grid);
|
||||
auto popop = opstack.top().op;
|
||||
opstack.pop();
|
||||
VoxelGridPtr &grid = opstack.top().grid;
|
||||
perform_csg(popop, grid, popgrid);
|
||||
}
|
||||
}
|
||||
|
||||
ret = std::move(opstack.top().grid);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::csg
|
||||
|
||||
#endif // VOXELIZECSGMESH_HPP
|
||||
|
|
@ -52,7 +52,8 @@ enum ExtrusionLoopRole {
|
|||
inline bool is_perimeter(ExtrusionRole role)
|
||||
{
|
||||
return role == erPerimeter
|
||||
|| role == erExternalPerimeter;
|
||||
|| role == erExternalPerimeter
|
||||
|| role == erOverhangPerimeter;
|
||||
}
|
||||
|
||||
inline bool is_internal_perimeter(ExtrusionRole role)
|
||||
|
|
@ -101,6 +102,8 @@ public:
|
|||
virtual bool is_collection() const { return false; }
|
||||
virtual bool is_loop() const { return false; }
|
||||
virtual bool can_reverse() const { return true; }
|
||||
virtual bool can_sort() const { return true; }//BBS: only used in ExtrusionEntityCollection
|
||||
virtual void set_reverse() {}
|
||||
virtual ExtrusionEntity* clone() const = 0;
|
||||
// Create a new object, initialize it with this object using the move semantics.
|
||||
virtual ExtrusionEntity* clone_move() = 0;
|
||||
|
|
@ -151,12 +154,53 @@ public:
|
|||
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(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_can_reverse(rhs.m_can_reverse)
|
||||
, 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_can_reverse(rhs.m_can_reverse)
|
||||
, 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_can_reverse(rhs.m_can_reverse)
|
||||
, 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_can_reverse(rhs.m_can_reverse)
|
||||
, m_role(rhs.m_role)
|
||||
, m_no_extrusion(rhs.m_no_extrusion)
|
||||
{}
|
||||
|
||||
ExtrusionPath& operator=(const ExtrusionPath& rhs) {
|
||||
m_can_reverse = rhs.m_can_reverse;
|
||||
m_role = rhs.m_role;
|
||||
m_no_extrusion = rhs.m_no_extrusion;
|
||||
this->mm3_per_mm = rhs.mm3_per_mm;
|
||||
|
|
@ -168,6 +212,7 @@ public:
|
|||
return *this;
|
||||
}
|
||||
ExtrusionPath& operator=(ExtrusionPath&& rhs) {
|
||||
m_can_reverse = rhs.m_can_reverse;
|
||||
m_role = rhs.m_role;
|
||||
m_no_extrusion = rhs.m_no_extrusion;
|
||||
this->mm3_per_mm = rhs.mm3_per_mm;
|
||||
|
|
@ -238,10 +283,12 @@ public:
|
|||
bool is_force_no_extrusion() const { return m_no_extrusion; }
|
||||
void set_force_no_extrusion(bool no_extrusion) { m_no_extrusion = no_extrusion; }
|
||||
void set_extrusion_role(ExtrusionRole extrusion_role) { m_role = extrusion_role; }
|
||||
void set_reverse() override { m_can_reverse = false; }
|
||||
bool can_reverse() const override { return m_can_reverse; }
|
||||
|
||||
private:
|
||||
void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const;
|
||||
|
||||
bool m_can_reverse = true;
|
||||
ExtrusionRole m_role;
|
||||
//BBS
|
||||
bool m_no_extrusion = false;
|
||||
|
|
@ -256,16 +303,27 @@ public:
|
|||
ExtrusionPaths paths;
|
||||
|
||||
ExtrusionMultiPath() {}
|
||||
ExtrusionMultiPath(const ExtrusionMultiPath &rhs) : paths(rhs.paths) {}
|
||||
ExtrusionMultiPath(ExtrusionMultiPath &&rhs) : paths(std::move(rhs.paths)) {}
|
||||
ExtrusionMultiPath(const ExtrusionMultiPath &rhs) : paths(rhs.paths), m_can_reverse(rhs.m_can_reverse) {}
|
||||
ExtrusionMultiPath(ExtrusionMultiPath &&rhs) : paths(std::move(rhs.paths)), m_can_reverse(rhs.m_can_reverse) {}
|
||||
ExtrusionMultiPath(const ExtrusionPaths &paths) : paths(paths) {}
|
||||
ExtrusionMultiPath(const ExtrusionPath &path) { this->paths.push_back(path); }
|
||||
ExtrusionMultiPath(const ExtrusionPath &path) {this->paths.push_back(path); m_can_reverse = path.can_reverse(); }
|
||||
|
||||
ExtrusionMultiPath& operator=(const ExtrusionMultiPath &rhs) { this->paths = rhs.paths; return *this; }
|
||||
ExtrusionMultiPath& operator=(ExtrusionMultiPath &&rhs) { this->paths = std::move(rhs.paths); return *this; }
|
||||
ExtrusionMultiPath &operator=(const ExtrusionMultiPath &rhs)
|
||||
{
|
||||
this->paths = rhs.paths;
|
||||
m_can_reverse = rhs.m_can_reverse;
|
||||
return *this;
|
||||
}
|
||||
ExtrusionMultiPath &operator=(ExtrusionMultiPath &&rhs)
|
||||
{
|
||||
this->paths = std::move(rhs.paths);
|
||||
m_can_reverse = rhs.m_can_reverse;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool is_loop() const override { return false; }
|
||||
bool can_reverse() const override { return true; }
|
||||
bool can_reverse() const override { return m_can_reverse; }
|
||||
void set_reverse() override { m_can_reverse = false; }
|
||||
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)); }
|
||||
|
|
@ -298,6 +356,9 @@ public:
|
|||
append(dst, p.polyline.points);
|
||||
}
|
||||
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
|
||||
|
||||
private:
|
||||
bool m_can_reverse = true;
|
||||
};
|
||||
|
||||
// Single continuous extrusion loop, possibly with varying extrusion thickness, extrusion height or bridging / non bridging.
|
||||
|
|
|
|||
|
|
@ -32,12 +32,17 @@ public:
|
|||
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) {}
|
||||
ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort), is_reverse(other.is_reverse) { this->append(other.entities); }
|
||||
ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), no_sort(other.no_sort), is_reverse(other.is_reverse) {}
|
||||
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; }
|
||||
{
|
||||
this->entities = std::move(other.entities);
|
||||
this->no_sort = other.no_sort;
|
||||
is_reverse = other.is_reverse;
|
||||
return *this;
|
||||
}
|
||||
~ExtrusionEntityCollection() { clear(); }
|
||||
explicit operator ExtrusionPaths() const;
|
||||
|
||||
|
|
@ -50,7 +55,15 @@ public:
|
|||
}
|
||||
return out;
|
||||
}
|
||||
bool can_reverse() const override { return !this->no_sort; }
|
||||
bool can_sort() const override { return !this->no_sort; }
|
||||
bool can_reverse() const override
|
||||
{
|
||||
if (this->no_sort)
|
||||
return false;
|
||||
else
|
||||
return is_reverse;
|
||||
}
|
||||
void set_reverse() override { is_reverse = false; }
|
||||
bool empty() const { return this->entities.empty(); }
|
||||
void clear();
|
||||
void swap (ExtrusionEntityCollection &c);
|
||||
|
|
@ -126,6 +139,9 @@ public:
|
|||
throw Slic3r::RuntimeError("Calling length() on a ExtrusionEntityCollection");
|
||||
return 0.;
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_reverse{true};
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -157,7 +157,9 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
|||
if (surface.is_solid()) {
|
||||
params.density = 100.f;
|
||||
//FIXME for non-thick bridges, shall we allow a bottom surface pattern?
|
||||
if (surface.is_external() && ! is_bridge) {
|
||||
if (surface.is_solid_infill())
|
||||
params.pattern = region_config.internal_solid_infill_pattern.value;
|
||||
else if (surface.is_external() && ! is_bridge) {
|
||||
if(surface.is_top())
|
||||
params.pattern = region_config.top_surface_pattern.value;
|
||||
else
|
||||
|
|
@ -199,7 +201,6 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
|||
// 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:
|
||||
// Anchor a sparse infill to inner perimeters with the following anchor length:
|
||||
params.anchor_length = float(region_config.infill_anchor);
|
||||
if (region_config.infill_anchor.percent)
|
||||
params.anchor_length = float(params.anchor_length * 0.01 * params.spacing);
|
||||
|
|
@ -492,8 +493,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
|||
params.using_internal_flow = using_internal_flow;
|
||||
params.no_extrusion_overlap = surface_fill.params.overlap;
|
||||
params.with_loop = surface_fill.params.with_loop;
|
||||
|
||||
params.config = &layerm->region().config();
|
||||
if (surface_fill.params.pattern == ipGrid)
|
||||
params.can_reverse = false;
|
||||
for (ExPolygon& expoly : surface_fill.expolygons) {
|
||||
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
|
||||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
||||
|
|
@ -538,6 +540,7 @@ void Layer::make_ironing()
|
|||
|
||||
// First classify regions based on the extruder used.
|
||||
struct IroningParams {
|
||||
InfillPattern pattern;
|
||||
int extruder = -1;
|
||||
bool just_infill = false;
|
||||
// Spacing of the ironing lines, also to calculate the extrusion flow from.
|
||||
|
|
@ -577,8 +580,7 @@ void Layer::make_ironing()
|
|||
|
||||
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;
|
||||
this->line_spacing == rhs.line_spacing && this->height == rhs.height && this->speed == rhs.speed && this->angle == rhs.angle && this->pattern == rhs.pattern;
|
||||
}
|
||||
|
||||
LayerRegion *layerm = nullptr;
|
||||
|
|
@ -625,24 +627,36 @@ void Layer::make_ironing()
|
|||
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.pattern = config.ironing_pattern;
|
||||
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;
|
||||
|
||||
InfillPattern f_pattern = ipRectilinear;
|
||||
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(f_pattern));
|
||||
f->set_bounding_box(this->object()->bounding_box());
|
||||
f->layer_id = this->id();
|
||||
f->z = this->print_z;
|
||||
f->overlap = 0;
|
||||
for (size_t i = 0; i < by_extruder.size();) {
|
||||
// Find span of regions equivalent to the ironing operation.
|
||||
IroningParams &ironing_params = by_extruder[i];
|
||||
// Create the filler object.
|
||||
if( f_pattern != ironing_params.pattern )
|
||||
{
|
||||
f_pattern = ironing_params.pattern;
|
||||
f = std::unique_ptr<Fill>(Fill::new_from_type(f_pattern));
|
||||
f->set_bounding_box(this->object()->bounding_box());
|
||||
f->layer_id = this->id();
|
||||
f->z = this->print_z;
|
||||
f->overlap = 0;
|
||||
}
|
||||
|
||||
size_t j = i;
|
||||
for (++ j; j < by_extruder.size() && ironing_params == by_extruder[j]; ++ j) ;
|
||||
|
||||
|
|
@ -704,10 +718,10 @@ void Layer::make_ironing()
|
|||
}
|
||||
|
||||
// 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;
|
||||
f->spacing = ironing_params.line_spacing;
|
||||
f->angle = float(ironing_params.angle + 0.25 * M_PI);
|
||||
f->link_max_length = (coord_t) scale_(3. * f->spacing);
|
||||
double extrusion_height = ironing_params.height * f->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());
|
||||
|
|
@ -715,7 +729,7 @@ void Layer::make_ironing()
|
|||
surface_fill.expolygon = std::move(expoly);
|
||||
Polylines polylines;
|
||||
try {
|
||||
polylines = fill.fill_surface(&surface_fill, fill_params);
|
||||
polylines = f->fill_surface(&surface_fill, fill_params);
|
||||
} catch (InfillFailedException &) {
|
||||
}
|
||||
if (! polylines.empty()) {
|
||||
|
|
|
|||
|
|
@ -192,6 +192,7 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
|
|||
out.push_back(eec = new ExtrusionEntityCollection());
|
||||
// Only concentric fills are not sorted.
|
||||
eec->no_sort = this->no_sort();
|
||||
size_t idx = eec->entities.size();
|
||||
if (params.use_arachne) {
|
||||
Flow new_flow = params.flow.with_spacing(float(this->spacing));
|
||||
variable_width(thick_polylines, params.extrusion_role, new_flow, eec->entities);
|
||||
|
|
@ -203,6 +204,10 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
|
|||
params.extrusion_role,
|
||||
flow_mm3_per_mm, float(flow_width), params.flow.height());
|
||||
}
|
||||
if (!params.can_reverse) {
|
||||
for (size_t i = idx; i < eec->entities.size(); i++)
|
||||
eec->entities[i]->set_reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ struct FillParams
|
|||
float no_extrusion_overlap{ 0.0 };
|
||||
const PrintRegionConfig* config{ nullptr };
|
||||
bool dont_sort{ false }; // do not sort the lines, just simply connect them
|
||||
bool can_reverse{true};
|
||||
};
|
||||
static_assert(IsTriviallyCopyable<FillParams>::value, "FillParams class is not POD (and it should be - see constructor).");
|
||||
|
||||
|
|
|
|||
|
|
@ -2994,6 +2994,10 @@ Polylines FillGrid::fill_surface(const Surface *surface, const FillParams ¶m
|
|||
{ { 0.f, 0.f }, { float(M_PI / 2.), 0.f } },
|
||||
polylines_out))
|
||||
BOOST_LOG_TRIVIAL(error) << "FillGrid::fill_surface() failed to fill a region.";
|
||||
|
||||
if (this->layer_id % 2 == 1)
|
||||
for (int i = 0; i < polylines_out.size(); i++)
|
||||
std::reverse(polylines_out[i].begin(), polylines_out[i].end());
|
||||
return polylines_out;
|
||||
}
|
||||
|
||||
|
|
|
|||
98
src/libslic3r/FlushVolCalc.cpp
Normal file
98
src/libslic3r/FlushVolCalc.cpp
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#include <cmath>
|
||||
#include <assert.h>
|
||||
#include "slic3r/Utils/ColorSpaceConvert.hpp"
|
||||
|
||||
#include "FlushVolCalc.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
const int g_min_flush_volume_from_support = 420.f;
|
||||
const int g_flush_volume_to_support = 230;
|
||||
|
||||
const int g_max_flush_volume = 800;
|
||||
|
||||
static float to_radians(float degree)
|
||||
{
|
||||
return degree / 180.f * M_PI;
|
||||
}
|
||||
|
||||
|
||||
static float get_luminance(float r, float g, float b)
|
||||
{
|
||||
return r * 0.3 + g * 0.59 + b * 0.11;
|
||||
}
|
||||
|
||||
static float calc_triangle_3rd_edge(float edge_a, float edge_b, float degree_ab)
|
||||
{
|
||||
return std::sqrt(edge_a * edge_a + edge_b * edge_b - 2 * edge_a * edge_b * std::cos(to_radians(degree_ab)));
|
||||
}
|
||||
|
||||
static float DeltaHS_BBS(float h1, float s1, float v1, float h2, float s2, float v2)
|
||||
{
|
||||
float h1_rad = to_radians(h1);
|
||||
float h2_rad = to_radians(h2);
|
||||
|
||||
float dx = std::cos(h1_rad) * s1 * v1 - cos(h2_rad) * s2 * v2;
|
||||
float dy = std::sin(h1_rad) * s1 * v1 - sin(h2_rad) * s2 * v2;
|
||||
float dxy = std::sqrt(dx * dx + dy * dy);
|
||||
return std::min(1.2f, dxy);
|
||||
}
|
||||
|
||||
FlushVolCalculator::FlushVolCalculator(int min, int max, float multiplier)
|
||||
:m_min_flush_vol(min), m_max_flush_vol(max), m_multiplier(multiplier)
|
||||
{
|
||||
}
|
||||
|
||||
int FlushVolCalculator::calc_flush_vol(unsigned char src_a, unsigned char src_r, unsigned char src_g, unsigned char src_b,
|
||||
unsigned char dst_a, unsigned char dst_r, unsigned char dst_g, unsigned char dst_b)
|
||||
{
|
||||
// BBS: Transparent materials are treated as white materials
|
||||
if (src_a == 0) {
|
||||
src_r = src_g = src_b = 255;
|
||||
}
|
||||
if (dst_a == 0) {
|
||||
dst_r = dst_g = dst_b = 255;
|
||||
}
|
||||
|
||||
float src_r_f, src_g_f, src_b_f, dst_r_f, dst_g_f, dst_b_f;
|
||||
float from_hsv_h, from_hsv_s, from_hsv_v;
|
||||
float to_hsv_h, to_hsv_s, to_hsv_v;
|
||||
|
||||
src_r_f = (float)src_r / 255.f;
|
||||
src_g_f = (float)src_g / 255.f;
|
||||
src_b_f = (float)src_b / 255.f;
|
||||
dst_r_f = (float)dst_r / 255.f;
|
||||
dst_g_f = (float)dst_g / 255.f;
|
||||
dst_b_f = (float)dst_b / 255.f;
|
||||
|
||||
// Calculate color distance in HSV color space
|
||||
RGB2HSV(src_r_f, src_g_f,src_b_f, &from_hsv_h, &from_hsv_s, &from_hsv_v);
|
||||
RGB2HSV(dst_r_f, dst_g_f, dst_b_f, &to_hsv_h, &to_hsv_s, &to_hsv_v);
|
||||
float hs_dist = DeltaHS_BBS(from_hsv_h, from_hsv_s, from_hsv_v, to_hsv_h, to_hsv_s, to_hsv_v);
|
||||
|
||||
// 1. Color difference is more obvious if the dest color has high luminance
|
||||
// 2. Color difference is more obvious if the source color has low luminance
|
||||
float from_lumi = get_luminance(src_r_f, src_g_f, src_b_f);
|
||||
float to_lumi = get_luminance(dst_r_f, dst_g_f, dst_b_f);
|
||||
float lumi_flush = 0.f;
|
||||
if (to_lumi >= from_lumi) {
|
||||
lumi_flush = std::pow(to_lumi - from_lumi, 0.7f) * 560.f;
|
||||
}
|
||||
else {
|
||||
lumi_flush = (from_lumi - to_lumi) * 80.f;
|
||||
|
||||
float inter_hsv_v = 0.67 * to_hsv_v + 0.33 * from_hsv_v;
|
||||
hs_dist = std::min(inter_hsv_v, hs_dist);
|
||||
}
|
||||
float hs_flush = 230.f * hs_dist;
|
||||
|
||||
float flush_volume = calc_triangle_3rd_edge(hs_flush, lumi_flush, 120.f);
|
||||
flush_volume = std::max(flush_volume, 60.f);
|
||||
|
||||
//float flush_multiplier = std::atof(m_flush_multiplier_ebox->GetValue().c_str());
|
||||
flush_volume += m_min_flush_vol;
|
||||
return std::min((int)flush_volume, m_max_flush_vol);
|
||||
}
|
||||
|
||||
}
|
||||
34
src/libslic3r/FlushVolCalc.hpp
Normal file
34
src/libslic3r/FlushVolCalc.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef slic3r_FlushVolCalc_hpp_
|
||||
#define slic3r_FlushVolCalc_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Config.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
extern const int g_min_flush_volume_from_support;
|
||||
extern const int g_flush_volume_to_support;
|
||||
extern const int g_max_flush_volume;
|
||||
|
||||
class FlushVolCalculator
|
||||
{
|
||||
public:
|
||||
FlushVolCalculator(int min, int max, float multiplier = 1.0f);
|
||||
~FlushVolCalculator()
|
||||
{
|
||||
}
|
||||
|
||||
int calc_flush_vol(unsigned char src_a, unsigned char src_r, unsigned char src_g, unsigned char src_b,
|
||||
unsigned char dst_a, unsigned char dst_r, unsigned char dst_g, unsigned char dst_b);
|
||||
|
||||
private:
|
||||
int m_min_flush_vol;
|
||||
int m_max_flush_vol;
|
||||
float m_multiplier;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -15,9 +15,13 @@
|
|||
#define DIR_SEPARATOR '/'
|
||||
#endif
|
||||
|
||||
//Translation
|
||||
#include "I18N.hpp"
|
||||
#define _L(s) Slic3r::I18N::translate(s)
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool load_obj(const char *path, TriangleMesh *meshptr)
|
||||
bool load_obj(const char *path, TriangleMesh *meshptr, std::string &message)
|
||||
{
|
||||
if (meshptr == nullptr)
|
||||
return false;
|
||||
|
|
@ -26,6 +30,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr)
|
|||
ObjParser::ObjData data;
|
||||
if (! ObjParser::objparse(path, data)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path;
|
||||
message = _L("load_obj: failed to parse");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -40,10 +45,12 @@ bool load_obj(const char *path, TriangleMesh *meshptr)
|
|||
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.";
|
||||
message = _L("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.";
|
||||
message = _L("The file contains polygons with less than 2 vertices.");
|
||||
return false;
|
||||
}
|
||||
if (num_face_vertices == 4)
|
||||
|
|
@ -75,6 +82,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr)
|
|||
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.";
|
||||
message = _L("The file contains invalid vertex index.");
|
||||
return false;
|
||||
}
|
||||
indices[cnt ++] = vertex.coordIdx;
|
||||
|
|
@ -91,6 +99,7 @@ bool load_obj(const char *path, TriangleMesh *meshptr)
|
|||
*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;
|
||||
message = _L("This OBJ file couldn't be read because it's empty.");
|
||||
return false;
|
||||
}
|
||||
if (meshptr->volume() < 0)
|
||||
|
|
@ -98,11 +107,11 @@ bool load_obj(const char *path, TriangleMesh *meshptr)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool load_obj(const char *path, Model *model, const char *object_name_in)
|
||||
bool load_obj(const char *path, Model *model, std::string &message, const char *object_name_in)
|
||||
{
|
||||
TriangleMesh mesh;
|
||||
|
||||
bool ret = load_obj(path, &mesh);
|
||||
bool ret = load_obj(path, &mesh, message);
|
||||
|
||||
if (ret) {
|
||||
std::string object_name;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ 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 load_obj(const char *path, TriangleMesh *mesh, std::string &message);
|
||||
extern bool load_obj(const char *path, Model *model, std::string &message, const char *object_name = nullptr);
|
||||
|
||||
extern bool store_obj(const char *path, TriangleMesh *mesh);
|
||||
extern bool store_obj(const char *path, ModelObject *model);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
#include "TopExp_Explorer.hxx"
|
||||
#include "BRep_Tool.hxx"
|
||||
|
||||
const double STEP_TRANS_CHORD_ERROR = 0.0025;
|
||||
const double STEP_TRANS_CHORD_ERROR = 0.003;
|
||||
const double STEP_TRANS_ANGLE_RES = 0.5;
|
||||
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -24,7 +24,9 @@ struct ThumbnailData;
|
|||
#define EMBEDDED_FILAMENT_FILE_FORMAT "Metadata/filament_settings_%1%.config"
|
||||
#define EMBEDDED_PRINTER_FILE_FORMAT "Metadata/machine_settings_%1%.config"
|
||||
|
||||
#define BBL_DESIGNER_MODEL_TITLE_TAG "Title"
|
||||
#define BBL_DESIGNER_PROFILE_ID_TAG "DesignProfileId"
|
||||
#define BBL_DESIGNER_PROFILE_TITLE_TAG "ProfileTitle"
|
||||
#define BBL_DESIGNER_MODEL_ID_TAG "DesignModelId"
|
||||
|
||||
|
||||
|
|
@ -76,10 +78,12 @@ struct PlateData
|
|||
std::string gcode_weight;
|
||||
std::string plate_name;
|
||||
std::vector<FilamentInfo> slice_filaments_info;
|
||||
std::vector<size_t> skipped_objects;
|
||||
DynamicPrintConfig config;
|
||||
bool is_support_used {false};
|
||||
bool is_sliced_valid = false;
|
||||
bool toolpath_outside {false};
|
||||
bool is_label_object_enabled {false};
|
||||
|
||||
std::vector<GCodeProcessorResult::SliceWarning> warnings;
|
||||
|
||||
|
|
@ -108,6 +112,7 @@ enum class SaveStrategy
|
|||
WithSliceInfo = 1 << 8,
|
||||
SkipAuxiliary = 1 << 9,
|
||||
UseLoadedId = 1 << 10,
|
||||
ShareMesh = 1 << 11,
|
||||
|
||||
SplitModel = 0x1000 | ProductionExt,
|
||||
Encrypted = SecureContentExt | SplitModel,
|
||||
|
|
@ -221,6 +226,10 @@ extern bool load_bbs_3mf(const char* path, DynamicPrintConfig* config, ConfigSub
|
|||
|
||||
extern std::string bbs_3mf_get_thumbnail(const char * path);
|
||||
|
||||
extern bool load_gcode_3mf_from_stream(std::istream & data, DynamicPrintConfig* config, Model* model, PlateDataPtrs* plate_data_list,
|
||||
Semver* file_version);
|
||||
|
||||
|
||||
//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.
|
||||
|
|
|
|||
|
|
@ -89,14 +89,10 @@ inline Grids line_rasterization(const Line &line, int64_t xdist = RasteXDistance
|
|||
}
|
||||
} // namespace RasterizationImpl
|
||||
|
||||
void LinesBucketQueue::emplace_back_bucket(std::vector<ExtrusionPaths> &&paths, const void *objPtr, Point offset)
|
||||
void LinesBucketQueue::emplace_back_bucket(ExtrusionLayers &&els, const void *objPtr, Point offset)
|
||||
{
|
||||
auto oldSize = _buckets.capacity();
|
||||
if (_objsPtrToId.find(objPtr) == _objsPtrToId.end()) {
|
||||
_objsPtrToId.insert({objPtr, _objsPtrToId.size()});
|
||||
_idToObjsPtr.insert({_objsPtrToId.size() - 1, objPtr});
|
||||
}
|
||||
_buckets.emplace_back(std::move(paths), _objsPtrToId[objPtr], offset);
|
||||
_buckets.emplace_back(std::move(els), objPtr, offset);
|
||||
_pq.push(&_buckets.back());
|
||||
auto newSize = _buckets.capacity();
|
||||
if (oldSize != newSize) { // pointers change
|
||||
|
|
@ -106,15 +102,16 @@ void LinesBucketQueue::emplace_back_bucket(std::vector<ExtrusionPaths> &&paths,
|
|||
}
|
||||
}
|
||||
|
||||
double LinesBucketQueue::removeLowests()
|
||||
// remove lowest and get the current bottom z
|
||||
float LinesBucketQueue::getCurrBottomZ()
|
||||
{
|
||||
auto lowest = _pq.top();
|
||||
_pq.pop();
|
||||
double curHeight = lowest->curHeight();
|
||||
float layerBottomZ = lowest->curBottomZ();
|
||||
std::vector<LinesBucket *> lowests;
|
||||
lowests.push_back(lowest);
|
||||
|
||||
while (_pq.empty() == false && std::abs(_pq.top()->curHeight() - lowest->curHeight()) < EPSILON) {
|
||||
while (_pq.empty() == false && std::abs(_pq.top()->curBottomZ() - lowest->curBottomZ()) < EPSILON) {
|
||||
lowests.push_back(_pq.top());
|
||||
_pq.pop();
|
||||
}
|
||||
|
|
@ -123,7 +120,7 @@ double LinesBucketQueue::removeLowests()
|
|||
bp->raise();
|
||||
if (bp->valid()) { _pq.push(bp); }
|
||||
}
|
||||
return curHeight;
|
||||
return layerBottomZ;
|
||||
}
|
||||
|
||||
LineWithIDs LinesBucketQueue::getCurLines() const
|
||||
|
|
@ -156,32 +153,44 @@ void getExtrusionPathsFromEntity(const ExtrusionEntityCollection *entity, Extrus
|
|||
getExtrusionPathImpl(entity, paths);
|
||||
}
|
||||
|
||||
ExtrusionPaths getExtrusionPathsFromLayer(LayerRegionPtrs layerRegionPtrs)
|
||||
ExtrusionLayers getExtrusionPathsFromLayer(const LayerRegionPtrs layerRegionPtrs)
|
||||
{
|
||||
ExtrusionPaths paths;
|
||||
for (auto regionPtr : layerRegionPtrs) {
|
||||
getExtrusionPathsFromEntity(®ionPtr->perimeters, paths);
|
||||
if (regionPtr->perimeters.empty() == false) { getExtrusionPathsFromEntity(®ionPtr->fills, paths); }
|
||||
ExtrusionLayers perimeters; // periments and infills
|
||||
perimeters.resize(layerRegionPtrs.size());
|
||||
int i = 0;
|
||||
for (LayerRegion *regionPtr : layerRegionPtrs) {
|
||||
perimeters[i].layer = regionPtr->layer();
|
||||
perimeters[i].bottom_z = regionPtr->layer()->bottom_z();
|
||||
perimeters[i].height = regionPtr->layer()->height;
|
||||
getExtrusionPathsFromEntity(®ionPtr->perimeters, perimeters[i].paths);
|
||||
getExtrusionPathsFromEntity(®ionPtr->fills, perimeters[i].paths);
|
||||
++i;
|
||||
}
|
||||
return paths;
|
||||
return perimeters;
|
||||
}
|
||||
|
||||
ExtrusionPaths getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer)
|
||||
ExtrusionLayer getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer)
|
||||
{
|
||||
ExtrusionPaths paths;
|
||||
getExtrusionPathsFromEntity(&supportLayer->support_fills, paths);
|
||||
return paths;
|
||||
ExtrusionLayer el;
|
||||
getExtrusionPathsFromEntity(&supportLayer->support_fills, el.paths);
|
||||
el.layer = supportLayer;
|
||||
el.bottom_z = supportLayer->bottom_z();
|
||||
el.height = supportLayer->height;
|
||||
return el;
|
||||
}
|
||||
|
||||
std::pair<std::vector<ExtrusionPaths>, std::vector<ExtrusionPaths>> getAllLayersExtrusionPathsFromObject(PrintObject *obj)
|
||||
ObjectExtrusions getAllLayersExtrusionPathsFromObject(PrintObject *obj)
|
||||
{
|
||||
std::vector<ExtrusionPaths> objPaths, supportPaths;
|
||||
ObjectExtrusions oe;
|
||||
|
||||
for (auto layerPtr : obj->layers()) { objPaths.push_back(getExtrusionPathsFromLayer(layerPtr->regions())); }
|
||||
for (auto layerPtr : obj->layers()) {
|
||||
auto perimeters = getExtrusionPathsFromLayer(layerPtr->regions());
|
||||
oe.perimeters.insert(oe.perimeters.end(), perimeters.begin(), perimeters.end());
|
||||
}
|
||||
|
||||
for (auto supportLayerPtr : obj->support_layers()) { supportPaths.push_back(getExtrusionPathsFromSupportLayer(supportLayerPtr)); }
|
||||
for (auto supportLayerPtr : obj->support_layers()) { oe.support.push_back(getExtrusionPathsFromSupportLayer(supportLayerPtr)); }
|
||||
|
||||
return {std::move(objPaths), std::move(supportPaths)};
|
||||
return oe;
|
||||
}
|
||||
|
||||
ConflictComputeOpt ConflictChecker::find_inter_of_lines(const LineWithIDs &lines)
|
||||
|
|
@ -205,72 +214,90 @@ ConflictComputeOpt ConflictChecker::find_inter_of_lines(const LineWithIDs &lines
|
|||
}
|
||||
|
||||
ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs,
|
||||
std::optional<const FakeWipeTower *> wtdptr) // find the first intersection point of lines in different objects
|
||||
std::optional<const FakeWipeTower *> wtdptr) // find the first intersection point of lines in different objects
|
||||
{
|
||||
if (objs.size() <= 1) { return {}; }
|
||||
if (objs.size() <= 1 && !wtdptr) { return {}; }
|
||||
LinesBucketQueue conflictQueue;
|
||||
if (wtdptr.has_value()) { // wipe tower at 0 by default
|
||||
auto wtpaths = wtdptr.value()->getFakeExtrusionPathsFromWipeTower();
|
||||
conflictQueue.emplace_back_bucket(std::move(wtpaths), wtdptr.value(), {wtdptr.value()->plate_origin.x(),wtdptr.value()->plate_origin.y()});
|
||||
auto wtpaths = wtdptr.value()->getFakeExtrusionPathsFromWipeTower();
|
||||
ExtrusionLayers wtels;
|
||||
wtels.type = ExtrusionLayersType::WIPE_TOWER;
|
||||
for (int i = 0; i < wtpaths.size(); ++i) { // assume that wipe tower always has same height
|
||||
ExtrusionLayer el;
|
||||
el.paths = wtpaths[i];
|
||||
el.bottom_z = wtpaths[i].front().height * (float) i;
|
||||
el.layer = nullptr;
|
||||
wtels.push_back(el);
|
||||
}
|
||||
conflictQueue.emplace_back_bucket(std::move(wtels), wtdptr.value(), {wtdptr.value()->plate_origin.x(), wtdptr.value()->plate_origin.y()});
|
||||
}
|
||||
for (PrintObject *obj : objs) {
|
||||
auto layers = getAllLayersExtrusionPathsFromObject(obj);
|
||||
conflictQueue.emplace_back_bucket(std::move(layers.first), obj, obj->instances().front().shift);
|
||||
conflictQueue.emplace_back_bucket(std::move(layers.second), obj, obj->instances().front().shift);
|
||||
conflictQueue.emplace_back_bucket(std::move(layers.perimeters), obj, obj->instances().front().shift);
|
||||
conflictQueue.emplace_back_bucket(std::move(layers.support), obj, obj->instances().front().shift);
|
||||
}
|
||||
|
||||
std::vector<LineWithIDs> layersLines;
|
||||
std::vector<double> heights;
|
||||
std::vector<float> bottomZs;
|
||||
while (conflictQueue.valid()) {
|
||||
LineWithIDs lines = conflictQueue.getCurLines();
|
||||
double curHeight = conflictQueue.removeLowests();
|
||||
heights.push_back(curHeight);
|
||||
LineWithIDs lines = conflictQueue.getCurLines();
|
||||
float curBottomZ = conflictQueue.getCurrBottomZ();
|
||||
bottomZs.push_back(curBottomZ);
|
||||
layersLines.push_back(std::move(lines));
|
||||
}
|
||||
|
||||
bool find = false;
|
||||
tbb::concurrent_vector<std::pair<ConflictComputeResult,double>> conflict;
|
||||
|
||||
bool find = false;
|
||||
tbb::concurrent_vector<std::pair<ConflictComputeResult, float>> conflict;
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, layersLines.size()), [&](tbb::blocked_range<size_t> range) {
|
||||
for (size_t i = range.begin(); i < range.end(); i++) {
|
||||
auto interRes = find_inter_of_lines(layersLines[i]);
|
||||
if (interRes.has_value()) {
|
||||
find = true;
|
||||
conflict.emplace_back(interRes.value(),heights[i]);
|
||||
conflict.emplace_back(interRes.value(), bottomZs[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (find) {
|
||||
const void *ptr1 = conflictQueue.idToObjsPtr(conflict[0].first._obj1);
|
||||
const void *ptr2 = conflictQueue.idToObjsPtr(conflict[0].first._obj2);
|
||||
double conflictHeight = conflict[0].second;
|
||||
const void *ptr1 = conflict[0].first._obj1;
|
||||
const void *ptr2 = conflict[0].first._obj2;
|
||||
float conflictPrintZ = conflict[0].second;
|
||||
if (wtdptr.has_value()) {
|
||||
const FakeWipeTower *wtdp = wtdptr.value();
|
||||
if (ptr1 == wtdp || ptr2 == wtdp) {
|
||||
if (ptr2 == wtdp) { std::swap(ptr1, ptr2); }
|
||||
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(ptr2);
|
||||
return std::make_optional<ConflictResult>("WipeTower", obj2->model_object()->name, conflictHeight, nullptr, ptr2);
|
||||
return std::make_optional<ConflictResult>("WipeTower", obj2->model_object()->name, conflictPrintZ, nullptr, ptr2);
|
||||
}
|
||||
}
|
||||
const PrintObject *obj1 = reinterpret_cast<const PrintObject *>(ptr1);
|
||||
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(ptr2);
|
||||
return std::make_optional<ConflictResult>(obj1->model_object()->name, obj2->model_object()->name, conflictHeight, ptr1, ptr2);
|
||||
return std::make_optional<ConflictResult>(obj1->model_object()->name, obj2->model_object()->name, conflictPrintZ, ptr1, ptr2);
|
||||
} else
|
||||
return {};
|
||||
}
|
||||
|
||||
ConflictComputeOpt ConflictChecker::line_intersect(const LineWithID &l1, const LineWithID &l2)
|
||||
{
|
||||
constexpr double SUPPORT_THRESHOLD = 100; // this large almost disables conflict check of supports
|
||||
constexpr double OTHER_THRESHOLD = 0.01;
|
||||
if (l1._id == l2._id) { return {}; } // return true if lines are from same object
|
||||
Point inter;
|
||||
bool intersect = l1._line.intersection(l2._line, &inter);
|
||||
|
||||
if (intersect) {
|
||||
auto dist1 = std::min(unscale(Point(l1._line.a - inter)).norm(), unscale(Point(l1._line.b - inter)).norm());
|
||||
auto dist2 = std::min(unscale(Point(l2._line.a - inter)).norm(), unscale(Point(l2._line.b - inter)).norm());
|
||||
auto dist = std::min(dist1, dist2);
|
||||
if (dist > 0.01) { return std::make_optional<ConflictComputeResult>(l1._id, l2._id); } // the two lines intersects if dist>0.01mm
|
||||
double dist1 = std::min(unscale(Point(l1._line.a - inter)).norm(), unscale(Point(l1._line.b - inter)).norm());
|
||||
double dist2 = std::min(unscale(Point(l2._line.a - inter)).norm(), unscale(Point(l2._line.b - inter)).norm());
|
||||
double dist = std::min(dist1, dist2);
|
||||
ExtrusionRole r1 = l1._role;
|
||||
ExtrusionRole r2 = l2._role;
|
||||
bool both_support = r1 == ExtrusionRole::erSupportMaterial || r1 == ExtrusionRole::erSupportMaterialInterface || r1 == ExtrusionRole::erSupportTransition;
|
||||
both_support = both_support && ( r2 == ExtrusionRole::erSupportMaterial || r2 == ExtrusionRole::erSupportMaterialInterface || r2 == ExtrusionRole::erSupportTransition);
|
||||
if (dist > (both_support ? SUPPORT_THRESHOLD:OTHER_THRESHOLD)) {
|
||||
// the two lines intersects if dist>0.01mm for regular lines, and if dist>1mm for both supports
|
||||
return std::make_optional<ConflictComputeResult>(l1._id, l2._id);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,55 +14,91 @@ namespace Slic3r {
|
|||
|
||||
struct LineWithID
|
||||
{
|
||||
Line _line;
|
||||
int _id;
|
||||
int _role;
|
||||
Line _line;
|
||||
const void * _id;
|
||||
ExtrusionRole _role;
|
||||
|
||||
LineWithID(const Line &line, int id, int role) : _line(line), _id(id), _role(role) {}
|
||||
LineWithID(const Line &line, const void* id, ExtrusionRole role) : _line(line), _id(id), _role(role) {}
|
||||
};
|
||||
|
||||
using LineWithIDs = std::vector<LineWithID>;
|
||||
|
||||
struct ExtrusionLayer
|
||||
{
|
||||
ExtrusionPaths paths;
|
||||
const Layer * layer;
|
||||
float bottom_z;
|
||||
float height;
|
||||
};
|
||||
|
||||
enum class ExtrusionLayersType { INFILL, PERIMETERS, SUPPORT, WIPE_TOWER };
|
||||
|
||||
struct ExtrusionLayers : public std::vector<ExtrusionLayer>
|
||||
{
|
||||
ExtrusionLayersType type;
|
||||
};
|
||||
|
||||
struct ObjectExtrusions
|
||||
{
|
||||
ExtrusionLayers perimeters;
|
||||
ExtrusionLayers support;
|
||||
|
||||
ObjectExtrusions()
|
||||
{
|
||||
perimeters.type = ExtrusionLayersType::PERIMETERS;
|
||||
support.type = ExtrusionLayersType::SUPPORT;
|
||||
}
|
||||
};
|
||||
|
||||
class LinesBucket
|
||||
{
|
||||
private:
|
||||
double _curHeight = 0.0;
|
||||
public:
|
||||
float _curBottomZ = 0.0;
|
||||
unsigned _curPileIdx = 0;
|
||||
|
||||
std::vector<ExtrusionPaths> _piles;
|
||||
int _id;
|
||||
Point _offset;
|
||||
ExtrusionLayers _piles;
|
||||
const void* _id;
|
||||
Point _offset;
|
||||
|
||||
public:
|
||||
LinesBucket(std::vector<ExtrusionPaths> &&paths, int id, Point offset) : _piles(paths), _id(id), _offset(offset) {}
|
||||
LinesBucket(ExtrusionLayers &&paths, const void* id, Point offset) : _piles(paths), _id(id), _offset(offset) {}
|
||||
LinesBucket(LinesBucket &&) = default;
|
||||
|
||||
std::pair<int, int> curRange() const
|
||||
{
|
||||
auto begin = std::lower_bound(_piles.begin(), _piles.end(), _piles[_curPileIdx], [](const ExtrusionLayer &l, const ExtrusionLayer &r) { return l.bottom_z < r.bottom_z; });
|
||||
auto end = std::upper_bound(_piles.begin(), _piles.end(), _piles[_curPileIdx], [](const ExtrusionLayer &l, const ExtrusionLayer &r) { return l.bottom_z < r.bottom_z; });
|
||||
return std::make_pair<int, int>(std::distance(_piles.begin(), begin), std::distance(_piles.begin(), end));
|
||||
}
|
||||
bool valid() const { return _curPileIdx < _piles.size(); }
|
||||
void raise()
|
||||
{
|
||||
if (valid()) {
|
||||
if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height; }
|
||||
_curPileIdx++;
|
||||
}
|
||||
if (!valid()) { return; }
|
||||
auto [b, e] = curRange();
|
||||
_curPileIdx += (e - b);
|
||||
_curBottomZ = _curPileIdx == _piles.size() ? _piles.back().bottom_z : _piles[_curPileIdx].bottom_z;
|
||||
}
|
||||
double curHeight() const { return _curHeight; }
|
||||
float curBottomZ() const { return _curBottomZ; }
|
||||
LineWithIDs curLines() const
|
||||
{
|
||||
auto [b, e] = curRange();
|
||||
LineWithIDs lines;
|
||||
for (const ExtrusionPath &path : _piles[_curPileIdx]) {
|
||||
if (path.is_force_no_extrusion() == false) {
|
||||
Polyline check_polyline = path.polyline;
|
||||
check_polyline.translate(_offset);
|
||||
Lines tmpLines = check_polyline.lines();
|
||||
for (const Line &line : tmpLines) { lines.emplace_back(line, _id, path.role()); }
|
||||
for (int i = b; i < e; ++i) {
|
||||
for (const ExtrusionPath &path : _piles[i].paths) {
|
||||
if (path.is_force_no_extrusion() == false) {
|
||||
Polyline check_polyline = path.polyline;
|
||||
check_polyline.translate(_offset);
|
||||
Lines tmpLines = check_polyline.lines();
|
||||
for (const Line &line : tmpLines) { lines.emplace_back(line, _id, path.role()); }
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
friend bool operator>(const LinesBucket &left, const LinesBucket &right) { return left._curHeight > right._curHeight; }
|
||||
friend bool operator<(const LinesBucket &left, const LinesBucket &right) { return left._curHeight < right._curHeight; }
|
||||
friend bool operator==(const LinesBucket &left, const LinesBucket &right) { return left._curHeight == right._curHeight; }
|
||||
friend bool operator>(const LinesBucket &left, const LinesBucket &right) { return left._curBottomZ > right._curBottomZ; }
|
||||
friend bool operator<(const LinesBucket &left, const LinesBucket &right) { return left._curBottomZ < right._curBottomZ; }
|
||||
friend bool operator==(const LinesBucket &left, const LinesBucket &right) { return left._curBottomZ == right._curBottomZ; }
|
||||
};
|
||||
|
||||
struct LinesBucketPtrComp
|
||||
|
|
@ -72,40 +108,31 @@ struct LinesBucketPtrComp
|
|||
|
||||
class LinesBucketQueue
|
||||
{
|
||||
private:
|
||||
public:
|
||||
std::vector<LinesBucket> _buckets;
|
||||
std::priority_queue<LinesBucket *, std::vector<LinesBucket *>, LinesBucketPtrComp> _pq;
|
||||
std::map<int, const void *> _idToObjsPtr;
|
||||
std::map<const void *, int> _objsPtrToId;
|
||||
|
||||
public:
|
||||
void emplace_back_bucket(std::vector<ExtrusionPaths> &&paths, const void *objPtr, Point offset);
|
||||
void emplace_back_bucket(ExtrusionLayers &&els, const void *objPtr, Point offset);
|
||||
bool valid() const { return _pq.empty() == false; }
|
||||
const void *idToObjsPtr(int id)
|
||||
{
|
||||
if (_idToObjsPtr.find(id) != _idToObjsPtr.end())
|
||||
return _idToObjsPtr[id];
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
double removeLowests();
|
||||
float getCurrBottomZ();
|
||||
LineWithIDs getCurLines() const;
|
||||
};
|
||||
|
||||
void getExtrusionPathsFromEntity(const ExtrusionEntityCollection *entity, ExtrusionPaths &paths);
|
||||
|
||||
ExtrusionPaths getExtrusionPathsFromLayer(LayerRegionPtrs layerRegionPtrs);
|
||||
ExtrusionLayers getExtrusionPathsFromLayer(const LayerRegionPtrs layerRegionPtrs);
|
||||
|
||||
ExtrusionPaths getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer);
|
||||
ExtrusionLayer getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer);
|
||||
|
||||
std::pair<std::vector<ExtrusionPaths>, std::vector<ExtrusionPaths>> getAllLayersExtrusionPathsFromObject(PrintObject *obj);
|
||||
ObjectExtrusions getAllLayersExtrusionPathsFromObject(PrintObject *obj);
|
||||
|
||||
struct ConflictComputeResult
|
||||
{
|
||||
int _obj1;
|
||||
int _obj2;
|
||||
const void* _obj1;
|
||||
const void* _obj2;
|
||||
|
||||
ConflictComputeResult(int o1, int o2) : _obj1(o1), _obj2(o2) {}
|
||||
ConflictComputeResult(const void* o1, const void* o2) : _obj1(o1), _obj2(o2) {}
|
||||
ConflictComputeResult() = default;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
|||
// For absolute extruder distances it will be switched off.
|
||||
// Tapering the absolute extruder distances requires to process every extrusion value after the first transition
|
||||
// layer.
|
||||
bool transition = m_transition_layer && m_config.use_relative_e_distances;
|
||||
bool transition = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||
float layer_height_factor = layer_height / total_layer_length;
|
||||
float len = 0.f;
|
||||
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len]
|
||||
|
|
|
|||
|
|
@ -782,6 +782,9 @@ void annotate_inside_outside(VD &vd, const Lines &lines)
|
|||
|
||||
for (const VD::edge_type &edge : vd.edges())
|
||||
if (edge.vertex1() == nullptr) {
|
||||
if (edge.vertex0() == nullptr)
|
||||
continue;
|
||||
|
||||
// Infinite Voronoi edge separating two Point sites or a Point site and a Segment site.
|
||||
// Infinite edge is always outside and it references at least one valid vertex.
|
||||
assert(edge.is_infinite());
|
||||
|
|
@ -888,6 +891,9 @@ void annotate_inside_outside(VD &vd, const Lines &lines)
|
|||
for (const VD::edge_type &edge : vd.edges()) {
|
||||
assert((edge_category(edge) == EdgeCategory::Unknown) == (edge_category(edge.twin()) == EdgeCategory::Unknown));
|
||||
if (edge_category(edge) == EdgeCategory::Unknown) {
|
||||
if (!edge.is_finite())
|
||||
continue;
|
||||
|
||||
assert(edge.is_finite());
|
||||
const VD::cell_type &cell = *edge.cell();
|
||||
const VD::cell_type &cell2 = *edge.twin()->cell();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "MeshBoolean.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/TryCatchSignal.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#undef PI
|
||||
|
||||
// Include igl first. It defines "L" macro which then clashes with our localization
|
||||
|
|
@ -12,6 +13,7 @@
|
|||
#include <CGAL/Polygon_mesh_processing/corefinement.h>
|
||||
#include <CGAL/Exact_integer.h>
|
||||
#include <CGAL/Surface_mesh.h>
|
||||
#include <CGAL/Cartesian_converter.h>
|
||||
#include <CGAL/Polygon_mesh_processing/orient_polygon_soup.h>
|
||||
#include <CGAL/Polygon_mesh_processing/repair.h>
|
||||
#include <CGAL/Polygon_mesh_processing/remesh.h>
|
||||
|
|
@ -22,6 +24,9 @@
|
|||
#include <CGAL/property_map.h>
|
||||
#include <CGAL/boost/graph/copy_face_graph.h>
|
||||
#include <CGAL/boost/graph/Face_filtered_graph.h>
|
||||
// BBS: for boolean using mcut
|
||||
#include "mcut/include/mcut/mcut.h"
|
||||
#include "boost/log/trivial.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace MeshBoolean {
|
||||
|
|
@ -32,17 +37,17 @@ using MapMatrixXiUnaligned = Eigen::Map<const Eigen::Matrix<int, Eigen::Dynami
|
|||
TriangleMesh eigen_to_triangle_mesh(const EigenMesh &emesh)
|
||||
{
|
||||
auto &VC = emesh.first; auto &FC = emesh.second;
|
||||
|
||||
|
||||
indexed_triangle_set its;
|
||||
its.vertices.reserve(size_t(VC.rows()));
|
||||
its.indices.reserve(size_t(FC.rows()));
|
||||
|
||||
|
||||
for (Eigen::Index i = 0; i < VC.rows(); ++i)
|
||||
its.vertices.emplace_back(VC.row(i).cast<float>());
|
||||
|
||||
|
||||
for (Eigen::Index i = 0; i < FC.rows(); ++i)
|
||||
its.indices.emplace_back(FC.row(i));
|
||||
|
||||
|
||||
return TriangleMesh { std::move(its) };
|
||||
}
|
||||
|
||||
|
|
@ -63,12 +68,12 @@ void minus(EigenMesh &A, const EigenMesh &B)
|
|||
{
|
||||
auto &[VA, FA] = A;
|
||||
auto &[VB, FB] = B;
|
||||
|
||||
|
||||
Eigen::MatrixXd VC;
|
||||
Eigen::MatrixXi FC;
|
||||
igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_MINUS);
|
||||
igl::copyleft::cgal::mesh_boolean(VA, FA, VB, FB, boolean_type, VC, FC);
|
||||
|
||||
|
||||
VA = std::move(VC); FA = std::move(FC);
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +92,7 @@ void self_union(EigenMesh &A)
|
|||
|
||||
igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_UNION);
|
||||
igl::copyleft::cgal::mesh_boolean(V, F, Eigen::MatrixXd(), Eigen::MatrixXi(), boolean_type, VC, FC);
|
||||
|
||||
|
||||
A = std::move(result);
|
||||
}
|
||||
|
||||
|
|
@ -114,6 +119,11 @@ struct CGALMesh {
|
|||
CGALMesh(const _EpicMesh& _m) :m(_m) {}
|
||||
};
|
||||
|
||||
void save_CGALMesh(const std::string& fname, const CGALMesh& cgal_mesh) {
|
||||
std::ofstream os(fname);
|
||||
os << cgal_mesh.m;
|
||||
os.close();
|
||||
}
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Converions from and to CGAL mesh
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -179,12 +189,13 @@ inline Vec3f to_vec3f(const _EpecMesh::Point& v)
|
|||
return { float(iv.x()), float(iv.y()), float(iv.z()) };
|
||||
}
|
||||
|
||||
template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
|
||||
template<class _Mesh>
|
||||
indexed_triangle_set cgal_to_indexed_triangle_set(const _Mesh &cgalmesh)
|
||||
{
|
||||
indexed_triangle_set its;
|
||||
its.vertices.reserve(cgalmesh.num_vertices());
|
||||
its.indices.reserve(cgalmesh.num_faces());
|
||||
|
||||
|
||||
const auto &faces = cgalmesh.faces();
|
||||
const auto &vertices = cgalmesh.vertices();
|
||||
int vsize = int(vertices.size());
|
||||
|
|
@ -208,8 +219,8 @@ template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
|
|||
if (i == 3)
|
||||
its.indices.emplace_back(facet);
|
||||
}
|
||||
|
||||
return TriangleMesh(std::move(its));
|
||||
|
||||
return its;
|
||||
}
|
||||
|
||||
std::unique_ptr<CGALMesh, CGALMeshDeleter>
|
||||
|
|
@ -223,7 +234,12 @@ triangle_mesh_to_cgal(const std::vector<stl_vertex> &V,
|
|||
|
||||
TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh)
|
||||
{
|
||||
return cgal_to_triangle_mesh(cgalmesh.m);
|
||||
return TriangleMesh{cgal_to_indexed_triangle_set(cgalmesh.m)};
|
||||
}
|
||||
|
||||
indexed_triangle_set cgal_to_indexed_triangle_set(const CGALMesh &cgalmesh)
|
||||
{
|
||||
return cgal_to_indexed_triangle_set(cgalmesh.m);
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -303,10 +319,7 @@ void segment(CGALMesh& src, std::vector<CGALMesh>& dst, double smoothing_alpha =
|
|||
_EpicMesh out;
|
||||
CGAL::copy_face_graph(segment_mesh, out);
|
||||
|
||||
//std::ostringstream oss;
|
||||
//oss << "Segment_" << id << ".off";
|
||||
//std::ofstream os(oss.str().data());
|
||||
//os << out;
|
||||
// save_CGALMesh("out.off", out);
|
||||
|
||||
// fill holes
|
||||
typedef boost::graph_traits<_EpicMesh>::halfedge_descriptor halfedge_descriptor;
|
||||
|
|
@ -330,7 +343,7 @@ void segment(CGALMesh& src, std::vector<CGALMesh>& dst, double smoothing_alpha =
|
|||
//if (id > 2) {
|
||||
// mesh_merged.join(out);
|
||||
//}
|
||||
//else
|
||||
//else
|
||||
{
|
||||
dst.emplace_back(std::move(CGALMesh(out)));
|
||||
}
|
||||
|
|
@ -382,9 +395,17 @@ TriangleMesh merge(std::vector<TriangleMesh> meshes)
|
|||
return cgal_to_triangle_mesh(dst);
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Now the public functions for TriangleMesh input:
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
template<class Op> void _mesh_boolean_do(Op &&op, indexed_triangle_set &A, const indexed_triangle_set &B)
|
||||
{
|
||||
CGALMesh meshA;
|
||||
CGALMesh meshB;
|
||||
triangle_mesh_to_cgal(A.vertices, A.indices, meshA.m);
|
||||
triangle_mesh_to_cgal(B.vertices, B.indices, meshB.m);
|
||||
|
||||
_cgal_do(op, meshA, meshB);
|
||||
|
||||
A = cgal_to_indexed_triangle_set(meshA.m);
|
||||
}
|
||||
|
||||
template<class Op> void _mesh_boolean_do(Op &&op, TriangleMesh &A, const TriangleMesh &B)
|
||||
{
|
||||
|
|
@ -392,10 +413,10 @@ template<class Op> void _mesh_boolean_do(Op &&op, TriangleMesh &A, const Triangl
|
|||
CGALMesh meshB;
|
||||
triangle_mesh_to_cgal(A.its.vertices, A.its.indices, meshA.m);
|
||||
triangle_mesh_to_cgal(B.its.vertices, B.its.indices, meshB.m);
|
||||
|
||||
|
||||
_cgal_do(op, meshA, meshB);
|
||||
|
||||
A = cgal_to_triangle_mesh(meshA.m);
|
||||
|
||||
A = cgal_to_triangle_mesh(meshA);
|
||||
}
|
||||
|
||||
void minus(TriangleMesh &A, const TriangleMesh &B)
|
||||
|
|
@ -413,6 +434,21 @@ void intersect(TriangleMesh &A, const TriangleMesh &B)
|
|||
_mesh_boolean_do(_cgal_intersection, A, B);
|
||||
}
|
||||
|
||||
void minus(indexed_triangle_set &A, const indexed_triangle_set &B)
|
||||
{
|
||||
_mesh_boolean_do(_cgal_diff, A, B);
|
||||
}
|
||||
|
||||
void plus(indexed_triangle_set &A, const indexed_triangle_set &B)
|
||||
{
|
||||
_mesh_boolean_do(_cgal_union, A, B);
|
||||
}
|
||||
|
||||
void intersect(indexed_triangle_set &A, const indexed_triangle_set &B)
|
||||
{
|
||||
_mesh_boolean_do(_cgal_intersection, A, B);
|
||||
}
|
||||
|
||||
bool does_self_intersect(const TriangleMesh &mesh)
|
||||
{
|
||||
CGALMesh cgalm;
|
||||
|
|
@ -424,7 +460,7 @@ void CGALMeshDeleter::operator()(CGALMesh *ptr) { delete ptr; }
|
|||
|
||||
bool does_bound_a_volume(const CGALMesh &mesh)
|
||||
{
|
||||
return CGALProc::does_bound_a_volume(mesh.m);
|
||||
return CGAL::is_closed(mesh.m) && CGALProc::does_bound_a_volume(mesh.m);
|
||||
}
|
||||
|
||||
bool empty(const CGALMesh &mesh)
|
||||
|
|
@ -432,7 +468,437 @@ bool empty(const CGALMesh &mesh)
|
|||
return mesh.m.is_empty();
|
||||
}
|
||||
|
||||
CGALMeshPtr clone(const CGALMesh &m)
|
||||
{
|
||||
return CGALMeshPtr{new CGALMesh{m}};
|
||||
}
|
||||
|
||||
} // namespace cgal
|
||||
|
||||
|
||||
namespace mcut {
|
||||
/* BBS: MusangKing
|
||||
* mcut mesh array format for Boolean Opts calculation
|
||||
*/
|
||||
struct McutMesh
|
||||
{
|
||||
// variables for mesh data in a format suited for mcut
|
||||
std::vector<uint32_t> faceSizesArray;
|
||||
std::vector<uint32_t> faceIndicesArray;
|
||||
std::vector<double> vertexCoordsArray;
|
||||
};
|
||||
void McutMeshDeleter::operator()(McutMesh *ptr) { delete ptr; }
|
||||
|
||||
bool empty(const McutMesh &mesh) { return mesh.vertexCoordsArray.empty() || mesh.faceIndicesArray.empty(); }
|
||||
void triangle_mesh_to_mcut(const TriangleMesh &src_mesh, McutMesh &srcMesh, const Transform3d &src_nm = Transform3d::Identity())
|
||||
{
|
||||
// vertices precision convention and copy
|
||||
srcMesh.vertexCoordsArray.reserve(src_mesh.its.vertices.size() * 3);
|
||||
for (int i = 0; i < src_mesh.its.vertices.size(); ++i) {
|
||||
const Vec3d v = src_nm * src_mesh.its.vertices[i].cast<double>();
|
||||
srcMesh.vertexCoordsArray.push_back(v[0]);
|
||||
srcMesh.vertexCoordsArray.push_back(v[1]);
|
||||
srcMesh.vertexCoordsArray.push_back(v[2]);
|
||||
}
|
||||
|
||||
// faces copy
|
||||
srcMesh.faceIndicesArray.reserve(src_mesh.its.indices.size() * 3);
|
||||
srcMesh.faceSizesArray.reserve(src_mesh.its.indices.size());
|
||||
for (int i = 0; i < src_mesh.its.indices.size(); ++i) {
|
||||
const int &f0 = src_mesh.its.indices[i][0];
|
||||
const int &f1 = src_mesh.its.indices[i][1];
|
||||
const int &f2 = src_mesh.its.indices[i][2];
|
||||
srcMesh.faceIndicesArray.push_back(f0);
|
||||
srcMesh.faceIndicesArray.push_back(f1);
|
||||
srcMesh.faceIndicesArray.push_back(f2);
|
||||
|
||||
srcMesh.faceSizesArray.push_back((uint32_t) 3);
|
||||
}
|
||||
}
|
||||
|
||||
McutMeshPtr triangle_mesh_to_mcut(const indexed_triangle_set &M)
|
||||
{
|
||||
std::unique_ptr<McutMesh, McutMeshDeleter> out(new McutMesh{});
|
||||
TriangleMesh trimesh(M);
|
||||
triangle_mesh_to_mcut(trimesh, *out.get());
|
||||
return out;
|
||||
}
|
||||
|
||||
TriangleMesh mcut_to_triangle_mesh(const McutMesh &mcutmesh)
|
||||
{
|
||||
uint32_t ccVertexCount = mcutmesh.vertexCoordsArray.size() / 3;
|
||||
auto &ccVertices = mcutmesh.vertexCoordsArray;
|
||||
auto &ccFaceIndices = mcutmesh.faceIndicesArray;
|
||||
auto &faceSizes = mcutmesh.faceSizesArray;
|
||||
uint32_t ccFaceCount = faceSizes.size();
|
||||
// rearrange vertices/faces and save into result mesh
|
||||
std::vector<Vec3f> vertices(ccVertexCount);
|
||||
for (uint32_t i = 0; i < ccVertexCount; i++) {
|
||||
vertices[i][0] = (float) ccVertices[(uint64_t) i * 3 + 0];
|
||||
vertices[i][1] = (float) ccVertices[(uint64_t) i * 3 + 1];
|
||||
vertices[i][2] = (float) ccVertices[(uint64_t) i * 3 + 2];
|
||||
}
|
||||
|
||||
// output faces
|
||||
int faceVertexOffsetBase = 0;
|
||||
|
||||
// for each face in CC
|
||||
std::vector<Vec3i> faces(ccFaceCount);
|
||||
for (uint32_t f = 0; f < ccFaceCount; ++f) {
|
||||
int faceSize = faceSizes.at(f);
|
||||
|
||||
// for each vertex in face
|
||||
for (int v = 0; v < faceSize; v++) { faces[f][v] = ccFaceIndices[(uint64_t) faceVertexOffsetBase + v]; }
|
||||
faceVertexOffsetBase += faceSize;
|
||||
}
|
||||
|
||||
TriangleMesh out(vertices, faces);
|
||||
return out;
|
||||
}
|
||||
|
||||
void merge_mcut_meshes(McutMesh& src, const McutMesh& cut) {
|
||||
indexed_triangle_set all_its;
|
||||
TriangleMesh tri_src = mcut_to_triangle_mesh(src);
|
||||
TriangleMesh tri_cut = mcut_to_triangle_mesh(cut);
|
||||
its_merge(all_its, tri_src.its);
|
||||
its_merge(all_its, tri_cut.its);
|
||||
src = *triangle_mesh_to_mcut(all_its);
|
||||
}
|
||||
|
||||
MCAPI_ATTR void MCAPI_CALL mcDebugOutput(McDebugSource source,
|
||||
McDebugType type,
|
||||
unsigned int id,
|
||||
McDebugSeverity severity,
|
||||
size_t length,
|
||||
const char* message,
|
||||
const void* userParam)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug)<<Slic3r::format("mcut mcDebugOutput message ( %d ): %s ", id, message);
|
||||
|
||||
switch (source) {
|
||||
case MC_DEBUG_SOURCE_API:
|
||||
BOOST_LOG_TRIVIAL(debug)<<("Source: API");
|
||||
break;
|
||||
case MC_DEBUG_SOURCE_KERNEL:
|
||||
BOOST_LOG_TRIVIAL(debug)<<("Source: Kernel");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case MC_DEBUG_TYPE_ERROR:
|
||||
BOOST_LOG_TRIVIAL(debug)<<("Type: Error");
|
||||
break;
|
||||
case MC_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
|
||||
BOOST_LOG_TRIVIAL(debug)<<("Type: Deprecated Behaviour");
|
||||
break;
|
||||
case MC_DEBUG_TYPE_OTHER:
|
||||
BOOST_LOG_TRIVIAL(debug)<<("Type: Other");
|
||||
break;
|
||||
}
|
||||
|
||||
switch (severity) {
|
||||
case MC_DEBUG_SEVERITY_HIGH:
|
||||
BOOST_LOG_TRIVIAL(debug)<<("Severity: high");
|
||||
break;
|
||||
case MC_DEBUG_SEVERITY_MEDIUM:
|
||||
BOOST_LOG_TRIVIAL(debug)<<("Severity: medium");
|
||||
break;
|
||||
case MC_DEBUG_SEVERITY_LOW:
|
||||
BOOST_LOG_TRIVIAL(debug)<<("Severity: low");
|
||||
break;
|
||||
case MC_DEBUG_SEVERITY_NOTIFICATION:
|
||||
BOOST_LOG_TRIVIAL(debug)<<("Severity: notification");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts)
|
||||
{
|
||||
// create context
|
||||
McContext context = MC_NULL_HANDLE;
|
||||
McResult err = mcCreateContext(&context, 0);
|
||||
// add debug callback according to https://cutdigital.github.io/mcut.site/tutorials/debugging/
|
||||
mcDebugMessageCallback(context, mcDebugOutput, nullptr);
|
||||
mcDebugMessageControl(
|
||||
context,
|
||||
MC_DEBUG_SOURCE_ALL,
|
||||
MC_DEBUG_TYPE_ERROR,
|
||||
MC_DEBUG_SEVERITY_MEDIUM,
|
||||
true);
|
||||
// We can either let MCUT compute all possible meshes (including patches etc.), or we can
|
||||
// constrain the library to compute exactly the boolean op mesh we want. This 'constrained' case
|
||||
// is done with the following flags.
|
||||
// NOTE#1: you can extend these flags by bitwise ORing with additional flags (see `McDispatchFlags' in mcut.h)
|
||||
// NOTE#2: below order of columns MATTERS
|
||||
const std::map<std::string, McFlags> booleanOpts = {
|
||||
{"A_NOT_B", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE},
|
||||
{"B_NOT_A", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW},
|
||||
{"UNION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE},
|
||||
{"INTERSECTION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW},
|
||||
};
|
||||
|
||||
std::map<std::string, McFlags>::const_iterator it = booleanOpts.find(boolean_opts);
|
||||
McFlags boolOpFlags = it->second;
|
||||
|
||||
if (srcMesh.vertexCoordsArray.empty() && (boolean_opts == "UNION" || boolean_opts == "B_NOT_A")) {
|
||||
srcMesh = cutMesh;
|
||||
mcReleaseContext(context);
|
||||
return;
|
||||
}
|
||||
|
||||
err = mcDispatch(context,
|
||||
MC_DISPATCH_VERTEX_ARRAY_DOUBLE | // vertices are in array of doubles
|
||||
MC_DISPATCH_ENFORCE_GENERAL_POSITION | // perturb if necessary
|
||||
boolOpFlags, // filter flags which specify the type of output we want
|
||||
// source mesh
|
||||
reinterpret_cast<const void *>(srcMesh.vertexCoordsArray.data()), reinterpret_cast<const uint32_t *>(srcMesh.faceIndicesArray.data()),
|
||||
srcMesh.faceSizesArray.data(), static_cast<uint32_t>(srcMesh.vertexCoordsArray.size() / 3), static_cast<uint32_t>(srcMesh.faceSizesArray.size()),
|
||||
// cut mesh
|
||||
reinterpret_cast<const void *>(cutMesh.vertexCoordsArray.data()), cutMesh.faceIndicesArray.data(), cutMesh.faceSizesArray.data(),
|
||||
static_cast<uint32_t>(cutMesh.vertexCoordsArray.size() / 3), static_cast<uint32_t>(cutMesh.faceSizesArray.size()));
|
||||
if (err != MC_NO_ERROR) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "MCUT mcDispatch fails! err=" << err;
|
||||
mcReleaseContext(context);
|
||||
if (boolean_opts == "UNION") {
|
||||
merge_mcut_meshes(srcMesh, cutMesh);
|
||||
}
|
||||
else {
|
||||
// when src mesh has multiple connected components, mcut refuses to work.
|
||||
// But we can force it to work by spliting the src mesh into disconnected components,
|
||||
// and do booleans seperately, then merge all the results.
|
||||
indexed_triangle_set all_its;
|
||||
TriangleMesh tri_src = mcut_to_triangle_mesh(srcMesh);
|
||||
std::vector<indexed_triangle_set> src_parts = its_split(tri_src.its);
|
||||
if (src_parts.size() == 1)
|
||||
{
|
||||
//can not split, return error directly
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("bool operation %1% failed, also can not split")%boolean_opts;
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < src_parts.size(); i++)
|
||||
{
|
||||
auto part = triangle_mesh_to_mcut(src_parts[i]);
|
||||
do_boolean(*part, cutMesh, boolean_opts);
|
||||
TriangleMesh tri_part = mcut_to_triangle_mesh(*part);
|
||||
its_merge(all_its, tri_part.its);
|
||||
}
|
||||
srcMesh = *triangle_mesh_to_mcut(all_its);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// query the number of available connected component
|
||||
uint32_t numConnComps;
|
||||
err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, 0, NULL, &numConnComps);
|
||||
if (err != MC_NO_ERROR || numConnComps==0) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "MCUT mcGetConnectedComponents fails! err=" << err << ", numConnComps" << numConnComps;
|
||||
mcReleaseContext(context);
|
||||
if (numConnComps == 0 && boolean_opts == "UNION") {
|
||||
merge_mcut_meshes(srcMesh, cutMesh);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<McConnectedComponent> connectedComponents(numConnComps, MC_NULL_HANDLE);
|
||||
err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, (uint32_t) connectedComponents.size(), connectedComponents.data(), NULL);
|
||||
|
||||
McutMesh outMesh;
|
||||
int N_vertices = 0;
|
||||
// traversal of all connected components
|
||||
for (int n = 0; n < numConnComps; ++n) {
|
||||
// query the data of each connected component from MCUT
|
||||
McConnectedComponent connComp = connectedComponents[n];
|
||||
|
||||
// query the vertices
|
||||
McSize numBytes = 0;
|
||||
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, 0, NULL, &numBytes);
|
||||
uint32_t ccVertexCount = (uint32_t) (numBytes / (sizeof(double) * 3));
|
||||
std::vector<double> ccVertices((uint64_t) ccVertexCount * 3u, 0);
|
||||
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, numBytes, (void *) ccVertices.data(), NULL);
|
||||
|
||||
// query the faces
|
||||
numBytes = 0;
|
||||
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, 0, NULL, &numBytes);
|
||||
std::vector<uint32_t> ccFaceIndices(numBytes / sizeof(uint32_t), 0);
|
||||
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, numBytes, ccFaceIndices.data(), NULL);
|
||||
std::vector<uint32_t> faceSizes(ccFaceIndices.size() / 3, 3);
|
||||
|
||||
const uint32_t ccFaceCount = static_cast<uint32_t>(faceSizes.size());
|
||||
|
||||
// Here we show, how to know when connected components, pertain particular boolean operations.
|
||||
McPatchLocation patchLocation = (McPatchLocation) 0;
|
||||
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_PATCH_LOCATION, sizeof(McPatchLocation), &patchLocation, NULL);
|
||||
|
||||
McFragmentLocation fragmentLocation = (McFragmentLocation) 0;
|
||||
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FRAGMENT_LOCATION, sizeof(McFragmentLocation), &fragmentLocation, NULL);
|
||||
|
||||
outMesh.vertexCoordsArray.insert(outMesh.vertexCoordsArray.end(), ccVertices.begin(), ccVertices.end());
|
||||
|
||||
// add offset to face index
|
||||
for (size_t i = 0; i < ccFaceIndices.size(); i++) {
|
||||
ccFaceIndices[i] += N_vertices;
|
||||
}
|
||||
|
||||
int faceVertexOffsetBase = 0;
|
||||
|
||||
// for each face in CC
|
||||
std::vector<Vec3i> faces(ccFaceCount);
|
||||
for (uint32_t f = 0; f < ccFaceCount; ++f) {
|
||||
bool reverseWindingOrder = (fragmentLocation == MC_FRAGMENT_LOCATION_BELOW) && (patchLocation == MC_PATCH_LOCATION_OUTSIDE);
|
||||
int faceSize = faceSizes.at(f);
|
||||
if (reverseWindingOrder) {
|
||||
std::vector<uint32_t> faceIndex(faceSize);
|
||||
// for each vertex in face
|
||||
for (int v = faceSize - 1; v >= 0; v--) { faceIndex[v] = ccFaceIndices[(uint64_t) faceVertexOffsetBase + v]; }
|
||||
std::copy(faceIndex.begin(), faceIndex.end(), ccFaceIndices.begin() + faceVertexOffsetBase);
|
||||
}
|
||||
faceVertexOffsetBase += faceSize;
|
||||
}
|
||||
|
||||
outMesh.faceIndicesArray.insert(outMesh.faceIndicesArray.end(), ccFaceIndices.begin(), ccFaceIndices.end());
|
||||
outMesh.faceSizesArray.insert(outMesh.faceSizesArray.end(), faceSizes.begin(), faceSizes.end());
|
||||
|
||||
N_vertices += ccVertexCount;
|
||||
}
|
||||
|
||||
// free connected component data
|
||||
err = mcReleaseConnectedComponents(context, 0, NULL);
|
||||
|
||||
// destroy context
|
||||
err = mcReleaseContext(context);
|
||||
|
||||
srcMesh = outMesh;
|
||||
}
|
||||
|
||||
/* BBS: Musang King
|
||||
* mcut for Mesh Boolean which provides C-style syntax API
|
||||
*/
|
||||
std::vector<TriangleMesh> make_boolean(const McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts)
|
||||
{
|
||||
// create context
|
||||
McContext context = MC_NULL_HANDLE;
|
||||
McResult err = mcCreateContext(&context, 0);
|
||||
// add debug callback according to https://cutdigital.github.io/mcut.site/tutorials/debugging/
|
||||
mcDebugMessageCallback(context, mcDebugOutput, nullptr);
|
||||
mcDebugMessageControl(
|
||||
context,
|
||||
MC_DEBUG_SOURCE_ALL,
|
||||
MC_DEBUG_TYPE_ERROR,
|
||||
MC_DEBUG_SEVERITY_MEDIUM,
|
||||
true);
|
||||
// We can either let MCUT compute all possible meshes (including patches etc.), or we can
|
||||
// constrain the library to compute exactly the boolean op mesh we want. This 'constrained' case
|
||||
// is done with the following flags.
|
||||
// NOTE#1: you can extend these flags by bitwise ORing with additional flags (see `McDispatchFlags' in mcut.h)
|
||||
// NOTE#2: below order of columns MATTERS
|
||||
const std::map<std::string, McFlags> booleanOpts = {
|
||||
{"A_NOT_B", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE},
|
||||
{"B_NOT_A", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW},
|
||||
{"UNION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_OUTSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_ABOVE},
|
||||
{"INTERSECTION", MC_DISPATCH_FILTER_FRAGMENT_SEALING_INSIDE | MC_DISPATCH_FILTER_FRAGMENT_LOCATION_BELOW},
|
||||
};
|
||||
|
||||
std::map<std::string, McFlags>::const_iterator it = booleanOpts.find(boolean_opts);
|
||||
McFlags boolOpFlags = it->second;
|
||||
|
||||
err = mcDispatch(context,
|
||||
MC_DISPATCH_VERTEX_ARRAY_DOUBLE | // vertices are in array of doubles
|
||||
MC_DISPATCH_ENFORCE_GENERAL_POSITION | // perturb if necessary
|
||||
boolOpFlags, // filter flags which specify the type of output we want
|
||||
// source mesh
|
||||
reinterpret_cast<const void *>(srcMesh.vertexCoordsArray.data()), reinterpret_cast<const uint32_t *>(srcMesh.faceIndicesArray.data()),
|
||||
srcMesh.faceSizesArray.data(), static_cast<uint32_t>(srcMesh.vertexCoordsArray.size() / 3), static_cast<uint32_t>(srcMesh.faceSizesArray.size()),
|
||||
// cut mesh
|
||||
reinterpret_cast<const void *>(cutMesh.vertexCoordsArray.data()), cutMesh.faceIndicesArray.data(), cutMesh.faceSizesArray.data(),
|
||||
static_cast<uint32_t>(cutMesh.vertexCoordsArray.size() / 3), static_cast<uint32_t>(cutMesh.faceSizesArray.size()));
|
||||
|
||||
// query the number of available connected component
|
||||
uint32_t numConnComps;
|
||||
err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, 0, NULL, &numConnComps);
|
||||
|
||||
std::vector<McConnectedComponent> connectedComponents(numConnComps, MC_NULL_HANDLE);
|
||||
err = mcGetConnectedComponents(context, MC_CONNECTED_COMPONENT_TYPE_FRAGMENT, (uint32_t) connectedComponents.size(), connectedComponents.data(), NULL);
|
||||
|
||||
std::vector<TriangleMesh> outs;
|
||||
// traversal of all connected components
|
||||
for (int n = 0; n < numConnComps; ++n) {
|
||||
// query the data of each connected component from MCUT
|
||||
McConnectedComponent connComp = connectedComponents[n];
|
||||
|
||||
// query the vertices
|
||||
McSize numBytes = 0;
|
||||
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, 0, NULL, &numBytes);
|
||||
uint32_t ccVertexCount = (uint32_t) (numBytes / (sizeof(double) * 3));
|
||||
std::vector<double> ccVertices((uint64_t) ccVertexCount * 3u, 0);
|
||||
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_VERTEX_DOUBLE, numBytes, (void *) ccVertices.data(), NULL);
|
||||
|
||||
// query the faces
|
||||
numBytes = 0;
|
||||
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, 0, NULL, &numBytes);
|
||||
std::vector<uint32_t> ccFaceIndices(numBytes / sizeof(uint32_t), 0);
|
||||
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FACE_TRIANGULATION, numBytes, ccFaceIndices.data(), NULL);
|
||||
std::vector<uint32_t> faceSizes(ccFaceIndices.size() / 3, 3);
|
||||
|
||||
const uint32_t ccFaceCount = static_cast<uint32_t>(faceSizes.size());
|
||||
|
||||
// Here we show, how to know when connected components, pertain particular boolean operations.
|
||||
McPatchLocation patchLocation = (McPatchLocation) 0;
|
||||
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_PATCH_LOCATION, sizeof(McPatchLocation), &patchLocation, NULL);
|
||||
|
||||
McFragmentLocation fragmentLocation = (McFragmentLocation) 0;
|
||||
err = mcGetConnectedComponentData(context, connComp, MC_CONNECTED_COMPONENT_DATA_FRAGMENT_LOCATION, sizeof(McFragmentLocation), &fragmentLocation, NULL);
|
||||
|
||||
// rearrange vertices/faces and save into result mesh
|
||||
std::vector<Vec3f> vertices(ccVertexCount);
|
||||
for (uint32_t i = 0; i < ccVertexCount; ++i) {
|
||||
vertices[i][0] = (float) ccVertices[(uint64_t) i * 3 + 0];
|
||||
vertices[i][1] = (float) ccVertices[(uint64_t) i * 3 + 1];
|
||||
vertices[i][2] = (float) ccVertices[(uint64_t) i * 3 + 2];
|
||||
}
|
||||
|
||||
// output faces
|
||||
int faceVertexOffsetBase = 0;
|
||||
|
||||
// for each face in CC
|
||||
std::vector<Vec3i> faces(ccFaceCount);
|
||||
for (uint32_t f = 0; f < ccFaceCount; ++f) {
|
||||
bool reverseWindingOrder = (fragmentLocation == MC_FRAGMENT_LOCATION_BELOW) && (patchLocation == MC_PATCH_LOCATION_OUTSIDE);
|
||||
int faceSize = faceSizes.at(f);
|
||||
|
||||
// for each vertex in face
|
||||
for (int v = (reverseWindingOrder ? (faceSize - 1) : 0); (reverseWindingOrder ? (v >= 0) : (v < faceSize)); v += (reverseWindingOrder ? -1 : 1)) {
|
||||
faces[f][v] = ccFaceIndices[(uint64_t) faceVertexOffsetBase + v];
|
||||
}
|
||||
faceVertexOffsetBase += faceSize;
|
||||
}
|
||||
|
||||
TriangleMesh out(vertices, faces);
|
||||
outs.emplace_back(out);
|
||||
}
|
||||
|
||||
// free connected component data
|
||||
err = mcReleaseConnectedComponents(context, (uint32_t) connectedComponents.size(), connectedComponents.data());
|
||||
|
||||
// destroy context
|
||||
err = mcReleaseContext(context);
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector<TriangleMesh> &dst_mesh, const std::string &boolean_opts)
|
||||
{
|
||||
McutMesh srcMesh, cutMesh;
|
||||
triangle_mesh_to_mcut(src_mesh, srcMesh);
|
||||
triangle_mesh_to_mcut(cut_mesh, cutMesh);
|
||||
//dst_mesh = make_boolean(srcMesh, cutMesh, boolean_opts);
|
||||
do_boolean(srcMesh, cutMesh, boolean_opts);
|
||||
dst_mesh.push_back(mcut_to_triangle_mesh(srcMesh));
|
||||
}
|
||||
|
||||
} // namespace mcut
|
||||
|
||||
|
||||
} // namespace MeshBoolean
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -26,27 +26,37 @@ namespace cgal {
|
|||
|
||||
struct CGALMesh;
|
||||
struct CGALMeshDeleter { void operator()(CGALMesh *ptr); };
|
||||
using CGALMeshPtr = std::unique_ptr<CGALMesh, CGALMeshDeleter>;
|
||||
|
||||
std::unique_ptr<CGALMesh, CGALMeshDeleter>
|
||||
triangle_mesh_to_cgal(const std::vector<stl_vertex> &V,
|
||||
const std::vector<stl_triangle_vertex_indices> &F);
|
||||
CGALMeshPtr clone(const CGALMesh &m);
|
||||
|
||||
inline std::unique_ptr<CGALMesh, CGALMeshDeleter> triangle_mesh_to_cgal(const indexed_triangle_set &M)
|
||||
void save_CGALMesh(const std::string& fname, const CGALMesh& cgal_mesh);
|
||||
|
||||
CGALMeshPtr triangle_mesh_to_cgal(
|
||||
const std::vector<stl_vertex> &V,
|
||||
const std::vector<stl_triangle_vertex_indices> &F);
|
||||
|
||||
inline CGALMeshPtr triangle_mesh_to_cgal(const indexed_triangle_set &M)
|
||||
{
|
||||
return triangle_mesh_to_cgal(M.vertices, M.indices);
|
||||
}
|
||||
inline std::unique_ptr<CGALMesh, CGALMeshDeleter> triangle_mesh_to_cgal(const TriangleMesh &M)
|
||||
inline CGALMeshPtr triangle_mesh_to_cgal(const TriangleMesh &M)
|
||||
{
|
||||
return triangle_mesh_to_cgal(M.its);
|
||||
}
|
||||
|
||||
TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh);
|
||||
|
||||
indexed_triangle_set cgal_to_indexed_triangle_set(const CGALMesh &cgalmesh);
|
||||
|
||||
// Do boolean mesh difference with CGAL bypassing igl.
|
||||
void minus(TriangleMesh &A, const TriangleMesh &B);
|
||||
void plus(TriangleMesh &A, const TriangleMesh &B);
|
||||
void intersect(TriangleMesh &A, const TriangleMesh &B);
|
||||
|
||||
void minus(indexed_triangle_set &A, const indexed_triangle_set &B);
|
||||
void plus(indexed_triangle_set &A, const indexed_triangle_set &B);
|
||||
void intersect(indexed_triangle_set &A, const indexed_triangle_set &B);
|
||||
|
||||
void minus(CGALMesh &A, CGALMesh &B);
|
||||
void plus(CGALMesh &A, CGALMesh &B);
|
||||
void intersect(CGALMesh &A, CGALMesh &B);
|
||||
|
|
@ -62,6 +72,27 @@ bool does_bound_a_volume(const CGALMesh &mesh);
|
|||
bool empty(const CGALMesh &mesh);
|
||||
}
|
||||
|
||||
namespace mcut {
|
||||
struct McutMesh;
|
||||
struct McutMeshDeleter
|
||||
{
|
||||
void operator()(McutMesh *ptr);
|
||||
};
|
||||
using McutMeshPtr = std::unique_ptr<McutMesh, McutMeshDeleter>;
|
||||
bool empty(const McutMesh &mesh);
|
||||
|
||||
McutMeshPtr triangle_mesh_to_mcut(const indexed_triangle_set &M);
|
||||
TriangleMesh mcut_to_triangle_mesh(const McutMesh &mcutmesh);
|
||||
|
||||
// do boolean and save result to srcMesh
|
||||
void do_boolean(McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts);
|
||||
|
||||
std::vector<TriangleMesh> make_boolean(const McutMesh &srcMesh, const McutMesh &cutMesh, const std::string &boolean_opts);
|
||||
|
||||
// do boolean and convert result to TriangleMesh
|
||||
void make_boolean(const TriangleMesh &src_mesh, const TriangleMesh &cut_mesh, std::vector<TriangleMesh> &dst_mesh, const std::string &boolean_opts);
|
||||
} // namespace mcut
|
||||
|
||||
} // namespace MeshBoolean
|
||||
} // namespace Slic3r
|
||||
#endif // libslic3r_MeshBoolean_hpp_
|
||||
|
|
|
|||
|
|
@ -108,6 +108,21 @@ template<class IndexT> struct ItsNeighborsWrapper
|
|||
const auto& get_index() const noexcept { return index_ref; }
|
||||
};
|
||||
|
||||
// Can be used as the second argument to its_split to apply a functor on each
|
||||
// part, instead of collecting them into a container.
|
||||
template<class Fn>
|
||||
struct SplitOutputFn {
|
||||
|
||||
Fn fn;
|
||||
|
||||
SplitOutputFn(Fn f): fn{std::move(f)} {}
|
||||
|
||||
SplitOutputFn &operator *() { return *this; }
|
||||
void operator=(indexed_triangle_set &&its) { fn(std::move(its)); }
|
||||
void operator=(indexed_triangle_set &its) { fn(its); }
|
||||
SplitOutputFn& operator++() { return *this; };
|
||||
};
|
||||
|
||||
// Splits a mesh into multiple meshes when possible.
|
||||
template<class Its, class OutputIt>
|
||||
void its_split(const Its &m, OutputIt out_it)
|
||||
|
|
@ -155,7 +170,8 @@ void its_split(const Its &m, OutputIt out_it)
|
|||
mesh.indices.emplace_back(new_face);
|
||||
}
|
||||
|
||||
out_it = std::move(mesh);
|
||||
*out_it = std::move(mesh);
|
||||
++out_it;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
|
|||
else if (boost::algorithm::iends_with(input_file, ".stl"))
|
||||
result = load_stl(input_file.c_str(), &model, nullptr, stlFn);
|
||||
else if (boost::algorithm::iends_with(input_file, ".obj"))
|
||||
result = load_obj(input_file.c_str(), &model);
|
||||
result = load_obj(input_file.c_str(), &model, message);
|
||||
else if (boost::algorithm::iends_with(input_file, ".svg"))
|
||||
result = load_svg(input_file.c_str(), &model, message);
|
||||
//BBS: remove the old .amf.xml files
|
||||
|
|
@ -1063,6 +1063,28 @@ void ModelObject::assign_new_unique_ids_recursive()
|
|||
// BBS: production extension
|
||||
int ModelObject::get_backup_id() const { return m_model ? get_model()->get_object_backup_id(*this) : -1; }
|
||||
|
||||
// BBS: Boolean Operations impl. - MusangKing
|
||||
bool ModelObject::make_boolean(ModelObject *cut_object, const std::string &boolean_opts)
|
||||
{
|
||||
// merge meshes into single volume instead of multi-parts object
|
||||
if (this->volumes.size() != 1) {
|
||||
// we can't merge meshes if there's not just one volume
|
||||
return false;
|
||||
}
|
||||
std::vector<TriangleMesh> new_meshes;
|
||||
|
||||
const TriangleMesh &cut_mesh = cut_object->mesh();
|
||||
MeshBoolean::mcut::make_boolean(this->mesh(), cut_mesh, new_meshes, boolean_opts);
|
||||
|
||||
this->clear_volumes();
|
||||
int i = 1;
|
||||
for (TriangleMesh &mesh : new_meshes) {
|
||||
ModelVolume *vol = this->add_volume(mesh);
|
||||
vol->name = this->name + "_" + std::to_string(i++);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh)
|
||||
{
|
||||
ModelVolume* v = new ModelVolume(this, mesh);
|
||||
|
|
@ -1707,7 +1729,7 @@ void ModelObject::apply_cut_connectors(const std::string &name)
|
|||
// Transform the new modifier to be aligned inside the instance
|
||||
new_volume->set_transformation(translate_transform * connector.rotation_m * scale_transform);
|
||||
|
||||
new_volume->cut_info = {connector.attribs.type, connector.radius_tolerance, connector.height_tolerance};
|
||||
new_volume->cut_info = {connector.attribs.type, connector.radius, connector.height, connector.radius_tolerance, connector.height_tolerance};
|
||||
new_volume->name = name + "-" + std::to_string(++connector_id);
|
||||
}
|
||||
cut_id.increase_connectors_cnt(cut_connectors.size());
|
||||
|
|
@ -1982,6 +2004,16 @@ static void reset_instance_transformation(ModelObject* object, size_t src_instan
|
|||
|
||||
for (size_t i = 0; i < object->instances.size(); ++i) {
|
||||
auto& obj_instance = object->instances[i];
|
||||
|
||||
Geometry::Transformation instance_transformation_copy = obj_instance->get_transformation();
|
||||
instance_transformation_copy.set_offset(Vec3d(0, 0, 0));
|
||||
if (object->volumes.size() == 1) {
|
||||
instance_transformation_copy.set_offset(-object->volumes[0]->get_offset());
|
||||
}
|
||||
|
||||
if (i == src_instance_idx && object->volumes.size() == 1)
|
||||
invalidate_translations(object, obj_instance);
|
||||
|
||||
const Vec3d offset = obj_instance->get_offset();
|
||||
const double rot_z = obj_instance->get_rotation().z();
|
||||
|
||||
|
|
@ -2011,6 +2043,13 @@ static void reset_instance_transformation(ModelObject* object, size_t src_instan
|
|||
}
|
||||
|
||||
obj_instance->set_rotation(rotation);
|
||||
|
||||
// update the assemble matrix
|
||||
const Transform3d &assemble_matrix = obj_instance->get_assemble_transformation().get_matrix();
|
||||
const Transform3d &instance_inverse_matrix = instance_transformation_copy.get_matrix().inverse();
|
||||
Transform3d new_instance_inverse_matrix = instance_inverse_matrix * obj_instance->get_transformation().get_matrix(true).inverse();
|
||||
Transform3d new_assemble_transform = assemble_matrix * new_instance_inverse_matrix;
|
||||
obj_instance->set_assemble_from_transform(new_assemble_transform);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2090,16 +2129,12 @@ ModelObjectPtrs ModelObject::cut(size_t instance, std::array<Vec3d, 4> plane_poi
|
|||
}
|
||||
else {
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) {
|
||||
invalidate_translations(upper, instances[instance]);
|
||||
|
||||
reset_instance_transformation(upper, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
|
||||
attributes.has(ModelObjectCutAttribute::FlipUpper), local_displace);
|
||||
|
||||
res.push_back(upper);
|
||||
}
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) {
|
||||
invalidate_translations(lower, instances[instance]);
|
||||
|
||||
reset_instance_transformation(lower, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
|
||||
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : attributes.has(ModelObjectCutAttribute::FlipLower));
|
||||
|
||||
|
|
@ -2108,8 +2143,6 @@ ModelObjectPtrs ModelObject::cut(size_t instance, std::array<Vec3d, 4> plane_poi
|
|||
|
||||
if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
|
||||
for (auto dowel : dowels) {
|
||||
invalidate_translations(dowel, instances[instance]);
|
||||
|
||||
reset_instance_transformation(dowel, instance, Transform3d::Identity(), false, false, local_dowels_displace);
|
||||
|
||||
local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0));
|
||||
|
|
@ -2323,9 +2356,15 @@ void ModelObject::split(ModelObjectPtrs* new_objects)
|
|||
{
|
||||
Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset();
|
||||
model_instance->set_offset(model_instance->get_offset() + shift);
|
||||
|
||||
//BBS: add assemble_view related logic
|
||||
model_instance->set_assemble_transformation(model_instance->get_transformation());
|
||||
model_instance->set_offset_to_assembly(new_vol->get_offset());
|
||||
Geometry::Transformation instance_transformation_copy = model_instance->get_transformation();
|
||||
instance_transformation_copy.set_offset(-new_vol->get_offset());
|
||||
const Transform3d &assemble_matrix = model_instance->get_assemble_transformation().get_matrix();
|
||||
const Transform3d &instance_inverse_matrix = instance_transformation_copy.get_matrix().inverse();
|
||||
Transform3d new_instance_inverse_matrix = instance_inverse_matrix * model_instance->get_transformation().get_matrix(true).inverse();
|
||||
Transform3d new_assemble_transform = assemble_matrix * new_instance_inverse_matrix;
|
||||
model_instance->set_assemble_from_transform(new_assemble_transform);
|
||||
}
|
||||
|
||||
new_vol->set_offset(Vec3d::Zero());
|
||||
|
|
@ -2749,11 +2788,23 @@ void ModelVolume::apply_tolerance()
|
|||
|
||||
Vec3d sf = get_scaling_factor();
|
||||
// make a "hole" wider
|
||||
sf[X] *= 1. + double(cut_info.radius_tolerance);
|
||||
sf[Y] *= 1. + double(cut_info.radius_tolerance);
|
||||
double size_scale = 1.f;
|
||||
if (abs(cut_info.radius - 0) < EPSILON) // For compatibility with old files
|
||||
size_scale = 1.f + double(cut_info.radius_tolerance);
|
||||
else
|
||||
size_scale = (double(cut_info.radius) + double(cut_info.radius_tolerance)) / double(cut_info.radius);
|
||||
|
||||
sf[X] *= size_scale;
|
||||
sf[Y] *= size_scale;
|
||||
|
||||
// make a "hole" dipper
|
||||
sf[Z] *= 1. + double(cut_info.height_tolerance);
|
||||
double height_scale = 1.f;
|
||||
if (abs(cut_info.height - 0) < EPSILON) // For compatibility with old files
|
||||
height_scale = 1.f + double(cut_info.height_tolerance);
|
||||
else
|
||||
height_scale = (double(cut_info.height) + double(cut_info.height_tolerance)) / double(cut_info.height);
|
||||
|
||||
sf[Z] *= height_scale;
|
||||
|
||||
set_scaling_factor(sf);
|
||||
}
|
||||
|
|
@ -3368,19 +3419,18 @@ void ModelInstance::get_arrange_polygon(void *ap, const Slic3r::DynamicPrintConf
|
|||
|
||||
//BBS: add materials related information
|
||||
ModelVolume *volume = NULL;
|
||||
for (size_t i = 0; i < object->volumes.size(); ++ i) {
|
||||
if (object->volumes[i]->is_model_part())
|
||||
{
|
||||
for (size_t i = 0; i < object->volumes.size(); ++i) {
|
||||
if (object->volumes[i]->is_model_part()) {
|
||||
volume = object->volumes[i];
|
||||
break;
|
||||
if (!volume) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "invalid object, should not happen";
|
||||
return;
|
||||
}
|
||||
auto ve = object->volumes[i]->get_extruders();
|
||||
ret.extrude_ids.insert(ret.extrude_ids.end(), ve.begin(), ve.end());
|
||||
}
|
||||
}
|
||||
if (!volume)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "invalid object, should not happen";
|
||||
return;
|
||||
}
|
||||
ret.extrude_ids = volume->get_extruders();
|
||||
|
||||
// get per-object support extruders
|
||||
auto op = object->get_config_value<ConfigOptionBool>(config_global, "enable_support");
|
||||
bool is_support_enabled = op && op->getBool();
|
||||
|
|
|
|||
|
|
@ -511,6 +511,10 @@ public:
|
|||
ModelObjectPtrs segment(size_t instance, unsigned int max_extruders, double smoothing_alpha = 0.5, int segment_number = 5);
|
||||
void split(ModelObjectPtrs* new_objects);
|
||||
void merge();
|
||||
|
||||
// BBS: Boolean opts - Musang King
|
||||
bool make_boolean(ModelObject *cut_object, const std::string &boolean_opts);
|
||||
|
||||
ModelObjectPtrs merge_volumes(std::vector<int>& vol_indeces);//BBS
|
||||
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
|
||||
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
|
||||
|
|
@ -698,7 +702,8 @@ enum class EnforcerBlockerType : int8_t {
|
|||
Extruder13,
|
||||
Extruder14,
|
||||
Extruder15,
|
||||
ExtruderMax
|
||||
Extruder16,
|
||||
ExtruderMax = Extruder16
|
||||
};
|
||||
|
||||
enum class ConversionType : int {
|
||||
|
|
@ -839,12 +844,15 @@ public:
|
|||
bool is_connector{false};
|
||||
bool is_processed{true};
|
||||
CutConnectorType connector_type{CutConnectorType::Plug};
|
||||
float radius{0.f};
|
||||
float height{0.f};
|
||||
float radius_tolerance{0.f}; // [0.f : 1.f]
|
||||
float height_tolerance{0.f}; // [0.f : 1.f]
|
||||
|
||||
CutInfo() = default;
|
||||
CutInfo(CutConnectorType type, float rad_tolerance, float h_tolerance, bool processed = false)
|
||||
: is_connector(true), is_processed(processed), connector_type(type), radius_tolerance(rad_tolerance), height_tolerance(h_tolerance)
|
||||
CutInfo(CutConnectorType type, float radius_, float height_, float rad_tolerance, float h_tolerance, bool processed = false)
|
||||
: is_connector(true), is_processed(processed), connector_type(type)
|
||||
, radius(radius_), height(height_), radius_tolerance(rad_tolerance), height_tolerance(h_tolerance)
|
||||
{}
|
||||
|
||||
void set_processed() { is_processed = true; }
|
||||
|
|
|
|||
|
|
@ -157,6 +157,11 @@ ArrangePolygon get_instance_arrange_poly(ModelInstance* instance, const Slic3r::
|
|||
}
|
||||
#else
|
||||
ap.brim_width = 0;
|
||||
// For by-layer printing, need to shrink bed a little, so the support won't go outside bed.
|
||||
// We set it to 5mm because that's how much a normal support will grow by default.
|
||||
auto supp_type_ptr = obj->get_config_value<ConfigOptionBool>(config, "enable_support");
|
||||
if (supp_type_ptr && supp_type_ptr->getBool())
|
||||
ap.brim_width = 5.0;
|
||||
#endif
|
||||
|
||||
ap.height = obj->bounding_box().size().z();
|
||||
|
|
|
|||
|
|
@ -705,14 +705,44 @@ bool Preset::has_lidar(PresetBundle *preset_bundle)
|
|||
return has_lidar;
|
||||
}
|
||||
|
||||
BedType Preset::get_default_bed_type(PresetBundle* preset_bundle)
|
||||
{
|
||||
if (config.has("default_bed_type") && !config.opt_string("default_bed_type").empty()) {
|
||||
try {
|
||||
std::string str_bed_type = config.opt_string("default_bed_type");
|
||||
int bed_type_value = atoi(str_bed_type.c_str());
|
||||
return BedType(bed_type_value);
|
||||
} catch(...) {
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
std::string model_id = this->get_printer_type(preset_bundle);
|
||||
if (model_id == "BL-P001" || model_id == "BL-P002") {
|
||||
return BedType::btPC;
|
||||
} else if (model_id == "C11") {
|
||||
return BedType::btPEI;
|
||||
}
|
||||
return BedType::btPEI;
|
||||
}
|
||||
|
||||
bool Preset::has_cali_lines(PresetBundle* preset_bundle)
|
||||
{
|
||||
std::string model_id = this->get_printer_type(preset_bundle);
|
||||
if (model_id == "BL-P001" || model_id == "BL-P002") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::vector<std::string> s_Preset_print_options {
|
||||
"layer_height", "initial_layer_print_height", "wall_loops", "slice_closing_radius", "spiral_mode", "slicing_mode",
|
||||
"top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness",
|
||||
"ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall",
|
||||
"seam_position", "staggered_inner_seams", "wall_infill_order", "sparse_infill_density", "sparse_infill_pattern", "top_surface_pattern", "bottom_surface_pattern",
|
||||
"infill_direction",
|
||||
"minimum_sparse_infill_area", "reduce_infill_retraction",
|
||||
"ironing_type", "ironing_flow", "ironing_speed", "ironing_spacing",
|
||||
"minimum_sparse_infill_area", "reduce_infill_retraction","internal_solid_infill_pattern",
|
||||
"ironing_type", "ironing_pattern", "ironing_flow", "ironing_speed", "ironing_spacing",
|
||||
"max_travel_detour_distance",
|
||||
"fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_distance",
|
||||
#ifdef HAS_PRESSURE_EQUALIZER
|
||||
|
|
@ -729,7 +759,7 @@ static std::vector<std::string> s_Preset_print_options {
|
|||
"independent_support_layer_height",
|
||||
"support_angle", "support_interface_top_layers", "support_interface_bottom_layers",
|
||||
"support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern",
|
||||
"support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "max_bridge_length", "print_sequence",
|
||||
"support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "max_bridge_length", "print_sequence", "support_remove_small_overhang",
|
||||
"filename_format", "wall_filament", "support_bottom_z_distance",
|
||||
"sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament",
|
||||
"ooze_prevention", "standby_temperature_delta", "interface_shells", "line_width", "initial_layer_line_width",
|
||||
|
|
@ -1960,8 +1990,9 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string
|
|||
}
|
||||
|
||||
//BBS: add project embedded preset logic
|
||||
void PresetCollection::save_current_preset(const std::string &new_name, bool detach, bool save_to_project)
|
||||
void PresetCollection::save_current_preset(const std::string &new_name, bool detach, bool save_to_project, Preset* _curr_preset)
|
||||
{
|
||||
Preset curr_preset = _curr_preset ? *_curr_preset : m_edited_preset;
|
||||
//BBS: add lock logic for sync preset in background
|
||||
std::string final_inherits;
|
||||
lock();
|
||||
|
|
@ -1980,7 +2011,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
|
|||
return;
|
||||
}
|
||||
// Overwriting an existing preset.
|
||||
preset.config = std::move(m_edited_preset.config);
|
||||
preset.config = std::move(curr_preset.config);
|
||||
// The newly saved preset will be activated -> make it visible.
|
||||
preset.is_visible = true;
|
||||
//TODO: remove the detach logic
|
||||
|
|
@ -1997,7 +2028,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
|
|||
unlock();
|
||||
} else {
|
||||
// Creating a new preset.
|
||||
Preset &preset = *m_presets.insert(it, m_edited_preset);
|
||||
Preset &preset = *m_presets.insert(it, curr_preset);
|
||||
std::string &inherits = preset.inherits();
|
||||
std::string old_name = preset.name;
|
||||
preset.name = new_name;
|
||||
|
|
|
|||
|
|
@ -303,6 +303,8 @@ public:
|
|||
bool is_custom_defined();
|
||||
|
||||
bool has_lidar(PresetBundle *preset_bundle);
|
||||
BedType get_default_bed_type(PresetBundle *preset_bundle);
|
||||
bool has_cali_lines(PresetBundle* preset_bundle);
|
||||
|
||||
|
||||
|
||||
|
|
@ -474,7 +476,7 @@ public:
|
|||
// a new preset is stored into the list of presets.
|
||||
// All presets are marked as not modified and the new preset is activated.
|
||||
//BBS: add project embedded preset logic
|
||||
void save_current_preset(const std::string &new_name, bool detach = false, bool save_to_project = false);
|
||||
void save_current_preset(const std::string &new_name, bool detach = false, bool save_to_project = false, Preset* _curr_preset = nullptr);
|
||||
|
||||
// Delete the current preset, activate the first visible preset.
|
||||
// returns true if the preset was deleted successfully.
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ static std::vector<std::string> s_project_options {
|
|||
|
||||
//BBS: add BBL as default
|
||||
const char *PresetBundle::BBL_BUNDLE = "Custom";
|
||||
const char *PresetBundle::BBL_DEFAULT_PRINTER_MODEL = "";
|
||||
const char *PresetBundle::BBL_DEFAULT_PRINTER_MODEL = "MyKlipper 0.4 nozzle";
|
||||
const char *PresetBundle::BBL_DEFAULT_PRINTER_VARIANT = "0.4";
|
||||
const char *PresetBundle::BBL_DEFAULT_FILAMENT = "My Generic PLA";
|
||||
|
||||
|
|
@ -255,6 +255,8 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
|
|||
|
||||
this->load_selections(config, preferred_selection);
|
||||
|
||||
set_calibrate_printer("");
|
||||
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" finished, returned substitutions %1%")%substitutions.size();
|
||||
return substitutions;
|
||||
|
|
@ -419,6 +421,12 @@ void PresetBundle::reset_project_embedded_presets()
|
|||
Preset* selected_filament = this->filaments.find_preset(filament_presets[i], false);
|
||||
if (!selected_filament) {
|
||||
//it should be the project embedded presets
|
||||
Preset& current_printer = this->printers.get_selected_preset();
|
||||
const std::vector<std::string> &prefered_filament_profiles = current_printer.config.option<ConfigOptionStrings>("default_filament_profile")->values;
|
||||
const std::string prefered_filament_profile = prefered_filament_profiles.empty() ? std::string() : prefered_filament_profiles.front();
|
||||
if (!prefered_filament_profile.empty())
|
||||
filament_presets[i] = prefered_filament_profile;
|
||||
else
|
||||
filament_presets[i] = this->filaments.first_visible().name;
|
||||
}
|
||||
}
|
||||
|
|
@ -525,23 +533,32 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
|||
// BBS do not load sla_print
|
||||
// BBS: change directoties by design
|
||||
try {
|
||||
std::string print_selected_preset_name = prints.get_selected_preset().name;
|
||||
this->prints.load_presets(dir_user_presets, PRESET_PRINT_NAME, substitutions, substitution_rule);
|
||||
prints.select_preset_by_name(print_selected_preset_name, false);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
std::string filament_selected_preset_name = filaments.get_selected_preset().name;
|
||||
this->filaments.load_presets(dir_user_presets, PRESET_FILAMENT_NAME, substitutions, substitution_rule);
|
||||
filaments.select_preset_by_name(filament_selected_preset_name, false);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
std::string printer_selected_preset_name = printers.get_selected_preset().name;
|
||||
this->printers.load_presets(dir_user_presets, PRESET_PRINTER_NAME, substitutions, substitution_rule);
|
||||
printers.select_preset_by_name(printer_selected_preset_name, false);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
if (!errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative);
|
||||
this->update_multi_material_filament_presets();
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
|
||||
set_calibrate_printer("");
|
||||
|
||||
return PresetsConfigSubstitutions();
|
||||
}
|
||||
|
||||
|
|
@ -609,6 +626,8 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig &
|
|||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
//this->load_selections(config, PresetPreferences());
|
||||
|
||||
set_calibrate_printer("");
|
||||
|
||||
if (! errors_cummulative.empty())
|
||||
throw Slic3r::RuntimeError(errors_cummulative);
|
||||
|
||||
|
|
@ -1535,8 +1554,8 @@ unsigned int PresetBundle::sync_ams_list(unsigned int &unknowns)
|
|||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
|
||||
auto filament_type = "Generic " + ams.opt_string("filament_type", 0u);
|
||||
iter = std::find_if(filaments.begin(), filaments.end(), [&filament_type](auto &f) { return f.is_compatible && f.is_system
|
||||
&& boost::algorithm::starts_with(f.name, filament_type);
|
||||
});
|
||||
&& boost::algorithm::starts_with(f.name, filament_type);
|
||||
});
|
||||
if (iter == filaments.end())
|
||||
iter = std::find_if(filaments.begin(), filaments.end(), [&filament_type](auto &f) { return f.is_compatible && f.is_system; });
|
||||
if (iter == filaments.end())
|
||||
|
|
@ -1557,6 +1576,29 @@ unsigned int PresetBundle::sync_ams_list(unsigned int &unknowns)
|
|||
return filament_presets.size();
|
||||
}
|
||||
|
||||
void PresetBundle::set_calibrate_printer(std::string name)
|
||||
{
|
||||
if (name.empty()) {
|
||||
calibrate_filaments.clear();
|
||||
return;
|
||||
}
|
||||
if (!name.empty())
|
||||
calibrate_printer = printers.find_preset(name);
|
||||
const Preset & printer_preset = calibrate_printer ? *calibrate_printer : printers.get_edited_preset();
|
||||
const PresetWithVendorProfile active_printer = printers.get_preset_with_vendor_profile(printer_preset);
|
||||
DynamicPrintConfig config;
|
||||
config.set_key_value("printer_preset", new ConfigOptionString(active_printer.preset.name));
|
||||
const ConfigOption *opt = active_printer.preset.config.option("nozzle_diameter");
|
||||
if (opt) config.set_key_value("num_extruders", new ConfigOptionInt((int) static_cast<const ConfigOptionFloats *>(opt)->values.size()));
|
||||
calibrate_filaments.clear();
|
||||
for (size_t i = filaments.num_default_presets(); i < filaments.size(); ++i) {
|
||||
const Preset & preset = filaments.m_presets[i];
|
||||
const PresetWithVendorProfile this_preset_with_vendor_profile = filaments.get_preset_with_vendor_profile(preset);
|
||||
bool is_compatible = is_compatible_with_printer(this_preset_with_vendor_profile, active_printer, &config);
|
||||
if (is_compatible) calibrate_filaments.insert(&preset);
|
||||
}
|
||||
}
|
||||
|
||||
//BBS: check whether this is the only edited filament
|
||||
bool PresetBundle::is_the_only_edited_filament(unsigned int filament_index)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ public:
|
|||
|
||||
// Orca: update selected filament and print
|
||||
void update_selections(AppConfig &config);
|
||||
void set_calibrate_printer(std::string name);
|
||||
|
||||
PresetCollection prints;
|
||||
PresetCollection sla_prints;
|
||||
|
|
@ -102,6 +103,9 @@ public:
|
|||
std::vector<std::string> filament_presets;
|
||||
// BBS: ams
|
||||
std::vector<DynamicPrintConfig> filament_ams_list;
|
||||
// Calibrate
|
||||
Preset const * calibrate_printer = nullptr;
|
||||
std::set<Preset const *> calibrate_filaments;
|
||||
|
||||
// The project configuration values are kept separated from the print/filament/printer preset,
|
||||
// they are being serialized / deserialized from / to the .amf, .3mf, .config, .gcode,
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
|
|||
|
||||
static const t_config_enum_values s_keys_map_OverhangFanThreshold = {
|
||||
{ "0%", Overhang_threshold_none },
|
||||
{ "5%", Overhang_threshold_1_4 },
|
||||
{ "10%", Overhang_threshold_1_4 },
|
||||
{ "25%", Overhang_threshold_2_4 },
|
||||
{ "50%", Overhang_threshold_3_4 },
|
||||
{ "75%", Overhang_threshold_4_4 },
|
||||
|
|
@ -673,13 +673,13 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_keys_map = &ConfigOptionEnum<OverhangFanThreshold>::get_enum_values();
|
||||
def->mode = comAdvanced;
|
||||
def->enum_values.emplace_back("0%");
|
||||
def->enum_values.emplace_back("5%");
|
||||
def->enum_values.emplace_back("10%");
|
||||
def->enum_values.emplace_back("25%");
|
||||
def->enum_values.emplace_back("50%");
|
||||
def->enum_values.emplace_back("75%");
|
||||
def->enum_values.emplace_back("95%");
|
||||
def->enum_labels.emplace_back("0%");
|
||||
def->enum_labels.emplace_back("5%");
|
||||
def->enum_labels.emplace_back("10%");
|
||||
def->enum_labels.emplace_back("25%");
|
||||
def->enum_labels.emplace_back("50%");
|
||||
def->enum_labels.emplace_back("75%");
|
||||
|
|
@ -1061,6 +1061,15 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_labels = def_top_fill_pattern->enum_labels;
|
||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipRectilinear));
|
||||
|
||||
def = this->add("internal_solid_infill_pattern", coEnum);
|
||||
def->label = L("Internal solid infill pattern");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Line pattern of internal solid infill. if the detect nattow internal solid infill be enabled, the concentric pattern will be used for the small area.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<InfillPattern>::get_enum_values();
|
||||
def->enum_values = def_top_fill_pattern->enum_values;
|
||||
def->enum_labels = def_top_fill_pattern->enum_labels;
|
||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipRectilinear));
|
||||
|
||||
def = this->add("outer_wall_line_width", coFloatOrPercent);
|
||||
def->label = L("Outer wall");
|
||||
def->category = L("Quality");
|
||||
|
|
@ -1348,6 +1357,8 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_values.push_back("PET-CF");
|
||||
def->enum_values.push_back("PETG-CF");
|
||||
def->enum_values.push_back("PVA");
|
||||
def->enum_values.push_back("HIPS");
|
||||
def->enum_values.push_back("PLA-AERO");
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionStrings { "PLA" });
|
||||
|
||||
|
|
@ -1388,6 +1399,9 @@ void PrintConfigDef::init_fff_params()
|
|||
def->cli = ConfigOptionDef::nocli;
|
||||
|
||||
def = this->add("filament_vendor", coStrings);
|
||||
def->label = L("Vendor");
|
||||
def->tooltip = L("Vendor of filament. For show only");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionStrings{L("(Undefined)")});
|
||||
def->cli = ConfigOptionDef::nocli;
|
||||
|
||||
|
|
@ -2028,6 +2042,17 @@ void PrintConfigDef::init_fff_params()
|
|||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<IroningType>(IroningType::NoIroning));
|
||||
|
||||
def = this->add("ironing_pattern", coEnum);
|
||||
def->label = L("Ironing Pattern");
|
||||
def->category = L("Quality");
|
||||
def->enum_keys_map = &ConfigOptionEnum<InfillPattern>::get_enum_values();
|
||||
def->enum_values.push_back("concentric");
|
||||
def->enum_values.push_back("zig-zag");
|
||||
def->enum_labels.push_back(L("Concentric"));
|
||||
def->enum_labels.push_back(L("Rectilinear"));
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipRectilinear));
|
||||
|
||||
def = this->add("ironing_flow", coPercent);
|
||||
def->label = L("Ironing flow");
|
||||
def->category = L("Quality");
|
||||
|
|
@ -2994,6 +3019,13 @@ void PrintConfigDef::init_fff_params()
|
|||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("support_remove_small_overhang", coBool);
|
||||
def->label = L("Remove small overhangs");
|
||||
def->category = L("Support");
|
||||
def->tooltip = L("Remove small overhangs that possibly need no supports.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
// BBS: change type to common float.
|
||||
// It may be rounded to mulitple layer height when independent_support_layer_height is false.
|
||||
def = this->add("support_top_z_distance", coFloat);
|
||||
|
|
@ -4491,6 +4523,8 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
|
|||
ReplaceString(value, split_key, copy_key);
|
||||
}
|
||||
}
|
||||
} else if (opt_key == "overhang_fan_threshold" && value == "5%") {
|
||||
value = "10%";
|
||||
}
|
||||
|
||||
// Ignore the following obsolete configuration keys:
|
||||
|
|
@ -4502,7 +4536,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
|
|||
, "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative"
|
||||
#endif /* HAS_PRESSURE_EQUALIZER */
|
||||
// BBS
|
||||
, "support_sharp_tails","remove_small_overhangs", "support_with_sheath",
|
||||
, "support_sharp_tails","support_remove_small_overhangs", "support_with_sheath",
|
||||
"tree_support_branch_diameter_angle", "tree_support_collision_resolution", "tree_support_with_infill",
|
||||
"max_volumetric_speed", "max_print_speed",
|
||||
"support_closing_radius",
|
||||
|
|
@ -4970,6 +5004,11 @@ std::map<std::string, std::string> validate(const FullPrintConfig &cfg, bool und
|
|||
error_message.emplace("bottom_surface_pattern", L("invalid value ") + cfg.bottom_surface_pattern.serialize());
|
||||
}
|
||||
|
||||
// --soild-fill-pattern
|
||||
if (!print_config_def.get("internal_solid_infill_pattern")->has_enum_value(cfg.internal_solid_infill_pattern.serialize())) {
|
||||
error_message.emplace("internal_solid_infill_pattern", L("invalid value ") + cfg.internal_solid_infill_pattern.serialize());
|
||||
}
|
||||
|
||||
// --fill-density
|
||||
if (fabs(cfg.sparse_infill_density.value - 100.) < EPSILON &&
|
||||
! print_config_def.get("top_surface_pattern")->has_enum_value(cfg.sparse_infill_pattern.serialize())) {
|
||||
|
|
@ -5168,14 +5207,14 @@ CLIActionsConfigDef::CLIActionsConfigDef()
|
|||
/*def = this->add("export_amf", coBool);
|
||||
def->label = L("Export AMF");
|
||||
def->tooltip = L("Export the model(s) as AMF.");
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
def->set_default_value(new ConfigOptionBool(false));*/
|
||||
|
||||
def = this->add("export_stl", coBool);
|
||||
def->label = L("Export STL");
|
||||
def->tooltip = L("Export the model(s) as STL.");
|
||||
def->tooltip = L("Export the objects as multiple STL.");
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("export_gcode", coBool);
|
||||
/*def = this->add("export_gcode", coBool);
|
||||
def->label = L("Export G-code");
|
||||
def->tooltip = L("Slice the model and export toolpaths as G-code.");
|
||||
def->cli = "export-gcode|gcode|g";
|
||||
|
|
@ -5207,6 +5246,12 @@ CLIActionsConfigDef::CLIActionsConfigDef()
|
|||
def->cli = "uptodate";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("load_defaultfila", coBool);
|
||||
def->label = L("Load default filaments");
|
||||
def->tooltip = L("Load first filament as default for those not loaded");
|
||||
def->cli_params = "option";
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("mtcpp", coInt);
|
||||
def->label = L("mtcpp");
|
||||
def->tooltip = L("max triangle count per plate for slicing.");
|
||||
|
|
@ -5307,6 +5352,12 @@ CLITransformConfigDef::CLITransformConfigDef()
|
|||
//def->cli = "arrange|a";
|
||||
def->set_default_value(new ConfigOptionInt(0));
|
||||
|
||||
def = this->add("repetitions", coInt);
|
||||
def->label = L("Repetions count");
|
||||
def->tooltip = L("Repetions count of the whole model");
|
||||
def->cli_params = "count";
|
||||
def->set_default_value(new ConfigOptionInt(1));
|
||||
|
||||
/*def = this->add("ensure_on_bed", coBool);
|
||||
def->label = L("Ensure on bed");
|
||||
def->tooltip = L("Lift the object above the bed when it is partially below. Enabled by default, use --no-ensure-on-bed to disable.");
|
||||
|
|
@ -5412,12 +5463,18 @@ CLIMiscConfigDef::CLIMiscConfigDef()
|
|||
def->cli_params = "\"filament1.json;filament2.json;...\"";
|
||||
def->set_default_value(new ConfigOptionStrings());
|
||||
|
||||
def = this->add("skip_objects", coStrings);
|
||||
def = this->add("skip_objects", coInts);
|
||||
def->label = L("Skip Objects");
|
||||
def->tooltip = L("Skip some objects in this print");
|
||||
def->cli_params = "\"3;5;10;77\"";
|
||||
def->cli_params = "\"3,5,10,77\"";
|
||||
def->set_default_value(new ConfigOptionInts());
|
||||
|
||||
def = this->add("uptodate_settings", coStrings);
|
||||
def->label = L("load uptodate process/machine settings when using uptodate");
|
||||
def->tooltip = L("load uptodate process/machine settings from the specified file when using uptodate");
|
||||
def->cli_params = "\"setting1.json;setting2.json\"";
|
||||
def->set_default_value(new ConfigOptionStrings());
|
||||
|
||||
/*def = this->add("output", coString);
|
||||
def->label = L("Output File");
|
||||
def->tooltip = L("The file where the output will be written (if not specified, it will be based on the input file).");
|
||||
|
|
|
|||
|
|
@ -663,6 +663,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||
((ConfigOptionFloat, support_angle))
|
||||
((ConfigOptionBool, support_on_build_plate_only))
|
||||
((ConfigOptionBool, support_critical_regions_only))
|
||||
((ConfigOptionBool, support_remove_small_overhang))
|
||||
((ConfigOptionFloat, support_top_z_distance))
|
||||
((ConfigOptionFloat, support_bottom_z_distance))
|
||||
((ConfigOptionInt, enforce_support_layers))
|
||||
|
|
@ -731,6 +732,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||
((ConfigOptionBool, ensure_vertical_shell_thickness))
|
||||
((ConfigOptionEnum<InfillPattern>, top_surface_pattern))
|
||||
((ConfigOptionEnum<InfillPattern>, bottom_surface_pattern))
|
||||
((ConfigOptionEnum<InfillPattern>, internal_solid_infill_pattern))
|
||||
((ConfigOptionFloatOrPercent, outer_wall_line_width))
|
||||
((ConfigOptionFloat, outer_wall_speed))
|
||||
((ConfigOptionFloat, infill_direction))
|
||||
|
|
@ -748,6 +750,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||
((ConfigOptionBool, infill_combination))
|
||||
// Ironing options
|
||||
((ConfigOptionEnum<IroningType>, ironing_type))
|
||||
((ConfigOptionEnum<InfillPattern>, ironing_pattern))
|
||||
((ConfigOptionPercent, ironing_flow))
|
||||
((ConfigOptionFloat, ironing_spacing))
|
||||
((ConfigOptionFloat, ironing_speed))
|
||||
|
|
|
|||
|
|
@ -855,6 +855,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||
} else if (
|
||||
opt_key == "top_surface_pattern"
|
||||
|| opt_key == "bottom_surface_pattern"
|
||||
|| opt_key == "internal_solid_infill_pattern"
|
||||
|| opt_key == "external_fill_link_max_length"
|
||||
|| opt_key == "sparse_infill_pattern"
|
||||
|| opt_key == "infill_anchor"
|
||||
|
|
|
|||
|
|
@ -504,14 +504,28 @@ bool groupingVolumes(std::vector<VolumeSlices> objSliceByVolume, std::vector<gro
|
|||
std::vector<int> groupIndex(objSliceByVolume.size(), -1);
|
||||
double offsetValue = 0.05 / SCALING_FACTOR;
|
||||
|
||||
std::vector<std::vector<int>> osvIndex;
|
||||
for (int i = 0; i != objSliceByVolume.size(); ++i) {
|
||||
for (int j = 0; j != objSliceByVolume[i].slices.size(); ++j) {
|
||||
objSliceByVolume[i].slices[j] = offset_ex(objSliceByVolume[i].slices[j], offsetValue);
|
||||
for (ExPolygon& poly_ex : objSliceByVolume[i].slices[j])
|
||||
poly_ex.douglas_peucker(resolution);
|
||||
osvIndex.push_back({ i,j });
|
||||
}
|
||||
}
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, osvIndex.size()),
|
||||
[&osvIndex, &objSliceByVolume, &offsetValue, &resolution](const tbb::blocked_range<int>& range) {
|
||||
for (auto k = range.begin(); k != range.end(); ++k) {
|
||||
for (ExPolygon& poly_ex : objSliceByVolume[osvIndex[k][0]].slices[osvIndex[k][1]])
|
||||
poly_ex.douglas_peucker(resolution);
|
||||
}
|
||||
});
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, osvIndex.size()),
|
||||
[&osvIndex, &objSliceByVolume,&offsetValue, &resolution](const tbb::blocked_range<int>& range) {
|
||||
for (auto k = range.begin(); k != range.end(); ++k) {
|
||||
objSliceByVolume[osvIndex[k][0]].slices[osvIndex[k][1]] = offset_ex(objSliceByVolume[osvIndex[k][0]].slices[osvIndex[k][1]], offsetValue);
|
||||
}
|
||||
});
|
||||
|
||||
for (int i = 0; i != objSliceByVolume.size(); ++i) {
|
||||
if (groupIndex[i] < 0) {
|
||||
groupIndex[i] = i;
|
||||
|
|
@ -596,27 +610,43 @@ void applyNegtiveVolumes(ModelVolumePtrs model_volumes, const std::vector<Volume
|
|||
}
|
||||
}
|
||||
|
||||
void reGroupingLayerPolygons(std::vector<groupedVolumeSlices>& gvss, ExPolygons eps)
|
||||
void reGroupingLayerPolygons(std::vector<groupedVolumeSlices>& gvss, ExPolygons &eps, double resolution)
|
||||
{
|
||||
std::vector<int> epsIndex;
|
||||
epsIndex.resize(eps.size(), -1);
|
||||
for (int ie = 0; ie != eps.size(); ie++) {
|
||||
if (eps[ie].area() <= 0)
|
||||
continue;
|
||||
double minArea = eps[ie].area();
|
||||
for (int iv = 0; iv != gvss.size(); iv++) {
|
||||
auto clipedExPolys = diff_ex(eps[ie], gvss[iv].slices);
|
||||
double area = 0;
|
||||
for (const auto& ce : clipedExPolys) {
|
||||
area += ce.area();
|
||||
}
|
||||
if (area < minArea) {
|
||||
minArea = area;
|
||||
epsIndex[ie] = iv;
|
||||
}
|
||||
}
|
||||
|
||||
auto gvssc = gvss;
|
||||
auto epsc = eps;
|
||||
|
||||
for (ExPolygon& poly_ex : epsc)
|
||||
poly_ex.douglas_peucker(resolution);
|
||||
|
||||
for (int i = 0; i != gvssc.size(); ++i) {
|
||||
for (ExPolygon& poly_ex : gvssc[i].slices)
|
||||
poly_ex.douglas_peucker(resolution);
|
||||
}
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, epsc.size()),
|
||||
[&epsc, &gvssc, &epsIndex](const tbb::blocked_range<int>& range) {
|
||||
for (auto ie = range.begin(); ie != range.end(); ++ie) {
|
||||
if (epsc[ie].area() <= 0)
|
||||
continue;
|
||||
|
||||
double minArea = epsc[ie].area();
|
||||
for (int iv = 0; iv != gvssc.size(); iv++) {
|
||||
auto clipedExPolys = diff_ex(epsc[ie], gvssc[iv].slices);
|
||||
double area = 0;
|
||||
for (const auto& ce : clipedExPolys) {
|
||||
area += ce.area();
|
||||
}
|
||||
if (area < minArea) {
|
||||
minArea = area;
|
||||
epsIndex[ie] = iv;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (int iv = 0; iv != gvss.size(); iv++)
|
||||
gvss[iv].slices.clear();
|
||||
|
||||
|
|
@ -663,9 +693,10 @@ std::string fix_slicing_errors(PrintObject* object, LayerPtrs &layers, const std
|
|||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - begin";
|
||||
std::atomic<bool> is_replaced = false;
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, buggy_layers.size()),
|
||||
[&layers, &throw_if_canceled, &buggy_layers, &error_msg](const tbb::blocked_range<size_t>& range) {
|
||||
[&layers, &throw_if_canceled, &buggy_layers, &is_replaced](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t buggy_layer_idx = range.begin(); buggy_layer_idx < range.end(); ++ buggy_layer_idx) {
|
||||
throw_if_canceled();
|
||||
size_t idx_layer = buggy_layers[buggy_layer_idx];
|
||||
|
|
@ -712,7 +743,7 @@ std::string fix_slicing_errors(PrintObject* object, LayerPtrs &layers, const std
|
|||
}
|
||||
if (!expolys.empty()) {
|
||||
//BBS
|
||||
error_msg = L("Empty layers around bottom are replaced by nearest normal layers.");
|
||||
is_replaced = true;
|
||||
layerm->slices.set(union_ex(expolys), stInternal);
|
||||
}
|
||||
}
|
||||
|
|
@ -723,6 +754,9 @@ std::string fix_slicing_errors(PrintObject* object, LayerPtrs &layers, const std
|
|||
throw_if_canceled();
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing objects - fixing slicing errors in parallel - end";
|
||||
|
||||
if(is_replaced)
|
||||
error_msg = L("Empty layers around bottom are replaced by nearest normal layers.");
|
||||
|
||||
// remove empty layers from bottom
|
||||
while (! layers.empty() && (layers.front()->lslices.empty() || layers.front()->empty())) {
|
||||
delete layers.front();
|
||||
|
|
@ -752,7 +786,7 @@ void groupingVolumesForBrim(PrintObject* object, LayerPtrs& layers, int firstLay
|
|||
applyNegtiveVolumes(object->model_object()->volumes, object->firstLayerObjSliceMod(), object->firstLayerObjGroupsMod(), scaled_resolution);
|
||||
|
||||
// BBS: the actual first layer slices stored in layers are re-sorted by volume group and will be used to generate brim
|
||||
reGroupingLayerPolygons(object->firstLayerObjGroupsMod(), layers.front()->lslices);
|
||||
reGroupingLayerPolygons(object->firstLayerObjGroupsMod(), layers.front()->lslices, scaled_resolution);
|
||||
}
|
||||
|
||||
// Called by make_perimeters()
|
||||
|
|
@ -777,7 +811,6 @@ void PrintObject::slice()
|
|||
m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile));
|
||||
this->slice_volumes();
|
||||
m_print->throw_if_canceled();
|
||||
|
||||
int firstLayerReplacedBy = 0;
|
||||
|
||||
#if 1
|
||||
|
|
@ -1137,8 +1170,12 @@ void PrintObject::slice_volumes()
|
|||
std::min(0.f, xy_hole_scaled),
|
||||
trimming);
|
||||
//BBS: trim surfaces
|
||||
for (size_t region_id = 0; region_id < layer->regions().size(); ++region_id)
|
||||
layer->regions()[region_id]->trim_surfaces(to_polygons(trimming));
|
||||
for (size_t region_id = 0; region_id < layer->regions().size(); ++region_id) {
|
||||
// BBS: split trimming result by region
|
||||
ExPolygons contour_exp = to_expolygons(std::move(layer->regions()[region_id]->slices.surfaces));
|
||||
|
||||
layer->regions()[region_id]->slices.set(intersection_ex(contour_exp, to_polygons(trimming)), stInternal);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Merge all regions' slices to get islands, chain them by a shortest path.
|
||||
|
|
|
|||
|
|
@ -185,4 +185,11 @@ namespace Slic3r {
|
|||
project_path.clear();
|
||||
}
|
||||
|
||||
BBLModelTask::BBLModelTask()
|
||||
{
|
||||
job_id = -1;
|
||||
design_id = -1;
|
||||
profile_id = -1;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ namespace Slic3r {
|
|||
class BBLProject;
|
||||
class BBLProfile;
|
||||
class BBLTask;
|
||||
class BBLModelTask;
|
||||
|
||||
|
||||
enum MachineBedType {
|
||||
|
|
@ -95,6 +96,20 @@ enum TaskUserOptions {
|
|||
OPTIONS_RECORD_TIMELAPSE = 4
|
||||
};
|
||||
|
||||
class BBLModelTask {
|
||||
public:
|
||||
BBLModelTask();
|
||||
~BBLModelTask() {}
|
||||
|
||||
int job_id;
|
||||
int design_id;
|
||||
int profile_id;
|
||||
std::string task_id;
|
||||
std::string model_id;
|
||||
std::string model_name;
|
||||
std::string profile_name;
|
||||
};
|
||||
|
||||
class BBLSubTask {
|
||||
public:
|
||||
enum SubTaskStatus {
|
||||
|
|
@ -112,6 +127,7 @@ public:
|
|||
BBLSubTask(const BBLSubTask& obj) {
|
||||
task_id = obj.task_id;
|
||||
parent_id = obj.parent_id;
|
||||
task_model_id = obj.task_model_id;
|
||||
task_project_id = obj.task_project_id;
|
||||
task_profile_id = obj.task_profile_id;
|
||||
task_name = obj.task_name;
|
||||
|
|
@ -127,9 +143,14 @@ public:
|
|||
task_flow_cali = obj.task_flow_cali;
|
||||
task_vibration_cali = obj.task_vibration_cali;
|
||||
task_layer_inspect = obj.task_layer_inspect;
|
||||
|
||||
job_id = obj.job_id;
|
||||
origin_model_name = obj.origin_model_name;
|
||||
origin_profile_name = obj.origin_profile_name;
|
||||
}
|
||||
|
||||
std::string task_id; /* plate id */
|
||||
std::string task_model_id; /* model id */
|
||||
std::string task_project_id; /* project id */
|
||||
std::string task_profile_id; /* profile id*/
|
||||
std::string task_name; /* task name, generally filename as task name */
|
||||
|
|
@ -161,6 +182,10 @@ public:
|
|||
BBLTask* parent_task_;
|
||||
std::string parent_id;
|
||||
|
||||
int job_id;
|
||||
std::string origin_model_name;
|
||||
std::string origin_profile_name;
|
||||
|
||||
int parse_content_json(std::string json_str);
|
||||
static BBLSubTask::SubTaskStatus parse_status(std::string status);
|
||||
static BBLSubTask::SubTaskStatus parse_user_service_task_status(int status);
|
||||
|
|
@ -186,6 +211,7 @@ public:
|
|||
std::wstring task_dst_url; /* put task to dest url in machine */
|
||||
BBLProfile* profile_;
|
||||
std::string task_project_id;
|
||||
std::string task_model_id;
|
||||
std::string task_profile_id;
|
||||
std::vector<BBLSubTask*> subtasks;
|
||||
std::map<std::string, BBLSliceInfo*> slice_info; /* slice info of subtasks, key: plate idx, 1, 2, 3, etc... */
|
||||
|
|
|
|||
|
|
@ -1057,6 +1057,16 @@ void chain_and_reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_pat
|
|||
reorder_extrusion_paths(extrusion_paths, chain_extrusion_paths(extrusion_paths, start_near));
|
||||
}
|
||||
|
||||
std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy) {
|
||||
Points points;
|
||||
for (const ExPolygon &exploy : input_exploy) {
|
||||
BoundingBox bbox;
|
||||
bbox = get_extents(exploy);
|
||||
points.push_back(bbox.center());
|
||||
}
|
||||
return chain_points(points);
|
||||
}
|
||||
|
||||
std::vector<size_t> chain_points(const Points &points, Point *start_near)
|
||||
{
|
||||
auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; };
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace ClipperLib { class PolyNode; }
|
|||
namespace Slic3r {
|
||||
|
||||
std::vector<size_t> chain_points(const Points &points, Point *start_near = nullptr);
|
||||
std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy);
|
||||
|
||||
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
|
||||
void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain);
|
||||
|
|
|
|||
|
|
@ -190,32 +190,47 @@ std::vector<coordf_t> layer_height_profile_from_ranges(
|
|||
// 2) Convert the trimmed ranges to a height profile, fill in the undefined intervals between z=0 and z=slicing_params.object_print_z_max()
|
||||
// with slicing_params.layer_height
|
||||
std::vector<coordf_t> layer_height_profile;
|
||||
for (std::vector<std::pair<t_layer_height_range,coordf_t>>::const_iterator it_range = ranges_non_overlapping.begin(); it_range != ranges_non_overlapping.end(); ++ it_range) {
|
||||
coordf_t lo = it_range->first.first;
|
||||
coordf_t hi = it_range->first.second;
|
||||
coordf_t height = it_range->second;
|
||||
coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2];
|
||||
if (lo > last_z + EPSILON) {
|
||||
auto last_z = [&layer_height_profile]() {
|
||||
return layer_height_profile.empty() ? 0. : *(layer_height_profile.end() - 2);
|
||||
};
|
||||
auto lh_append = [&layer_height_profile](coordf_t z, coordf_t layer_height) {
|
||||
if (!layer_height_profile.empty()) {
|
||||
bool last_z_matches = is_approx(*(layer_height_profile.end() - 2), z);
|
||||
bool last_h_matches = is_approx(layer_height_profile.back(), layer_height);
|
||||
if (last_h_matches) {
|
||||
if (last_z_matches) {
|
||||
// Drop a duplicate.
|
||||
return;
|
||||
}
|
||||
if (layer_height_profile.size() >= 4 && is_approx(*(layer_height_profile.end() - 3), layer_height)) {
|
||||
// Third repetition of the same layer_height. Update z of the last entry.
|
||||
*(layer_height_profile.end() - 2) = z;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
layer_height_profile.push_back(z);
|
||||
layer_height_profile.push_back(layer_height);
|
||||
};
|
||||
|
||||
for (const std::pair<t_layer_height_range, coordf_t>& non_overlapping_range : ranges_non_overlapping) {
|
||||
coordf_t lo = non_overlapping_range.first.first;
|
||||
coordf_t hi = non_overlapping_range.first.second;
|
||||
coordf_t height = non_overlapping_range.second;
|
||||
if (coordf_t z = last_z(); lo > z + EPSILON) {
|
||||
// Insert a step of normal layer height.
|
||||
layer_height_profile.push_back(last_z);
|
||||
layer_height_profile.push_back(slicing_params.layer_height);
|
||||
layer_height_profile.push_back(lo);
|
||||
layer_height_profile.push_back(slicing_params.layer_height);
|
||||
lh_append(z, slicing_params.layer_height);
|
||||
lh_append(lo, slicing_params.layer_height);
|
||||
}
|
||||
// Insert a step of the overriden layer height.
|
||||
layer_height_profile.push_back(lo);
|
||||
layer_height_profile.push_back(height);
|
||||
layer_height_profile.push_back(hi);
|
||||
layer_height_profile.push_back(height);
|
||||
lh_append(lo, height);
|
||||
lh_append(hi, height);
|
||||
}
|
||||
|
||||
coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2];
|
||||
if (last_z < slicing_params.object_print_z_height()) {
|
||||
if (coordf_t z = last_z(); z < slicing_params.object_print_z_height()) {
|
||||
// Insert a step of normal layer height up to the object top.
|
||||
layer_height_profile.push_back(last_z);
|
||||
layer_height_profile.push_back(slicing_params.layer_height);
|
||||
layer_height_profile.push_back(slicing_params.object_print_z_height());
|
||||
layer_height_profile.push_back(slicing_params.layer_height);
|
||||
lh_append(z, slicing_params.layer_height);
|
||||
lh_append(slicing_params.object_print_z_height(), slicing_params.layer_height);
|
||||
}
|
||||
|
||||
return layer_height_profile;
|
||||
|
|
|
|||
|
|
@ -1634,25 +1634,28 @@ static inline ExPolygons detect_overhangs(
|
|||
//FIXME add user defined filtering here based on minimal area or minimum radius or whatever.
|
||||
|
||||
// BBS
|
||||
for (ExPolygon& expoly : layerm->raw_slices) {
|
||||
bool is_sharp_tail = false;
|
||||
float accum_height = layer.height;
|
||||
if (g_config_support_sharp_tails) {
|
||||
for (ExPolygon& expoly : layerm->raw_slices) {
|
||||
if (offset_ex(expoly, -0.5 * fw).empty()) continue;
|
||||
bool is_sharp_tail = false;
|
||||
float accum_height = layer.height;
|
||||
|
||||
// 1. nothing below
|
||||
// Check whether this is a sharp tail region.
|
||||
// Should use lower_layer_expolys without any offset. Otherwise, it may missing sharp tails near the main body.
|
||||
if (g_config_support_sharp_tails && !overlaps(offset_ex(expoly, 0.5 * fw), lower_layer_expolys)) {
|
||||
is_sharp_tail = expoly.area() < area_thresh_well_supported && !offset_ex(expoly,-0.1*fw).empty();
|
||||
}
|
||||
// 1. nothing below
|
||||
// Check whether this is a sharp tail region.
|
||||
// Should use lower_layer_expolys without any offset. Otherwise, it may missing sharp tails near the main body.
|
||||
if (!overlaps(offset_ex(expoly, 0.5 * fw), lower_layer_expolys)) {
|
||||
is_sharp_tail = expoly.area() < area_thresh_well_supported && !offset_ex(expoly, -0.1 * fw).empty();
|
||||
}
|
||||
|
||||
if (is_sharp_tail) {
|
||||
ExPolygons overhang = diff_ex({ expoly }, lower_layer_polygons);
|
||||
layer.sharp_tails.push_back(expoly);
|
||||
layer.sharp_tails_height.insert({ &expoly, accum_height });
|
||||
overhang = offset_ex(overhang, 0.05 * fw);
|
||||
polygons_append(diff_polygons, to_polygons(overhang));
|
||||
if (is_sharp_tail) {
|
||||
ExPolygons overhang = diff_ex({ expoly }, lower_layer_expolys);
|
||||
layer.sharp_tails.push_back(expoly);
|
||||
layer.sharp_tails_height.insert({ &expoly, accum_height });
|
||||
overhang = offset_ex(overhang, 0.05 * fw);
|
||||
polygons_append(diff_polygons, to_polygons(overhang));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (diff_polygons.empty())
|
||||
|
|
@ -1790,13 +1793,21 @@ static inline std::tuple<Polygons, Polygons, double> detect_contacts(
|
|||
// For the same reason, the non-bridging support area may be smaller than the bridging support area!
|
||||
slices_margin_update(std::min(lower_layer_offset, float(scale_(gap_xy))), no_interface_offset);
|
||||
// Offset the contact polygons outside.
|
||||
|
||||
// BBS: already trim the support in trim_support_layers_by_object()
|
||||
#if 0
|
||||
for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) {
|
||||
diff_polygons = diff(
|
||||
offset(
|
||||
diff_polygons,
|
||||
scaled<float>(SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS),
|
||||
ClipperLib::jtRound,
|
||||
// round mitter limit
|
||||
scale_(0.05)),
|
||||
slices_margin.polygons);
|
||||
}
|
||||
#else
|
||||
diff_polygons = diff(diff_polygons, slices_margin.polygons);
|
||||
#endif
|
||||
}
|
||||
|
||||
polygons_append(contact_polygons, diff_polygons);
|
||||
} // for each layer.region
|
||||
|
||||
|
|
@ -2255,21 +2266,18 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
const coordf_t extrusion_width = m_object_config->line_width.get_abs_value(object.print()->config().nozzle_diameter.get_at(object.config().support_interface_filament-1));
|
||||
const coordf_t extrusion_width_scaled = scale_(extrusion_width);
|
||||
if (is_auto(m_object_config->support_type.value) && g_config_support_sharp_tails && !detect_first_sharp_tail_only) {
|
||||
for (size_t layer_nr = 0; layer_nr < object.layer_count(); layer_nr++) {
|
||||
for (size_t layer_nr = layer_id_start; layer_nr < num_layers; layer_nr++) {
|
||||
if (object.print()->canceled())
|
||||
break;
|
||||
|
||||
const Layer* layer = object.get_layer(layer_nr);
|
||||
const Layer* lower_layer = layer->lower_layer;
|
||||
// skip if:
|
||||
// 1) if the current layer is already detected as sharp tails
|
||||
// 2) lower layer has no sharp tails
|
||||
if (!lower_layer || layer->sharp_tails.empty() == false || lower_layer->sharp_tails.empty() == true)
|
||||
if (!lower_layer)
|
||||
continue;
|
||||
|
||||
// BBS detect sharp tail
|
||||
const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails;
|
||||
auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height;
|
||||
const auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height;
|
||||
for (const ExPolygon& expoly : layer->lslices) {
|
||||
bool is_sharp_tail = false;
|
||||
float accum_height = layer->height;
|
||||
|
|
@ -2300,13 +2308,13 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
}
|
||||
|
||||
// 2.3 check whether sharp tail exceed the max height
|
||||
for (auto& lower_sharp_tail_height : lower_layer_sharptails_height) {
|
||||
for (const auto& lower_sharp_tail_height : lower_layer_sharptails_height) {
|
||||
if (lower_sharp_tail_height.first->overlaps(expoly)) {
|
||||
accum_height += lower_sharp_tail_height.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (accum_height >= sharp_tail_max_support_height) {
|
||||
if (accum_height > sharp_tail_max_support_height) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
|
|
@ -2343,7 +2351,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
return MyLayersPtr();
|
||||
|
||||
// BBS group overhang clusters
|
||||
if (g_config_remove_small_overhangs) {
|
||||
const bool config_remove_small_overhangs = m_object_config->support_remove_small_overhang.value;
|
||||
if (config_remove_small_overhangs) {
|
||||
std::vector<OverhangCluster> clusters;
|
||||
double fw_scaled = scale_(extrusion_width);
|
||||
std::set<ExPolygon*> removed_overhang;
|
||||
|
|
@ -2371,8 +2380,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
|
|||
if (!cluster.is_sharp_tail && !cluster.is_cantilever) {
|
||||
// 2. check overhang cluster size is small
|
||||
cluster.is_small_overhang = false;
|
||||
auto erode1 = offset_ex(cluster.merged_overhangs_dilated, -2.5 * fw_scaled);
|
||||
if (area(erode1) < SQ(scale_(0.1))) {
|
||||
auto erode1 = offset_ex(cluster.merged_overhangs_dilated, -1.0 * fw_scaled);
|
||||
Point bbox_sz = get_extents(erode1).size();
|
||||
if (bbox_sz.x() < 2 * fw_scaled || bbox_sz.y() < 2 * fw_scaled) {
|
||||
cluster.is_small_overhang = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -2824,41 +2834,10 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
|
|||
return bottom_contacts;
|
||||
}
|
||||
|
||||
// FN_HIGHER_EQUAL: the provided object pointer has a Z value >= of an internal threshold.
|
||||
// Find the first item with Z value >= of an internal threshold of fn_higher_equal.
|
||||
// If no vec item with Z value >= of an internal threshold of fn_higher_equal is found, return vec.size()
|
||||
// If the initial idx is size_t(-1), then use binary search.
|
||||
// Otherwise search linearly upwards.
|
||||
template<typename IteratorType, typename IndexType, typename FN_HIGHER_EQUAL>
|
||||
IndexType idx_higher_or_equal(IteratorType begin, IteratorType end, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal)
|
||||
{
|
||||
auto size = int(end - begin);
|
||||
if (size == 0) {
|
||||
idx = 0;
|
||||
} else if (idx == IndexType(-1)) {
|
||||
// First of the batch of layers per thread pool invocation. Use binary search.
|
||||
int idx_low = 0;
|
||||
int idx_high = std::max(0, size - 1);
|
||||
while (idx_low + 1 < idx_high) {
|
||||
int idx_mid = (idx_low + idx_high) / 2;
|
||||
if (fn_higher_equal(begin[idx_mid]))
|
||||
idx_high = idx_mid;
|
||||
else
|
||||
idx_low = idx_mid;
|
||||
}
|
||||
idx = fn_higher_equal(begin[idx_low]) ? idx_low :
|
||||
(fn_higher_equal(begin[idx_high]) ? idx_high : size);
|
||||
} else {
|
||||
// For the other layers of this batch of layers, search incrementally, which is cheaper than the binary search.
|
||||
while (int(idx) < size && ! fn_higher_equal(begin[idx]))
|
||||
++ idx;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
template<typename T, typename IndexType, typename FN_HIGHER_EQUAL>
|
||||
IndexType idx_higher_or_equal(const std::vector<T>& vec, IndexType idx, FN_HIGHER_EQUAL fn_higher_equal)
|
||||
{
|
||||
return idx_higher_or_equal(vec.begin(), vec.end(), idx, fn_higher_equal);
|
||||
return Layer::idx_higher_or_equal(vec.begin(), vec.end(), idx, fn_higher_equal);
|
||||
}
|
||||
|
||||
// FN_LOWER_EQUAL: the provided object pointer has a Z value <= of an internal threshold.
|
||||
|
|
@ -3343,7 +3322,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
|
|||
assert(! support_layer.polygons.empty() && support_layer.print_z >= m_slicing_params.raft_contact_top_z + EPSILON);
|
||||
// Find the overlapping object layers including the extra above / below gap.
|
||||
coordf_t z_threshold = support_layer.bottom_print_z() - gap_extra_below + EPSILON;
|
||||
idx_object_layer_overlapping = idx_higher_or_equal(
|
||||
idx_object_layer_overlapping = Layer::idx_higher_or_equal(
|
||||
object.layers().begin(), object.layers().end(), idx_object_layer_overlapping,
|
||||
[z_threshold](const Layer *layer){ return layer->print_z >= z_threshold; });
|
||||
// Collect all the object layers intersecting with this layer.
|
||||
|
|
@ -4633,6 +4612,40 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
}
|
||||
#endif
|
||||
|
||||
// Calculate top interface angle
|
||||
float angle_of_biggest_bridge = -1.f;
|
||||
do
|
||||
{
|
||||
// Currently only works when thick_bridges is off
|
||||
if (m_object->config().thick_bridges)
|
||||
break;
|
||||
|
||||
coordf_t object_layer_bottom_z = support_layer.print_z + m_slicing_params.gap_support_object;
|
||||
const Layer* object_layer = m_object->get_layer_at_bottomz(object_layer_bottom_z, 10.0 * EPSILON);
|
||||
if (object_layer == nullptr)
|
||||
break;
|
||||
|
||||
if (object_layer != nullptr) {
|
||||
float biggest_bridge_area = 0.f;
|
||||
const Polygons& top_contact_polys = top_contact_layer.polygons_to_extrude();
|
||||
for (auto layerm : object_layer->regions()) {
|
||||
for (auto bridge_surface : layerm->fill_surfaces.filter_by_type(stBottomBridge)) {
|
||||
float bs_area = bridge_surface->area();
|
||||
if (bs_area <= biggest_bridge_area || bridge_surface->bridge_angle < 0.f)
|
||||
continue;
|
||||
|
||||
angle_of_biggest_bridge = bridge_surface->bridge_angle;
|
||||
biggest_bridge_area = bs_area;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
|
||||
auto calc_included_angle_degree = [](int degree_a, int degree_b) {
|
||||
int iad = std::abs(degree_b - degree_a);
|
||||
return std::min(iad, 180 - iad);
|
||||
};
|
||||
|
||||
// Top and bottom contacts, interface layers.
|
||||
for (size_t i = 0; i < 3; ++ i) {
|
||||
MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer);
|
||||
|
|
@ -4655,6 +4668,22 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
|||
// Use interface angle for the interface layers.
|
||||
m_support_params.interface_angle + interface_angle_delta;
|
||||
|
||||
// BBS
|
||||
bool can_adjust_top_interface_angle = (m_object_config->support_interface_top_layers.value > 1 && &layer_ex == &top_contact_layer);
|
||||
if (can_adjust_top_interface_angle && angle_of_biggest_bridge >= 0.f) {
|
||||
int bridge_degree = (int)Geometry::rad2deg(angle_of_biggest_bridge);
|
||||
int support_intf_degree = (int)Geometry::rad2deg(filler_interface->angle);
|
||||
int max_included_degree = 0;
|
||||
int step = 90;
|
||||
for (int add_on_degree = 0; add_on_degree < 180; add_on_degree += step) {
|
||||
int degree_to_try = support_intf_degree + add_on_degree;
|
||||
int included_degree = calc_included_angle_degree(bridge_degree, degree_to_try);
|
||||
if (included_degree > max_included_degree) {
|
||||
max_included_degree = included_degree;
|
||||
filler_interface->angle = Geometry::deg2rad((float)degree_to_try);
|
||||
}
|
||||
}
|
||||
}
|
||||
double density = interface_as_base ? m_support_params.support_density : m_support_params.interface_density;
|
||||
filler_interface->spacing = interface_as_base ? m_support_params.support_material_flow.spacing() : m_support_params.support_material_interface_flow.spacing();
|
||||
filler_interface->link_max_length = coord_t(scale_(filler_interface->spacing * link_max_length_factor / density));
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ public:
|
|||
bool is_external() const { return this->is_top() || this->is_bottom(); }
|
||||
bool is_internal() const { return ! this->is_external(); }
|
||||
bool is_solid() const { return this->is_external() || this->surface_type == stInternalSolid || this->surface_type == stInternalBridge; }
|
||||
bool is_solid_infill() const { return this->surface_type == stInternalSolid; }
|
||||
};
|
||||
|
||||
typedef std::vector<Surface> Surfaces;
|
||||
|
|
|
|||
|
|
@ -187,6 +187,26 @@ std::optional<std::string> get_current_thread_name()
|
|||
|
||||
#endif // _WIN32
|
||||
|
||||
// To be called at the start of the application to save the current thread ID as the main (UI) thread ID.
|
||||
static boost::thread::id g_main_thread_id;
|
||||
|
||||
void save_main_thread_id()
|
||||
{
|
||||
g_main_thread_id = boost::this_thread::get_id();
|
||||
}
|
||||
|
||||
// Retrieve the cached main (UI) thread ID.
|
||||
boost::thread::id get_main_thread_id()
|
||||
{
|
||||
return g_main_thread_id;
|
||||
}
|
||||
|
||||
// Checks whether the main (UI) thread is active.
|
||||
bool is_main_thread_active()
|
||||
{
|
||||
return get_main_thread_id() == boost::this_thread::get_id();
|
||||
}
|
||||
|
||||
// Spawn (n - 1) worker threads on Intel TBB thread pool and name them by an index and a system thread ID.
|
||||
// Also it sets locale of the worker threads to "C" for the G-code generator to produce "." as a decimal separator.
|
||||
void name_tbb_thread_pool_threads_set_locale()
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@ inline bool set_thread_name(boost::thread &thread, const std::string &thread_nam
|
|||
bool set_current_thread_name(const char *thread_name);
|
||||
inline bool set_current_thread_name(const std::string &thread_name) { return set_current_thread_name(thread_name.c_str()); }
|
||||
|
||||
// To be called at the start of the application to save the current thread ID as the main (UI) thread ID.
|
||||
void save_main_thread_id();
|
||||
// Retrieve the cached main (UI) thread ID.
|
||||
boost::thread::id get_main_thread_id();
|
||||
// Checks whether the main (UI) thread is active.
|
||||
bool is_main_thread_active();
|
||||
|
||||
// Returns nullopt if not supported.
|
||||
// Not supported by OSX.
|
||||
// Naming threads is only supported on newer Windows 10.
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@
|
|||
namespace Slic3r
|
||||
{
|
||||
#define unscale_(val) ((val) * SCALING_FACTOR)
|
||||
#define FIRST_LAYER_EXPANSION 1.2
|
||||
|
||||
|
||||
inline unsigned int round_divide(unsigned int dividend, unsigned int divisor) //!< Return dividend divided by divisor rounded to the nearest integer
|
||||
{
|
||||
|
|
@ -734,6 +732,7 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
|
|||
const coordf_t max_bridge_length = scale_(config.max_bridge_length.value);
|
||||
const bool bridge_no_support = max_bridge_length > 0;
|
||||
const bool support_critical_regions_only = config.support_critical_regions_only.value;
|
||||
const bool config_remove_small_overhangs = config.support_remove_small_overhang.value;
|
||||
const int enforce_support_layers = config.enforce_support_layers.value;
|
||||
const double area_thresh_well_supported = SQ(scale_(6));
|
||||
const double length_thresh_well_supported = scale_(6);
|
||||
|
|
@ -870,9 +869,11 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
|
|||
}
|
||||
}
|
||||
ExPolygons curr_polys;
|
||||
std::vector<const ExPolygon*> curr_poly_ptrs;
|
||||
for (const ExPolygon& expoly : layer->lslices) {
|
||||
if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) {
|
||||
curr_polys.emplace_back(expoly);
|
||||
curr_poly_ptrs.emplace_back(&expoly);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -885,28 +886,30 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
|
|||
overhang_areas.end());
|
||||
|
||||
|
||||
ExPolygons overhangs_sharp_tail;
|
||||
if (is_auto(stype) && g_config_support_sharp_tails)
|
||||
{
|
||||
// BBS detect sharp tail
|
||||
const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails;
|
||||
auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height;
|
||||
for (ExPolygon& expoly : layer->lslices) {
|
||||
for (const ExPolygon* expoly : curr_poly_ptrs) {
|
||||
bool is_sharp_tail = false;
|
||||
// 1. nothing below
|
||||
// this is a sharp tail region if it's small but non-ignorable
|
||||
if (!overlaps(offset_ex(expoly, 0.5 * extrusion_width_scaled), lower_polys)) {
|
||||
is_sharp_tail = expoly.area() < area_thresh_well_supported && !offset_ex(expoly, -0.1 * extrusion_width_scaled).empty();
|
||||
if (!overlaps(offset_ex(*expoly, 0.5 * extrusion_width_scaled), lower_polys)) {
|
||||
is_sharp_tail = expoly->area() < area_thresh_well_supported && !offset_ex(*expoly, -0.1 * extrusion_width_scaled).empty();
|
||||
}
|
||||
|
||||
if (is_sharp_tail) {
|
||||
ExPolygons overhang = diff_ex({ expoly }, lower_layer->lslices);
|
||||
layer->sharp_tails.push_back(expoly);
|
||||
layer->sharp_tails_height.insert({ &expoly, layer->height });
|
||||
ExPolygons overhang = diff_ex({ *expoly }, lower_polys);
|
||||
layer->sharp_tails.push_back(*expoly);
|
||||
layer->sharp_tails_height.insert({ expoly, layer->height });
|
||||
append(overhang_areas, overhang);
|
||||
|
||||
if (!overhang.empty())
|
||||
if (!overhang.empty()) {
|
||||
has_sharp_tails = true;
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
SVG svg(format("SVG/sharp_tail_orig_%.02f.svg", layer->print_z), m_object->bounding_box());
|
||||
if (svg.is_opened()) svg.draw(overhang, "red");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -953,15 +956,12 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
|
|||
Layer* layer = m_object->get_layer(layer_nr);
|
||||
SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
|
||||
Layer* lower_layer = layer->lower_layer;
|
||||
// skip if:
|
||||
// 1) if the current layer is already detected as sharp tails
|
||||
// 2) lower layer has no sharp tails
|
||||
if (!lower_layer || layer->sharp_tails.empty() == false || lower_layer->sharp_tails.empty() == true)
|
||||
if (!lower_layer)
|
||||
continue;
|
||||
|
||||
// BBS detect sharp tail
|
||||
const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails;
|
||||
auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height;
|
||||
const auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height;
|
||||
for (ExPolygon& expoly : layer->lslices) {
|
||||
bool is_sharp_tail = false;
|
||||
float accum_height = layer->height;
|
||||
|
|
@ -992,13 +992,13 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
|
|||
}
|
||||
|
||||
// 2.3 check whether sharp tail exceed the max height
|
||||
for (auto& lower_sharp_tail_height : lower_layer_sharptails_height) {
|
||||
for (const auto& lower_sharp_tail_height : lower_layer_sharptails_height) {
|
||||
if (lower_sharp_tail_height.first->overlaps(expoly)) {
|
||||
accum_height += lower_sharp_tail_height.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (accum_height >= sharp_tail_max_support_height) {
|
||||
if (accum_height > sharp_tail_max_support_height) {
|
||||
is_sharp_tail = false;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1024,8 +1024,8 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
|
|||
if (!overhang.empty())
|
||||
has_sharp_tails = true;
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
SVG svg(get_svg_filename(std::to_string(layer->print_z), "sharp_tail"), m_object->bounding_box());
|
||||
if (svg.is_opened()) svg.draw(overhang, "yellow");
|
||||
SVG svg(format("SVG/sharp_tail_%.02f.svg",layer->print_z), m_object->bounding_box());
|
||||
if (svg.is_opened()) svg.draw(overhang, "red");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -1050,7 +1050,7 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
|
|||
auto blockers = m_object->slice_support_blockers();
|
||||
m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers);
|
||||
m_object->project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers);
|
||||
if (is_auto(stype) && g_config_remove_small_overhangs) {
|
||||
if (is_auto(stype) && config_remove_small_overhangs) {
|
||||
if (blockers.size() < m_object->layer_count())
|
||||
blockers.resize(m_object->layer_count());
|
||||
for (auto& cluster : overhangClusters) {
|
||||
|
|
@ -1066,18 +1066,25 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
|
|||
|
||||
if (!cluster.is_sharp_tail && !cluster.is_cantilever) {
|
||||
// 2. check overhang cluster size is smaller than 3.0 * fw_scaled
|
||||
auto erode1 = offset_ex(cluster.merged_poly, -1.5 * extrusion_width_scaled);
|
||||
cluster.is_small_overhang = area(erode1) < SQ(scale_(0.1));
|
||||
auto erode1 = offset_ex(cluster.merged_poly, -1 * extrusion_width_scaled);
|
||||
Point bbox_sz = get_extents(erode1).size();
|
||||
if (bbox_sz.x() < 2 * extrusion_width_scaled || bbox_sz.y() < 2 * extrusion_width_scaled) {
|
||||
cluster.is_small_overhang = true;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
const Layer* layer1 = m_object->get_layer(cluster.min_layer);
|
||||
BoundingBox bbox = cluster.merged_bbox;
|
||||
bbox.merge(get_extents(layer1->lslices));
|
||||
SVG svg(format("SVG/overhangCluster_%s_%s_tail=%s_cantilever=%s_small=%s.svg", cluster.min_layer, layer1->print_z, cluster.is_sharp_tail, cluster.is_cantilever, cluster.is_small_overhang), bbox);
|
||||
SVG svg(format("SVG/overhangCluster_%s-%s_%s-%s_tail=%s_cantilever=%s_small=%s.svg",
|
||||
cluster.min_layer, cluster.max_layer, layer1->print_z, m_object->get_layer(cluster.max_layer)->print_z,
|
||||
cluster.is_sharp_tail, cluster.is_cantilever, cluster.is_small_overhang), bbox);
|
||||
if (svg.is_opened()) {
|
||||
svg.draw(layer1->lslices, "red");
|
||||
svg.draw(cluster.merged_poly, "blue");
|
||||
svg.draw_text(bbox.min + Point(scale_(0), scale_(2)), "lslices", "red", 2);
|
||||
svg.draw_text(bbox.min + Point(scale_(0), scale_(2)), "overhang", "blue", 2);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -1100,7 +1107,7 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
|
|||
SupportLayer* ts_layer = m_object->get_support_layer(layer_nr + m_raft_layers);
|
||||
auto layer = m_object->get_layer(layer_nr);
|
||||
auto lower_layer = layer->lower_layer;
|
||||
if (support_critical_regions_only) {
|
||||
if (support_critical_regions_only && is_auto(stype)) {
|
||||
ts_layer->overhang_areas.clear();
|
||||
if (lower_layer == nullptr)
|
||||
ts_layer->overhang_areas = layer->sharp_tails;
|
||||
|
|
@ -1112,7 +1119,9 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
|
|||
|
||||
if (layer_nr < blockers.size()) {
|
||||
Polygons& blocker = blockers[layer_nr];
|
||||
ts_layer->overhang_areas = diff_ex(ts_layer->overhang_areas, offset_ex(blocker, scale_(radius_sample_resolution)));
|
||||
// Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise,
|
||||
// which are not valid polygons, and will be removed by offset_ex. union_ can make these polygons right.
|
||||
ts_layer->overhang_areas = diff_ex(ts_layer->overhang_areas, offset_ex(union_(blocker), scale_(radius_sample_resolution)));
|
||||
}
|
||||
|
||||
if (max_bridge_length > 0 && ts_layer->overhang_areas.size() > 0 && lower_layer) {
|
||||
|
|
@ -1124,16 +1133,24 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
|
|||
for (auto &area : ts_layer->overhang_areas) {
|
||||
ts_layer->overhang_types.emplace(&area, SupportLayer::Detected);
|
||||
}
|
||||
// enforcers
|
||||
if (layer_nr < enforcers.size()) {
|
||||
// enforcers now follow same logic as normal support. See STUDIO-3692
|
||||
if (layer_nr < enforcers.size() && lower_layer) {
|
||||
float no_interface_offset = std::accumulate(layer->regions().begin(), layer->regions().end(), FLT_MAX,
|
||||
[](float acc, const LayerRegion* layerm) { return std::min(acc, float(layerm->flow(frExternalPerimeter).scaled_width())); });
|
||||
Polygons lower_layer_polygons = (layer_nr == 0) ? Polygons() : to_polygons(lower_layer->lslices);
|
||||
Polygons& enforcer = enforcers[layer_nr];
|
||||
// coconut: enforcer can't do offset2_ex, otherwise faces with angle near 90 degrees can't have enforcers, which
|
||||
// is not good. For example: tails of animals needs extra support except the lowest tip.
|
||||
//enforcer = std::move(offset2_ex(enforcer, -0.1 * extrusion_width_scaled, 0.1 * extrusion_width_scaled));
|
||||
enforcer = offset(enforcer, 0.1 * extrusion_width_scaled);
|
||||
for (const Polygon& poly : enforcer) {
|
||||
ts_layer->overhang_areas.emplace_back(poly);
|
||||
ts_layer->overhang_types.emplace(&ts_layer->overhang_areas.back(), SupportLayer::Enforced);
|
||||
if (!enforcer.empty()) {
|
||||
Polygons enforcer_polygons = diff(intersection(layer->lslices, enforcer),
|
||||
// Inflate just a tiny bit to avoid intersection of the overhang areas with the object.
|
||||
expand(lower_layer_polygons, 0.05f * no_interface_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
// coconut: enforcer can't do offset2_ex, otherwise faces with angle near 90 degrees can't have enforcers, which
|
||||
// is not good. For example: tails of animals needs extra support except the lowest tip.
|
||||
//enforcer = std::move(offset2_ex(enforcer, -0.1 * extrusion_width_scaled, 0.1 * extrusion_width_scaled));
|
||||
enforcer_polygons = offset(enforcer_polygons, 0.1 * extrusion_width_scaled);
|
||||
for (const Polygon& poly : enforcer_polygons) {
|
||||
ts_layer->overhang_areas.emplace_back(poly);
|
||||
ts_layer->overhang_types.emplace(&ts_layer->overhang_areas.back(), SupportLayer::Enforced);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1143,13 +1160,15 @@ void TreeSupport::detect_overhangs(bool detect_first_sharp_tail_only)
|
|||
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
for (const SupportLayer* layer : m_object->support_layers()) {
|
||||
if (layer->overhang_areas.empty())
|
||||
if (layer->overhang_areas.empty() && (blockers.size()<=layer->id() || blockers[layer->id()].empty()))
|
||||
continue;
|
||||
|
||||
SVG svg(format("SVG/overhang_areas_%s.svg", layer->print_z), m_object->bounding_box());
|
||||
if (svg.is_opened()) {
|
||||
svg.draw_outline(m_object->get_layer(layer->id())->lslices, "yellow");
|
||||
svg.draw(layer->overhang_areas, "red");
|
||||
svg.draw(layer->overhang_areas, "orange");
|
||||
if (blockers.size() > layer->id())
|
||||
svg.draw(blockers[layer->id()], "red");
|
||||
for (auto& overhang : layer->overhang_areas) {
|
||||
double aarea = overhang.area()/ area_thresh_well_supported;
|
||||
auto pt = get_extents(overhang).center();
|
||||
|
|
@ -1959,7 +1978,8 @@ coordf_t TreeSupport::calc_branch_radius(coordf_t base_radius, coordf_t mm_to_to
|
|||
return radius;
|
||||
}
|
||||
|
||||
ExPolygons avoid_object_remove_extra_small_parts(ExPolygons &expolys, const ExPolygons &avoid_region) {
|
||||
template<typename RegionType> // RegionType could be ExPolygons or Polygons
|
||||
ExPolygons avoid_object_remove_extra_small_parts(ExPolygons &expolys, const RegionType&avoid_region) {
|
||||
ExPolygons expolys_out;
|
||||
for (auto expoly : expolys) {
|
||||
auto expolys_avoid = diff_ex(expoly, avoid_region);
|
||||
|
|
@ -1977,6 +1997,82 @@ ExPolygons avoid_object_remove_extra_small_parts(ExPolygons &expolys, const ExPo
|
|||
return expolys_out;
|
||||
}
|
||||
|
||||
Polygons TreeSupport::get_trim_support_regions(
|
||||
const PrintObject& object,
|
||||
SupportLayer* support_layer_ptr,
|
||||
const coordf_t gap_extra_above,
|
||||
const coordf_t gap_extra_below,
|
||||
const coordf_t gap_xy)
|
||||
{
|
||||
static const double sharp_tail_xy_gap = 0.2f;
|
||||
static const double no_overlap_xy_gap = 0.2f;
|
||||
double gap_xy_scaled = scale_(gap_xy);
|
||||
SupportLayer& support_layer = *support_layer_ptr;
|
||||
auto m_print_config = object.print()->config();
|
||||
|
||||
size_t idx_object_layer_overlapping = size_t(-1);
|
||||
|
||||
auto is_layers_overlap = [](const SupportLayer& support_layer, const Layer& object_layer, coordf_t bridging_height = 0.f) -> bool {
|
||||
if (std::abs(support_layer.print_z - object_layer.print_z) < EPSILON)
|
||||
return true;
|
||||
|
||||
coordf_t object_lh = bridging_height > EPSILON ? bridging_height : object_layer.height;
|
||||
if (support_layer.print_z < object_layer.print_z && support_layer.print_z > object_layer.print_z - object_lh)
|
||||
return true;
|
||||
|
||||
if (support_layer.print_z > object_layer.print_z && support_layer.bottom_z() < object_layer.print_z - EPSILON)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Find the overlapping object layers including the extra above / below gap.
|
||||
coordf_t z_threshold = support_layer.bottom_z() - gap_extra_below + EPSILON;
|
||||
idx_object_layer_overlapping = Layer::idx_higher_or_equal(
|
||||
object.layers().begin(), object.layers().end(), idx_object_layer_overlapping,
|
||||
[z_threshold](const Layer* layer) { return layer->print_z >= z_threshold; });
|
||||
// Collect all the object layers intersecting with this layer.
|
||||
Polygons polygons_trimming;
|
||||
size_t i = idx_object_layer_overlapping;
|
||||
for (; i < object.layers().size(); ++i) {
|
||||
const Layer& object_layer = *object.layers()[i];
|
||||
if (object_layer.bottom_z() > support_layer.print_z + gap_extra_above - EPSILON)
|
||||
break;
|
||||
|
||||
bool is_overlap = is_layers_overlap(support_layer, object_layer);
|
||||
for (const ExPolygon& expoly : object_layer.lslices) {
|
||||
// BBS
|
||||
bool is_sharptail = !intersection_ex({ expoly }, object_layer.sharp_tails).empty();
|
||||
coordf_t trimming_offset = is_sharptail ? scale_(sharp_tail_xy_gap) :
|
||||
is_overlap ? gap_xy_scaled :
|
||||
scale_(no_overlap_xy_gap);
|
||||
polygons_append(polygons_trimming, offset({ expoly }, trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
}
|
||||
}
|
||||
if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
|
||||
// Collect all bottom surfaces, which will be extruded with a bridging flow.
|
||||
for (; i < object.layers().size(); ++i) {
|
||||
const Layer& object_layer = *object.layers()[i];
|
||||
bool some_region_overlaps = false;
|
||||
for (LayerRegion* region : object_layer.regions()) {
|
||||
coordf_t bridging_height = region->region().bridging_height_avg(m_print_config);
|
||||
if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON)
|
||||
break;
|
||||
some_region_overlaps = true;
|
||||
|
||||
bool is_overlap = is_layers_overlap(support_layer, object_layer, bridging_height);
|
||||
coordf_t trimming_offset = is_overlap ? gap_xy_scaled : scale_(no_overlap_xy_gap);
|
||||
polygons_append(polygons_trimming,
|
||||
offset(region->fill_surfaces.filter_by_type(stBottomBridge), trimming_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS));
|
||||
}
|
||||
if (!some_region_overlaps)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return polygons_trimming;
|
||||
}
|
||||
|
||||
void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_nodes)
|
||||
{
|
||||
const PrintObjectConfig &config = m_object->config();
|
||||
|
|
@ -1985,6 +2081,7 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
|
|||
int bottom_gap_layers = round(m_slicing_params.gap_object_support / m_slicing_params.layer_height);
|
||||
const coordf_t branch_radius = config.tree_support_branch_diameter.value / 2;
|
||||
const coordf_t branch_radius_scaled = scale_(branch_radius);
|
||||
bool on_buildplate_only = config.support_on_build_plate_only.value;
|
||||
Polygon branch_circle; //Pre-generate a circle with correct diameter so that we don't have to recompute those (co)sines every time.
|
||||
|
||||
// Use square support if there are too many nodes per layer because circle support needs much longer time to compute
|
||||
|
|
@ -2122,7 +2219,8 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
|
|||
}
|
||||
area.emplace_back(ExPolygon(circle));
|
||||
// merge overhang to get a smoother interface surface
|
||||
if (top_interface_layers > 0 && node.support_roof_layers_below > 0) {
|
||||
// Do not merge when buildplate_only is on, because some underneath nodes may have been deleted.
|
||||
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !on_buildplate_only) {
|
||||
ExPolygons overhang_expanded;
|
||||
if (node.overhang->contour.size() > 100 || node.overhang->holes.size()>1)
|
||||
overhang_expanded.emplace_back(*node.overhang);
|
||||
|
|
@ -2174,7 +2272,8 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
|
|||
roof_1st_layer = std::move(offset2_ex(roof_1st_layer, contact_dist_scaled, -contact_dist_scaled));
|
||||
|
||||
// avoid object
|
||||
auto avoid_region_interface = m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr);
|
||||
//ExPolygons avoid_region_interface = m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr);
|
||||
Polygons avoid_region_interface = get_trim_support_regions(*m_object, ts_layer, m_slicing_params.gap_object_support, m_slicing_params.gap_support_object, m_ts_data->m_xy_distance);
|
||||
if (has_circle_node) {
|
||||
roof_areas = avoid_object_remove_extra_small_parts(roof_areas, avoid_region_interface);
|
||||
roof_1st_layer = avoid_object_remove_extra_small_parts(roof_1st_layer, avoid_region_interface);
|
||||
|
|
@ -2251,7 +2350,7 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
|
|||
}
|
||||
});
|
||||
|
||||
#if 1
|
||||
|
||||
if (with_lightning_infill)
|
||||
{
|
||||
const bool global_lightning_infill = true;
|
||||
|
|
@ -2323,7 +2422,7 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
|
|||
else if (!with_infill) {
|
||||
// move the holes to contour so they can be well supported
|
||||
|
||||
// check if poly's contour intersects with expoly's contour
|
||||
// check if poly's contour intersects with expoly's contour
|
||||
auto intersects_contour = [](Polygon poly, ExPolygon expoly, Point& pt_on_poly, Point& pt_on_expoly, Point& pt_far_on_poly, float dist_thresh = 0.01) {
|
||||
float min_dist = std::numeric_limits<float>::max();
|
||||
float max_dist = 0;
|
||||
|
|
@ -2436,7 +2535,7 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
for (int layer_nr = m_object->layer_count() - 1; layer_nr >= 0; layer_nr--) {
|
||||
|
|
@ -2731,20 +2830,17 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
|
|||
node_ = (node.dist_mm_to_top >= neighbour->dist_mm_to_top && p_node->parent) ? p_node : neighbour;
|
||||
else
|
||||
node_ = p_node->parent ? p_node : neighbour;
|
||||
// Make sure the next pass doesn't drop down either of these (since that already happened).
|
||||
node_->merged_neighbours.push_front(node_ == p_node ? neighbour : p_node);
|
||||
const bool to_buildplate = !is_inside_ex(m_ts_data->get_avoidance(0, layer_nr_next), next_position);
|
||||
Node * next_node = new Node(next_position, node_->distance_to_top + 1, layer_nr_next, node_->support_roof_layers_below-1, to_buildplate, node_->parent,
|
||||
Node * next_node = new Node(next_position, node_->distance_to_top + 1, layer_nr_next, node_->support_roof_layers_below-1, to_buildplate, node_,
|
||||
print_z_next, height_next);
|
||||
next_node->movement = next_position - node.position;
|
||||
get_max_move_dist(next_node);
|
||||
next_node->is_merged = true;
|
||||
contact_nodes[layer_nr_next].push_back(next_node);
|
||||
|
||||
// make sure the trees are all connected
|
||||
if (node.parent) node.parent->child = next_node;
|
||||
if (neighbour->parent) neighbour->parent->child = next_node;
|
||||
|
||||
// Make sure the next pass doesn't drop down either of these (since that already happened).
|
||||
node.merged_neighbours.push_front(neighbour);
|
||||
to_delete.insert(neighbour);
|
||||
to_delete.insert(p_node);
|
||||
}
|
||||
|
|
@ -2977,7 +3073,7 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
|
|||
}
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
#if 1
|
||||
// delete nodes with no children (means either it's a single layer nodes, or the branch has been deleted but not completely)
|
||||
for (size_t layer_nr = contact_nodes.size() - 1; layer_nr > 0; layer_nr--){
|
||||
auto layer_contact_nodes = contact_nodes[layer_nr];
|
||||
|
|
@ -3053,16 +3149,8 @@ void TreeSupport::smooth_nodes(std::vector<std::vector<Node *>> &contact_nodes)
|
|||
}
|
||||
}
|
||||
}
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
// save tree structure for viewing in python
|
||||
struct TreeNode {
|
||||
Vec3f pos;
|
||||
std::vector<int> children; // index of children in the storing vector
|
||||
TreeNode(Point pt, float z) {
|
||||
pos = { float(unscale_(pt.x())),float(unscale_(pt.y())),z };
|
||||
}
|
||||
};
|
||||
std::vector<TreeNode> tree_nodes;
|
||||
auto& tree_nodes = m_ts_data->tree_nodes;
|
||||
std::map<Node*, int> ptr2idx;
|
||||
std::map<int, Node*> idx2ptr;
|
||||
for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) {
|
||||
|
|
@ -3078,12 +3166,16 @@ void TreeSupport::smooth_nodes(std::vector<std::vector<Node *>> &contact_nodes)
|
|||
Node* p_node = idx2ptr[i];
|
||||
if (p_node->child)
|
||||
tree_node.children.push_back(ptr2idx[p_node->child]);
|
||||
if(p_node->parent)
|
||||
tree_node.parents.push_back(ptr2idx[p_node->parent]);
|
||||
}
|
||||
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
||||
nlohmann::json jj;
|
||||
for (size_t i = 0; i < tree_nodes.size(); i++) {
|
||||
nlohmann::json j;
|
||||
j["pos"] = tree_nodes[i].pos;
|
||||
j["children"] = tree_nodes[i].children;
|
||||
j["linked"] = !(tree_nodes[i].pos.z() > 0.205 && tree_nodes[i].children.empty());
|
||||
jj.push_back(j);
|
||||
}
|
||||
|
||||
|
|
@ -3356,12 +3448,14 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
|
|||
Point candidate = overhang_bounds.center();
|
||||
if (!overhang_part.contains(candidate))
|
||||
move_inside_expoly(overhang_part, candidate);
|
||||
Node *contact_node = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, true, Node::NO_PARENT, print_z,
|
||||
height, z_distance_top);
|
||||
contact_node->type = ePolygon;
|
||||
contact_node->overhang = &overhang_part;
|
||||
curr_nodes.emplace_back(contact_node);
|
||||
continue;
|
||||
if (!(config.support_on_build_plate_only && is_inside_ex(m_ts_data->m_layer_outlines_below[layer_nr], candidate))) {
|
||||
Node* contact_node = new Node(candidate, -z_distance_top_layers, layer_nr, support_roof_layers + z_distance_top_layers, true, Node::NO_PARENT, print_z,
|
||||
height, z_distance_top);
|
||||
contact_node->type = ePolygon;
|
||||
contact_node->overhang = &overhang_part;
|
||||
curr_nodes.emplace_back(contact_node);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
overhang_bounds.inflated(half_overhang_distance);
|
||||
|
|
@ -3413,7 +3507,8 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
|
|||
curr_nodes.emplace_back(contact_node);
|
||||
}
|
||||
}
|
||||
if (ts_layer->overhang_types[&overhang_part] == SupportLayer::Detected) {
|
||||
// add supports at corners for both auto and manual overhangs, github #2008
|
||||
if (/*ts_layer->overhang_types[&overhang_part] == SupportLayer::Detected*/1) {
|
||||
// add points at corners
|
||||
auto &points = overhang_part.contour.points;
|
||||
int nSize = points.size();
|
||||
|
|
@ -3589,7 +3684,6 @@ const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& ke
|
|||
const auto& radius = key.radius;
|
||||
const auto& layer_nr = key.layer_nr;
|
||||
BOOST_LOG_TRIVIAL(debug) << "calculate_avoidance on radius=" << radius << ", layer=" << layer_nr<<", recursion="<<key.recursions;
|
||||
std::pair<tbb::concurrent_unordered_map<RadiusLayerPair, ExPolygons, RadiusLayerPairHash, RadiusLayerPairEquality>::iterator,bool> ret;
|
||||
constexpr auto max_recursion_depth = 100;
|
||||
if (key.recursions <= max_recursion_depth*2) {
|
||||
if (layer_nr == 0) {
|
||||
|
|
@ -3616,14 +3710,15 @@ const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& ke
|
|||
const ExPolygons &collision = get_collision(radius, layer_nr);
|
||||
avoidance_areas.insert(avoidance_areas.end(), collision.begin(), collision.end());
|
||||
avoidance_areas = std::move(union_ex(avoidance_areas));
|
||||
ret = m_avoidance_cache.insert({key, std::move(avoidance_areas)});
|
||||
auto ret = m_avoidance_cache.insert({ key, std::move(avoidance_areas) });
|
||||
//assert(ret.second);
|
||||
return ret.first->second;
|
||||
} else {
|
||||
ExPolygons avoidance_areas = std::move(offset_ex(m_layer_outlines_below[layer_nr], scale_(m_xy_distance + radius)));
|
||||
ret = m_avoidance_cache.insert({key, std::move(avoidance_areas)});
|
||||
auto ret = m_avoidance_cache.insert({ key, std::move(avoidance_areas) });
|
||||
assert(ret.second);
|
||||
return ret.first->second;
|
||||
}
|
||||
return ret.first->second;
|
||||
}
|
||||
|
||||
} //namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace Slic3r
|
|||
{
|
||||
class PrintObject;
|
||||
class TreeSupport;
|
||||
class SupportLayer;
|
||||
|
||||
struct LayerHeightData
|
||||
{
|
||||
|
|
@ -30,6 +31,15 @@ struct LayerHeightData
|
|||
LayerHeightData(coordf_t z, coordf_t h, size_t next_layer) : print_z(z), height(h), next_layer_nr(next_layer) {}
|
||||
};
|
||||
|
||||
struct TreeNode {
|
||||
Vec3f pos;
|
||||
std::vector<int> children; // index of children in the storing vector
|
||||
std::vector<int> parents; // index of parents in the storing vector
|
||||
TreeNode(Point pt, float z) {
|
||||
pos = { float(unscale_(pt.x())),float(unscale_(pt.y())),z };
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Lazily generates tree guidance volumes.
|
||||
*
|
||||
|
|
@ -90,6 +100,8 @@ public:
|
|||
|
||||
std::vector<LayerHeightData> layer_heights;
|
||||
|
||||
std::vector<TreeNode> tree_nodes;
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief Convenience typedef for the keys to the caches
|
||||
|
|
@ -483,6 +495,14 @@ private:
|
|||
Polygons contact_nodes_to_polygon(const std::vector<Node*>& contact_nodes, Polygons layer_contours, int layer_nr, std::vector<double>& radiis, std::vector<bool>& is_interface);
|
||||
coordf_t calc_branch_radius(coordf_t base_radius, size_t layers_to_top, size_t tip_layers, double diameter_angle_scale_factor);
|
||||
coordf_t calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor);
|
||||
|
||||
// similar to SupportMaterial::trim_support_layers_by_object
|
||||
Polygons get_trim_support_regions(
|
||||
const PrintObject& object,
|
||||
SupportLayer* support_layer_ptr,
|
||||
const coordf_t gap_extra_above,
|
||||
const coordf_t gap_extra_below,
|
||||
const coordf_t gap_xy);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@
|
|||
#define CLI_PROCESS_NOT_COMPATIBLE -17
|
||||
#define CLI_INVALID_VALUES_IN_3MF -18
|
||||
#define CLI_POSTPROCESS_NOT_SUPPORTED -19
|
||||
#define CLI_PRINTABLE_SIZE_REDUCED -20
|
||||
#define CLI_OBJECT_ARRANGE_FAILED -21
|
||||
#define CLI_OBJECT_ORIENT_FAILED -22
|
||||
|
||||
|
||||
#define CLI_NO_SUITABLE_OBJECTS -50
|
||||
|
|
@ -53,6 +56,10 @@
|
|||
#define CLI_SLICING_TIME_EXCEEDS_LIMIT -58
|
||||
#define CLI_TRIANGLE_COUNT_EXCEEDS_LIMIT -59
|
||||
#define CLI_NO_SUITABLE_OBJECTS_AFTER_SKIP -60
|
||||
#define CLI_FILAMENT_NOT_MATCH_BED_TYPE -61
|
||||
#define CLI_FILAMENTS_DIFFERENT_TEMP -62
|
||||
#define CLI_OBJECT_COLLISION_IN_SEQ_PRINT -63
|
||||
#define CLI_OBJECT_COLLISION_IN_LAYER_PRINT -64
|
||||
|
||||
#define CLI_SLICING_ERROR -100
|
||||
#define CLI_GCODE_PATH_CONFLICTS -101
|
||||
|
|
@ -590,6 +597,19 @@ inline std::string get_bbl_remain_time_dhms(float time_in_secs)
|
|||
|
||||
bool bbl_calc_md5(std::string &filename, std::string &md5_out);
|
||||
|
||||
inline std::string filter_characters(const std::string& str, const std::string& filterChars)
|
||||
{
|
||||
std::string filteredStr = str;
|
||||
|
||||
auto removeFunc = [&filterChars](char ch) {
|
||||
return filterChars.find(ch) != std::string::npos;
|
||||
};
|
||||
|
||||
filteredStr.erase(std::remove_if(filteredStr.begin(), filteredStr.end(), removeFunc), filteredStr.end());
|
||||
|
||||
return filteredStr;
|
||||
}
|
||||
|
||||
void copy_directory_recursively(const boost::filesystem::path &source, const boost::filesystem::path &target);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <type_traits>
|
||||
#include <optional>
|
||||
|
||||
#ifdef _WIN32
|
||||
// On MSVC, std::deque degenerates to a list of pointers, which defeats its purpose of reducing allocator load and memory fragmentation.
|
||||
|
|
@ -147,6 +148,15 @@ inline void append(std::vector<T>& dest, std::vector<T>&& src)
|
|||
src.shrink_to_fit();
|
||||
}
|
||||
|
||||
template<class T, class... Args> // Arbitrary allocator can be used
|
||||
void clear_and_shrink(std::vector<T, Args...>& vec)
|
||||
{
|
||||
// shrink_to_fit does not garantee the release of memory nor does it clear()
|
||||
std::vector<T, Args...> tmp;
|
||||
vec.swap(tmp);
|
||||
assert(vec.capacity() == 0);
|
||||
}
|
||||
|
||||
// Append the source in reverse.
|
||||
template <typename T>
|
||||
inline void append_reversed(std::vector<T>& dest, const std::vector<T>& src)
|
||||
|
|
@ -274,9 +284,17 @@ constexpr inline T lerp(const T& a, const T& b, Number t)
|
|||
}
|
||||
|
||||
template <typename Number>
|
||||
constexpr inline bool is_approx(Number value, Number test_value)
|
||||
constexpr inline bool is_approx(Number value, Number test_value, Number precision = EPSILON)
|
||||
{
|
||||
return std::fabs(double(value) - double(test_value)) < double(EPSILON);
|
||||
return std::fabs(double(value) - double(test_value)) < double(precision);
|
||||
}
|
||||
|
||||
template<typename Number>
|
||||
constexpr inline bool is_approx(const std::optional<Number> &value,
|
||||
const std::optional<Number> &test_value)
|
||||
{
|
||||
return (!value.has_value() && !test_value.has_value()) ||
|
||||
(value.has_value() && test_value.has_value() && is_approx<Number>(*value, *test_value));
|
||||
}
|
||||
|
||||
// A meta-predicate which is true for integers wider than or equal to coord_t
|
||||
|
|
@ -347,16 +365,42 @@ public:
|
|||
Range(It b, It e) : from(std::move(b)), to(std::move(e)) {}
|
||||
|
||||
// Some useful container-like methods...
|
||||
inline size_t size() const { return end() - begin(); }
|
||||
inline bool empty() const { return size() == 0; }
|
||||
inline size_t size() const { return std::distance(from, to); }
|
||||
inline bool empty() const { return from == to; }
|
||||
};
|
||||
|
||||
template<class Cont> auto range(Cont &&cont)
|
||||
{
|
||||
return Range{std::begin(cont), std::end(cont)};
|
||||
}
|
||||
|
||||
template<class T, class = FloatingOnly<T>>
|
||||
constexpr T NaN = std::numeric_limits<T>::quiet_NaN();
|
||||
|
||||
constexpr float NaNf = NaN<float>;
|
||||
constexpr double NaNd = NaN<double>;
|
||||
|
||||
// Rounding up.
|
||||
// 1.5 is rounded to 2
|
||||
// 1.49 is rounded to 1
|
||||
// 0.5 is rounded to 1,
|
||||
// 0.49 is rounded to 0
|
||||
// -0.5 is rounded to 0,
|
||||
// -0.51 is rounded to -1,
|
||||
// -1.5 is rounded to -1.
|
||||
// -1.51 is rounded to -2.
|
||||
// If input is not a valid float (it is infinity NaN or if it does not fit)
|
||||
// the float to int conversion produces a max int on Intel and +-max int on ARM.
|
||||
template<typename I>
|
||||
inline IntegerOnly<I, I> fast_round_up(double a)
|
||||
{
|
||||
// Why does Java Math.round(0.49999999999999994) return 1?
|
||||
// https://stackoverflow.com/questions/9902968/why-does-math-round0-49999999999999994-return-1
|
||||
return a == 0.49999999999999994 ? I(0) : I(floor(a + 0.5));
|
||||
}
|
||||
|
||||
template<class T> using SamePair = std::pair<T, T>;
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue