Merge some BS1.7 changes:

internal_solid_infill_pattern
This commit is contained in:
SoftFever 2023-08-08 18:34:21 +08:00
parent 7ece35931e
commit bcbbbf35db
95 changed files with 44122 additions and 693 deletions

322
src/libslic3r/AABBMesh.cpp Normal file
View file

@ -0,0 +1,322 @@
#include "AABBMesh.hpp"
#include <Execution/ExecutionTBB.hpp>
#include <libslic3r/AABBTreeIndirect.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <numeric>
#ifdef SLIC3R_HOLE_RAYCASTER
#include <libslic3r/SLA/Hollowing.hpp>
#endif
namespace Slic3r {
class AABBMesh::AABBImpl {
private:
AABBTreeIndirect::Tree3f m_tree;
double m_triangle_ray_epsilon;
public:
void init(const indexed_triangle_set &its, bool calculate_epsilon)
{
m_triangle_ray_epsilon = 0.000001;
if (calculate_epsilon) {
// Calculate epsilon from average triangle edge length.
double l = its_average_edge_length(its);
if (l > 0)
m_triangle_ray_epsilon = 0.000001 * l * l;
}
m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
its.vertices, its.indices);
}
void intersect_ray(const indexed_triangle_set &its,
const Vec3d & s,
const Vec3d & dir,
igl::Hit & hit)
{
AABBTreeIndirect::intersect_ray_first_hit(its.vertices, its.indices,
m_tree, s, dir, hit, m_triangle_ray_epsilon);
}
void intersect_ray(const indexed_triangle_set &its,
const Vec3d & s,
const Vec3d & dir,
std::vector<igl::Hit> & hits)
{
AABBTreeIndirect::intersect_ray_all_hits(its.vertices, its.indices,
m_tree, s, dir, hits, m_triangle_ray_epsilon);
}
double squared_distance(const indexed_triangle_set & its,
const Vec3d & point,
int & i,
Eigen::Matrix<double, 1, 3> &closest)
{
size_t idx_unsigned = 0;
Vec3d closest_vec3d(closest);
double dist =
AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
its.vertices, its.indices, m_tree, point, idx_unsigned,
closest_vec3d);
i = int(idx_unsigned);
closest = closest_vec3d;
return dist;
}
};
template<class M> void AABBMesh::init(const M &mesh, bool calculate_epsilon)
{
// Build the AABB accelaration tree
m_aabb->init(*m_tm, calculate_epsilon);
}
AABBMesh::AABBMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon)
: m_tm(&tmesh)
, m_aabb(new AABBImpl())
, m_vfidx{tmesh}
, m_fnidx{its_face_neighbors(tmesh)}
{
init(tmesh, calculate_epsilon);
}
AABBMesh::AABBMesh(const TriangleMesh &mesh, bool calculate_epsilon)
: m_tm(&mesh.its)
, m_aabb(new AABBImpl())
, m_vfidx{mesh.its}
, m_fnidx{its_face_neighbors(mesh.its)}
{
init(mesh, calculate_epsilon);
}
AABBMesh::~AABBMesh() {}
AABBMesh::AABBMesh(const AABBMesh &other)
: m_tm(other.m_tm)
, m_aabb(new AABBImpl(*other.m_aabb))
, m_vfidx{other.m_vfidx}
, m_fnidx{other.m_fnidx}
{}
AABBMesh &AABBMesh::operator=(const AABBMesh &other)
{
m_tm = other.m_tm;
m_aabb.reset(new AABBImpl(*other.m_aabb));
m_vfidx = other.m_vfidx;
m_fnidx = other.m_fnidx;
return *this;
}
AABBMesh &AABBMesh::operator=(AABBMesh &&other) = default;
AABBMesh::AABBMesh(AABBMesh &&other) = default;
const std::vector<Vec3f>& AABBMesh::vertices() const
{
return m_tm->vertices;
}
const std::vector<Vec3i>& AABBMesh::indices() const
{
return m_tm->indices;
}
const Vec3f& AABBMesh::vertices(size_t idx) const
{
return m_tm->vertices[idx];
}
const Vec3i& AABBMesh::indices(size_t idx) const
{
return m_tm->indices[idx];
}
Vec3d AABBMesh::normal_by_face_id(int face_id) const {
return its_unnormalized_normal(*m_tm, face_id).cast<double>().normalized();
}
AABBMesh::hit_result
AABBMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
{
assert(is_approx(dir.norm(), 1.));
igl::Hit hit{-1, -1, 0.f, 0.f, 0.f};
hit.t = std::numeric_limits<float>::infinity();
#ifdef SLIC3R_HOLE_RAYCASTER
if (! m_holes.empty()) {
// If there are holes, the hit_results will be made by
// query_ray_hits (object) and filter_hits (holes):
return filter_hits(query_ray_hits(s, dir));
}
#endif
m_aabb->intersect_ray(*m_tm, s, dir, hit);
hit_result ret(*this);
ret.m_t = double(hit.t);
ret.m_dir = dir;
ret.m_source = s;
if(!std::isinf(hit.t) && !std::isnan(hit.t)) {
ret.m_normal = this->normal_by_face_id(hit.id);
ret.m_face_id = hit.id;
}
return ret;
}
std::vector<AABBMesh::hit_result>
AABBMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
{
std::vector<AABBMesh::hit_result> outs;
std::vector<igl::Hit> hits;
m_aabb->intersect_ray(*m_tm, s, dir, hits);
// The sort is necessary, the hits are not always sorted.
std::sort(hits.begin(), hits.end(),
[](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
// Remove duplicates. They sometimes appear, for example when the ray is cast
// along an axis of a cube due to floating-point approximations in igl (?)
hits.erase(std::unique(hits.begin(), hits.end(),
[](const igl::Hit& a, const igl::Hit& b)
{ return a.t == b.t; }),
hits.end());
// Convert the igl::Hit into hit_result
outs.reserve(hits.size());
for (const igl::Hit& hit : hits) {
outs.emplace_back(AABBMesh::hit_result(*this));
outs.back().m_t = double(hit.t);
outs.back().m_dir = dir;
outs.back().m_source = s;
if(!std::isinf(hit.t) && !std::isnan(hit.t)) {
outs.back().m_normal = this->normal_by_face_id(hit.id);
outs.back().m_face_id = hit.id;
}
}
return outs;
}
#ifdef SLIC3R_HOLE_RAYCASTER
AABBMesh::hit_result IndexedMesh::filter_hits(
const std::vector<AABBMesh::hit_result>& object_hits) const
{
assert(! m_holes.empty());
hit_result out(*this);
if (object_hits.empty())
return out;
const Vec3d& s = object_hits.front().source();
const Vec3d& dir = object_hits.front().direction();
// A helper struct to save an intersetion with a hole
struct HoleHit {
HoleHit(float t_p, const Vec3d& normal_p, bool entry_p) :
t(t_p), normal(normal_p), entry(entry_p) {}
float t;
Vec3d normal;
bool entry;
};
std::vector<HoleHit> hole_isects;
hole_isects.reserve(m_holes.size());
auto sf = s.cast<float>();
auto dirf = dir.cast<float>();
// Collect hits on all holes, preserve information about entry/exit
for (const sla::DrainHole& hole : m_holes) {
std::array<std::pair<float, Vec3d>, 2> isects;
if (hole.get_intersections(sf, dirf, isects)) {
// Ignore hole hits behind the source
if (isects[0].first > 0.f) hole_isects.emplace_back(isects[0].first, isects[0].second, true);
if (isects[1].first > 0.f) hole_isects.emplace_back(isects[1].first, isects[1].second, false);
}
}
// Holes can intersect each other, sort the hits by t
std::sort(hole_isects.begin(), hole_isects.end(),
[](const HoleHit& a, const HoleHit& b) { return a.t < b.t; });
// Now inspect the intersections with object and holes, in the order of
// increasing distance. Keep track how deep are we nested in mesh/holes and
// pick the correct intersection.
// This needs to be done twice - first to find out how deep in the structure
// the source is, then to pick the correct intersection.
int hole_nested = 0;
int object_nested = 0;
for (int dry_run=1; dry_run>=0; --dry_run) {
hole_nested = -hole_nested;
object_nested = -object_nested;
bool is_hole = false;
bool is_entry = false;
const HoleHit* next_hole_hit = hole_isects.empty() ? nullptr : &hole_isects.front();
const hit_result* next_mesh_hit = &object_hits.front();
while (next_hole_hit || next_mesh_hit) {
if (next_hole_hit && next_mesh_hit) // still have hole and obj hits
is_hole = (next_hole_hit->t < next_mesh_hit->m_t);
else
is_hole = next_hole_hit; // one or the other ran out
// Is this entry or exit hit?
is_entry = is_hole ? next_hole_hit->entry : ! next_mesh_hit->is_inside();
if (! dry_run) {
if (! is_hole && hole_nested == 0) {
// This is a valid object hit
return *next_mesh_hit;
}
if (is_hole && ! is_entry && object_nested != 0) {
// This holehit is the one we seek
out.m_t = next_hole_hit->t;
out.m_normal = next_hole_hit->normal;
out.m_source = s;
out.m_dir = dir;
return out;
}
}
// Increase/decrease the counter
(is_hole ? hole_nested : object_nested) += (is_entry ? 1 : -1);
// Advance the respective pointer
if (is_hole && next_hole_hit++ == &hole_isects.back())
next_hole_hit = nullptr;
if (! is_hole && next_mesh_hit++ == &object_hits.back())
next_mesh_hit = nullptr;
}
}
// if we got here, the ray ended up in infinity
return out;
}
#endif
double AABBMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
double sqdst = 0;
Eigen::Matrix<double, 1, 3> pp = p;
Eigen::Matrix<double, 1, 3> cc;
sqdst = m_aabb->squared_distance(*m_tm, pp, i, cc);
c = cc;
return sqdst;
}
} // namespace Slic3r

142
src/libslic3r/AABBMesh.hpp Normal file
View file

@ -0,0 +1,142 @@
#ifndef PRUSASLICER_AABBMESH_H
#define PRUSASLICER_AABBMESH_H
#include <memory>
#include <vector>
#include <libslic3r/Point.hpp>
#include <libslic3r/TriangleMesh.hpp>
// There is an implementation of a hole-aware raycaster that was eventually
// not used in production version. It is now hidden under following define
// for possible future use.
// #define SLIC3R_HOLE_RAYCASTER
#ifdef SLIC3R_HOLE_RAYCASTER
#include "libslic3r/SLA/Hollowing.hpp"
#endif
struct indexed_triangle_set;
namespace Slic3r {
class TriangleMesh;
// An index-triangle structure coupled with an AABB index to support ray
// casting and other higher level operations.
class AABBMesh {
class AABBImpl;
const indexed_triangle_set* m_tm;
std::unique_ptr<AABBImpl> m_aabb;
VertexFaceIndex m_vfidx; // vertex-face index
std::vector<Vec3i> m_fnidx; // face-neighbor index
#ifdef SLIC3R_HOLE_RAYCASTER
// This holds a copy of holes in the mesh. Initialized externally
// by load_mesh setter.
std::vector<sla::DrainHole> m_holes;
#endif
template<class M> void init(const M &mesh, bool calculate_epsilon);
public:
// calculate_epsilon ... calculate epsilon for triangle-ray intersection from an average triangle edge length.
// If set to false, a default epsilon is used, which works for "reasonable" meshes.
explicit AABBMesh(const indexed_triangle_set &tmesh, bool calculate_epsilon = false);
explicit AABBMesh(const TriangleMesh &mesh, bool calculate_epsilon = false);
AABBMesh(const AABBMesh& other);
AABBMesh& operator=(const AABBMesh&);
AABBMesh(AABBMesh &&other);
AABBMesh& operator=(AABBMesh &&other);
~AABBMesh();
const std::vector<Vec3f>& vertices() const;
const std::vector<Vec3i>& indices() const;
const Vec3f& vertices(size_t idx) const;
const Vec3i& indices(size_t idx) const;
// Result of a raycast
class hit_result {
// m_t holds a distance from m_source to the intersection.
double m_t = infty();
int m_face_id = -1;
const AABBMesh *m_mesh = nullptr;
Vec3d m_dir = Vec3d::Zero();
Vec3d m_source = Vec3d::Zero();
Vec3d m_normal = Vec3d::Zero();
friend class AABBMesh;
// A valid object of this class can only be obtained from
// IndexedMesh::query_ray_hit method.
explicit inline hit_result(const AABBMesh& em): m_mesh(&em) {}
public:
// This denotes no hit on the mesh.
static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); }
explicit inline hit_result(double val = infty()) : m_t(val) {}
inline double distance() const { return m_t; }
inline const Vec3d& direction() const { return m_dir; }
inline const Vec3d& source() const { return m_source; }
inline Vec3d position() const { return m_source + m_dir * m_t; }
inline int face() const { return m_face_id; }
inline bool is_valid() const { return m_mesh != nullptr; }
inline bool is_hit() const { return m_face_id >= 0 && !std::isinf(m_t); }
inline const Vec3d& normal() const {
assert(is_valid());
return m_normal;
}
inline bool is_inside() const {
return is_hit() && normal().dot(m_dir) > 0;
}
};
#ifdef SLIC3R_HOLE_RAYCASTER
// Inform the object about location of holes
// creates internal copy of the vector
void load_holes(const std::vector<sla::DrainHole>& holes) {
m_holes = holes;
}
// Iterates over hits and holes and returns the true hit, possibly
// on the inside of a hole.
// This function is currently not used anywhere, it was written when the
// holes were subtracted on slices, that is, before we started using CGAL
// to actually cut the holes into the mesh.
hit_result filter_hits(const std::vector<AABBMesh::hit_result>& obj_hits) const;
#endif
// Casting a ray on the mesh, returns the distance where the hit occures.
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
// Casts a ray on the mesh and returns all hits
std::vector<hit_result> query_ray_hits(const Vec3d &s, const Vec3d &dir) const;
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
inline double squared_distance(const Vec3d &p) const
{
int i;
Vec3d c;
return squared_distance(p, i, c);
}
Vec3d normal_by_face_id(int face_id) const;
const indexed_triangle_set * get_triangle_mesh() const { return m_tm; }
const VertexFaceIndex &vertex_face_index() const { return m_vfidx; }
const std::vector<Vec3i> &face_neighbor_index() const { return m_fnidx; }
};
} // namespace Slic3r::sla
#endif // INDEXEDMESH_H

130
src/libslic3r/AnyPtr.hpp Normal file
View file

@ -0,0 +1,130 @@
#ifndef ANYPTR_HPP
#define ANYPTR_HPP
#include <memory>
#include <type_traits>
#include <boost/variant.hpp>
namespace Slic3r {
// A general purpose pointer holder that can hold any type of smart pointer
// or raw pointer which can own or not own any object they point to.
// In case a raw pointer is stored, it is not destructed so ownership is
// assumed to be foreign.
//
// The stored pointer is not checked for being null when dereferenced.
//
// This is a movable only object due to the fact that it can possibly hold
// a unique_ptr which a non-copy.
template<class T>
class AnyPtr {
enum { RawPtr, UPtr, ShPtr, WkPtr };
boost::variant<T*, std::unique_ptr<T>, std::shared_ptr<T>, std::weak_ptr<T>> ptr;
template<class Self> static T *get_ptr(Self &&s)
{
switch (s.ptr.which()) {
case RawPtr: return boost::get<T *>(s.ptr);
case UPtr: return boost::get<std::unique_ptr<T>>(s.ptr).get();
case ShPtr: return boost::get<std::shared_ptr<T>>(s.ptr).get();
case WkPtr: {
auto shptr = boost::get<std::weak_ptr<T>>(s.ptr).lock();
return shptr.get();
}
}
return nullptr;
}
public:
template<class TT = T, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr(TT *p = nullptr) : ptr{p}
{}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr(std::unique_ptr<TT> p) : ptr{std::unique_ptr<T>(std::move(p))}
{}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr(std::shared_ptr<TT> p) : ptr{std::shared_ptr<T>(std::move(p))}
{}
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr(std::weak_ptr<TT> p) : ptr{std::weak_ptr<T>(std::move(p))}
{}
~AnyPtr() = default;
AnyPtr(AnyPtr &&other) noexcept : ptr{std::move(other.ptr)} {}
AnyPtr(const AnyPtr &other) = delete;
AnyPtr &operator=(AnyPtr &&other) noexcept { ptr = std::move(other.ptr); return *this; }
AnyPtr &operator=(const AnyPtr &other) = delete;
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr &operator=(TT *p) { ptr = p; return *this; }
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr &operator=(std::unique_ptr<TT> p) { ptr = std::move(p); return *this; }
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr &operator=(std::shared_ptr<TT> p) { ptr = p; return *this; }
template<class TT, class = std::enable_if_t<std::is_convertible_v<TT, T>>>
AnyPtr &operator=(std::weak_ptr<TT> p) { ptr = std::move(p); return *this; }
const T &operator*() const { return *get_ptr(*this); }
T &operator*() { return *get_ptr(*this); }
T *operator->() { return get_ptr(*this); }
const T *operator->() const { return get_ptr(*this); }
T *get() { return get_ptr(*this); }
const T *get() const { return get_ptr(*this); }
operator bool() const
{
switch (ptr.which()) {
case RawPtr: return bool(boost::get<T *>(ptr));
case UPtr: return bool(boost::get<std::unique_ptr<T>>(ptr));
case ShPtr: return bool(boost::get<std::shared_ptr<T>>(ptr));
case WkPtr: {
auto shptr = boost::get<std::weak_ptr<T>>(ptr).lock();
return bool(shptr);
}
}
return false;
}
// If the stored pointer is a shared or weak pointer, returns a reference
// counted copy. Empty shared pointer is returned otherwise.
std::shared_ptr<T> get_shared_cpy() const
{
std::shared_ptr<T> ret;
switch (ptr.which()) {
case ShPtr: ret = boost::get<std::shared_ptr<T>>(ptr); break;
case WkPtr: ret = boost::get<std::weak_ptr<T>>(ptr).lock(); break;
default:
;
}
return ret;
}
// If the underlying pointer is unique, convert to shared pointer
void convert_unique_to_shared()
{
if (ptr.which() == UPtr)
ptr = std::shared_ptr<T>{std::move(boost::get<std::unique_ptr<T>>(ptr))};
}
// Returns true if the data is owned by this AnyPtr instance
bool is_owned() const noexcept
{
return ptr.which() == UPtr || ptr.which() == ShPtr;
}
};
} // namespace Slic3r
#endif // ANYPTR_HPP

View file

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

View file

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

View file

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

View 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

View file

@ -0,0 +1,80 @@
#ifndef CSGMESHCOPY_HPP
#define CSGMESHCOPY_HPP
#include "CSGMesh.hpp"
namespace Slic3r { namespace csg {
// Copy a csg range but for the meshes, only copy the pointers. If the copy
// is made from a CSGPart compatible object, and the pointer is a shared one,
// it will be copied with reference counting.
template<class It, class OutIt>
void copy_csgrange_shallow(const Range<It> &csgrange, OutIt out)
{
for (const auto &part : csgrange) {
CSGPart cpy{{},
get_operation(part),
get_transform(part)};
cpy.stack_operation = get_stack_operation(part);
if constexpr (std::is_convertible_v<decltype(part), const CSGPart&>) {
if (auto shptr = part.its_ptr.get_shared_cpy()) {
cpy.its_ptr = shptr;
}
}
if (!cpy.its_ptr)
cpy.its_ptr = AnyPtr<const indexed_triangle_set>{get_mesh(part)};
*out = std::move(cpy);
++out;
}
}
// Copy the csg range, allocating new meshes
template<class It, class OutIt>
void copy_csgrange_deep(const Range<It> &csgrange, OutIt out)
{
for (const auto &part : csgrange) {
CSGPart cpy{{}, get_operation(part), get_transform(part)};
if (auto meshptr = get_mesh(part)) {
cpy.its_ptr = std::make_unique<const indexed_triangle_set>(*meshptr);
}
cpy.stack_operation = get_stack_operation(part);
*out = std::move(cpy);
++out;
}
}
template<class ItA, class ItB>
bool is_same(const Range<ItA> &A, const Range<ItB> &B)
{
bool ret = true;
size_t s = A.size();
if (B.size() != s)
ret = false;
size_t i = 0;
auto itA = A.begin();
auto itB = B.begin();
for (; ret && i < s; ++itA, ++itB, ++i) {
ret = ret &&
get_mesh(*itA) == get_mesh(*itB) &&
get_operation(*itA) == get_operation(*itB) &&
get_stack_operation(*itA) == get_stack_operation(*itB) &&
get_transform(*itA).isApprox(get_transform(*itB));
}
return ret;
}
}} // namespace Slic3r::csg
#endif // CSGCOPY_HPP

View file

@ -0,0 +1,92 @@
#ifndef MODELTOCSGMESH_HPP
#define MODELTOCSGMESH_HPP
#include "CSGMesh.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/SLA/Hollowing.hpp"
#include "libslic3r/MeshSplitImpl.hpp"
namespace Slic3r { namespace csg {
// Flags to select which parts to export from Model into a csg part collection.
// These flags can be chained with the | operator
enum ModelParts {
mpartsPositive = 1, // Include positive parts
mpartsNegative = 2, // Include negative parts
mpartsDrillHoles = 4, // Include drill holes
mpartsDoSplits = 8, // Split each splitable mesh and export as a union of csg parts
};
template<class OutIt>
bool model_to_csgmesh(const ModelObject &mo,
const Transform3d &trafo, // Applies to all exported parts
OutIt out, // Output iterator
// values of ModelParts OR-ed
int parts_to_include = mpartsPositive
)
{
bool do_positives = parts_to_include & mpartsPositive;
bool do_negatives = parts_to_include & mpartsNegative;
bool do_drillholes = parts_to_include & mpartsDrillHoles;
bool do_splits = parts_to_include & mpartsDoSplits;
bool has_splitable_volume = false;
for (const ModelVolume *vol : mo.volumes) {
if (vol && vol->mesh_ptr() &&
((do_positives && vol->is_model_part()) ||
(do_negatives && vol->is_negative_volume()))) {
if (do_splits && its_is_splittable(vol->mesh().its)) {
CSGPart part_begin{{}, vol->is_model_part() ? CSGType::Union : CSGType::Difference};
part_begin.stack_operation = CSGStackOp::Push;
*out = std::move(part_begin);
++out;
its_split(vol->mesh().its, SplitOutputFn{[&out, &vol, &trafo](indexed_triangle_set &&its) {
if (its.empty())
return;
CSGPart part{std::make_unique<indexed_triangle_set>(std::move(its)),
CSGType::Union,
(trafo * vol->get_matrix()).cast<float>()};
*out = std::move(part);
++out;
}});
CSGPart part_end{{}};
part_end.stack_operation = CSGStackOp::Pop;
*out = std::move(part_end);
++out;
has_splitable_volume = true;
} else {
CSGPart part{&(vol->mesh().its),
vol->is_model_part() ? CSGType::Union : CSGType::Difference,
(trafo * vol->get_matrix()).cast<float>()};
*out = std::move(part);
++out;
}
}
}
//if (do_drillholes) {
// sla::DrainHoles drainholes = sla::transformed_drainhole_points(mo, trafo);
// for (const sla::DrainHole &dhole : drainholes) {
// CSGPart part{std::make_unique<const indexed_triangle_set>(
// dhole.to_mesh()),
// CSGType::Difference};
// *out = std::move(part);
// ++out;
// }
//}
return has_splitable_volume;
}
}} // namespace Slic3r::csg
#endif // MODELTOCSGMESH_HPP

View file

@ -0,0 +1,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

View file

@ -0,0 +1,131 @@
#ifndef SLICECSGMESH_HPP
#define SLICECSGMESH_HPP
#include "CSGMesh.hpp"
#include <stack>
#include "libslic3r/TriangleMeshSlicer.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
namespace Slic3r { namespace csg {
namespace detail {
inline void merge_slices(csg::CSGType op, size_t i,
std::vector<ExPolygons> &target,
std::vector<ExPolygons> &source)
{
switch(op) {
case CSGType::Union:
for (ExPolygon &expoly : source[i])
target[i].emplace_back(std::move(expoly));
break;
case CSGType::Difference:
target[i] = diff_ex(target[i], source[i]);
break;
case CSGType::Intersection:
target[i] = intersection_ex(target[i], source[i]);
break;
}
}
inline void collect_nonempty_indices(csg::CSGType op,
const std::vector<float> &slicegrid,
const std::vector<ExPolygons> &slices,
std::vector<size_t> &indices)
{
indices.clear();
for (size_t i = 0; i < slicegrid.size(); ++i) {
if (op == CSGType::Intersection || !slices[i].empty())
indices.emplace_back(i);
}
}
} // namespace detail
template<class ItCSG>
std::vector<ExPolygons> slice_csgmesh_ex(
const Range<ItCSG> &csgrange,
const std::vector<float> &slicegrid,
const MeshSlicingParamsEx &params,
const std::function<void()> &throw_on_cancel = [] {})
{
using namespace detail;
struct Frame { CSGType op; std::vector<ExPolygons> slices; };
std::stack opstack{std::vector<Frame>{}};
MeshSlicingParamsEx params_cpy = params;
auto trafo = params.trafo;
auto nonempty_indices = reserve_vector<size_t>(slicegrid.size());
opstack.push({CSGType::Union, std::vector<ExPolygons>(slicegrid.size())});
for (const auto &csgpart : csgrange) {
const indexed_triangle_set *its = csg::get_mesh(csgpart);
auto op = get_operation(csgpart);
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
opstack.push({op, std::vector<ExPolygons>(slicegrid.size())});
op = CSGType::Union;
}
Frame *top = &opstack.top();
if (its) {
params_cpy.trafo = trafo * csg::get_transform(csgpart).template cast<double>();
std::vector<ExPolygons> slices = slice_mesh_ex(*its,
slicegrid, params_cpy,
throw_on_cancel);
assert(slices.size() == slicegrid.size());
collect_nonempty_indices(op, slicegrid, slices, nonempty_indices);
execution::for_each(
ex_tbb, nonempty_indices.begin(), nonempty_indices.end(),
[op, &slices, &top](size_t i) {
merge_slices(op, i, top->slices, slices);
}, execution::max_concurrency(ex_tbb));
}
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
std::vector<ExPolygons> popslices = std::move(top->slices);
auto popop = opstack.top().op;
opstack.pop();
std::vector<ExPolygons> &prev_slices = opstack.top().slices;
collect_nonempty_indices(popop, slicegrid, popslices, nonempty_indices);
execution::for_each(
ex_tbb, nonempty_indices.begin(), nonempty_indices.end(),
[&popslices, &prev_slices, popop](size_t i) {
merge_slices(popop, i, prev_slices, popslices);
}, execution::max_concurrency(ex_tbb));
}
}
std::vector<ExPolygons> ret = std::move(opstack.top().slices);
// TODO: verify if this part can be omitted or not.
execution::for_each(ex_tbb, ret.begin(), ret.end(), [](ExPolygons &slice) {
auto it = std::remove_if(slice.begin(), slice.end(), [](const ExPolygon &p){
return p.area() < double(SCALED_EPSILON) * double(SCALED_EPSILON);
});
// Hopefully, ExPolygons are moved, not copied to new positions
// and that is cheap for expolygons
slice.erase(it, slice.end());
slice = union_ex(slice);
}, execution::max_concurrency(ex_tbb));
return ret;
}
}} // namespace Slic3r::csg
#endif // SLICECSGMESH_HPP

View file

@ -0,0 +1,95 @@
#ifndef TRIANGLEMESHADAPTER_HPP
#define TRIANGLEMESHADAPTER_HPP
#include "CSGMesh.hpp"
#include "libslic3r/TriangleMesh.hpp"
namespace Slic3r { namespace csg {
// Provide default overloads for indexed_triangle_set to be usable as a plain
// CSGPart with an implicit union operation
inline CSGType get_operation(const indexed_triangle_set &part)
{
return CSGType::Union;
}
inline CSGStackOp get_stack_operation(const indexed_triangle_set &part)
{
return CSGStackOp::Continue;
}
inline const indexed_triangle_set * get_mesh(const indexed_triangle_set &part)
{
return &part;
}
inline Transform3f get_transform(const indexed_triangle_set &part)
{
return Transform3f::Identity();
}
inline CSGType get_operation(const indexed_triangle_set *const part)
{
return CSGType::Union;
}
inline CSGStackOp get_stack_operation(const indexed_triangle_set *const part)
{
return CSGStackOp::Continue;
}
inline const indexed_triangle_set * get_mesh(const indexed_triangle_set *const part)
{
return part;
}
inline Transform3f get_transform(const indexed_triangle_set *const part)
{
return Transform3f::Identity();
}
inline CSGType get_operation(const TriangleMesh &part)
{
return CSGType::Union;
}
inline CSGStackOp get_stack_operation(const TriangleMesh &part)
{
return CSGStackOp::Continue;
}
inline const indexed_triangle_set * get_mesh(const TriangleMesh &part)
{
return &part.its;
}
inline Transform3f get_transform(const TriangleMesh &part)
{
return Transform3f::Identity();
}
inline CSGType get_operation(const TriangleMesh * const part)
{
return CSGType::Union;
}
inline CSGStackOp get_stack_operation(const TriangleMesh * const part)
{
return CSGStackOp::Continue;
}
inline const indexed_triangle_set * get_mesh(const TriangleMesh * const part)
{
return &part->its;
}
inline Transform3f get_transform(const TriangleMesh * const part)
{
return Transform3f::Identity();
}
}} // namespace Slic3r::csg
#endif // TRIANGLEMESHADAPTER_HPP

View file

@ -0,0 +1,116 @@
#ifndef VOXELIZECSGMESH_HPP
#define VOXELIZECSGMESH_HPP
#include <functional>
#include <stack>
#include "CSGMesh.hpp"
#include "libslic3r/OpenVDBUtils.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
namespace Slic3r { namespace csg {
using VoxelizeParams = MeshToGridParams;
// This method can be overriden when a specific CSGPart type supports caching
// of the voxel grid
template<class CSGPartT>
VoxelGridPtr get_voxelgrid(const CSGPartT &csgpart, VoxelizeParams params)
{
const indexed_triangle_set *its = csg::get_mesh(csgpart);
VoxelGridPtr ret;
params.trafo(params.trafo() * csg::get_transform(csgpart));
if (its)
ret = mesh_to_grid(*its, params);
return ret;
}
namespace detail {
inline void perform_csg(CSGType op, VoxelGridPtr &dst, VoxelGridPtr &src)
{
if (!dst || !src)
return;
switch (op) {
case CSGType::Union:
if (is_grid_empty(*dst) && !is_grid_empty(*src))
dst = clone(*src);
else
grid_union(*dst, *src);
break;
case CSGType::Difference:
grid_difference(*dst, *src);
break;
case CSGType::Intersection:
grid_intersection(*dst, *src);
break;
}
}
} // namespace detail
template<class It>
VoxelGridPtr voxelize_csgmesh(const Range<It> &csgrange,
const VoxelizeParams &params = {})
{
using namespace detail;
VoxelGridPtr ret;
std::vector<VoxelGridPtr> grids (csgrange.size());
execution::for_each(ex_tbb, size_t(0), csgrange.size(), [&](size_t csgidx) {
if (params.statusfn() && params.statusfn()(-1))
return;
auto it = csgrange.begin();
std::advance(it, csgidx);
auto &csgpart = *it;
grids[csgidx] = get_voxelgrid(csgpart, params);
}, execution::max_concurrency(ex_tbb));
size_t csgidx = 0;
struct Frame { CSGType op = CSGType::Union; VoxelGridPtr grid; };
std::stack opstack{std::vector<Frame>{}};
opstack.push({CSGType::Union, mesh_to_grid({}, params)});
for (auto &csgpart : csgrange) {
if (params.statusfn() && params.statusfn()(-1))
break;
auto &partgrid = grids[csgidx++];
auto op = get_operation(csgpart);
if (get_stack_operation(csgpart) == CSGStackOp::Push) {
opstack.push({op, mesh_to_grid({}, params)});
op = CSGType::Union;
}
Frame *top = &opstack.top();
perform_csg(get_operation(csgpart), top->grid, partgrid);
if (get_stack_operation(csgpart) == CSGStackOp::Pop) {
VoxelGridPtr popgrid = std::move(top->grid);
auto popop = opstack.top().op;
opstack.pop();
VoxelGridPtr &grid = opstack.top().grid;
perform_csg(popop, grid, popgrid);
}
}
ret = std::move(opstack.top().grid);
return ret;
}
}} // namespace Slic3r::csg
#endif // VOXELIZECSGMESH_HPP

View file

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

View file

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

View file

@ -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()) {

View file

@ -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();
}
}
}

View file

@ -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).");

View file

@ -2994,6 +2994,10 @@ Polylines FillGrid::fill_surface(const Surface *surface, const FillParams &param
{ { 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;
}

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

View 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

View file

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

View file

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

View file

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

View file

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

View 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(&regionPtr->perimeters, paths);
if (regionPtr->perimeters.empty() == false) { getExtrusionPathsFromEntity(&regionPtr->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(&regionPtr->perimeters, perimeters[i].paths);
getExtrusionPathsFromEntity(&regionPtr->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 {};
}

View file

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

View file

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

View file

@ -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();

View file

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

View file

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

View file

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

View file

@ -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();

View file

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

View file

@ -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();

View file

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

View file

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

View file

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

View file

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

View file

@ -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).");

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

View file

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

View file

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

View file

@ -185,4 +185,11 @@ namespace Slic3r {
project_path.clear();
}
BBLModelTask::BBLModelTask()
{
job_id = -1;
design_id = -1;
profile_id = -1;
}
} // namespace Slic3r

View file

@ -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... */

View file

@ -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]; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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