NEW: enable lightning infill pattern for model and tree support

Change-Id: I6e2cbfdd30f8d222f88301ed0c8cc89e21cfdc24
(cherry picked from commit ddfee7c069cfc42685be509d48b8c609e1dc0cfc)
This commit is contained in:
xiang.zeng 2022-07-06 12:16:37 +08:00 committed by Lane.Wei
parent f331d5998e
commit 9cf95696a4
22 changed files with 1014 additions and 321 deletions

View File

@ -11,6 +11,7 @@
#include "FillBase.hpp"
#include "FillRectilinear.hpp"
#include "FillLightning.hpp"
#include "FillConcentricInternal.hpp"
#include "FillConcentric.hpp"
@ -373,7 +374,7 @@ void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill>
#endif
// friend to Layer
void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree)
void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator)
{
for (LayerRegion *layerm : m_regions)
layerm->fills.clear();
@ -413,7 +414,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
assert(fill_concentric != nullptr);
fill_concentric->print_config = &this->object()->print()->config();
fill_concentric->print_object_config = &this->object()->config();
}
} else if (surface_fill.params.pattern == ipLightning)
dynamic_cast<FillLightning::Filler*>(f.get())->generator = lightning_generator;
// calculate flow spacing for infill pattern generation
bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge;

View File

@ -1,20 +1,25 @@
#include "../Print.hpp"
#include "../ShortestPath.hpp"
#include "FillLightning.hpp"
#include "Lightning/Generator.hpp"
#include "../Surface.hpp"
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <numeric>
namespace Slic3r::FillLightning {
Polylines Filler::fill_surface(const Surface *surface, const FillParams &params)
void Filler::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out)
{
const Layer &layer = generator->getTreesForLayer(this->layer_id);
return layer.convertToLines(to_polygons(surface->expolygon), generator->infilll_extrusion_width());
const Layer &layer = generator->getTreesForLayer(this->layer_id);
Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled<coord_t>(0.5 * this->spacing - this->overlap));
if (params.dont_connect() || fill_lines.size() <= 1) {
append(polylines_out, chain_polylines(std::move(fill_lines)));
} else
connect_infill(std::move(fill_lines), expolygon, polylines_out, this->spacing, params);
}
void GeneratorDeleter::operator()(Generator *p) {

View File

@ -3,6 +3,13 @@
#include "FillBase.hpp"
/*
* A few modifications based on dba1179(2022.06.10) from Prusa, mainly in Generator.hpp and .cpp:
* 1. delete the second parameter(a throw back function) of Generator(), since I didnt find corresponding throw back function in BBS code
* 2. those codes that call the functions above
* 3. add codes of generating lightning in TreeSupport.cpp
*/
namespace Slic3r {
class PrintObject;
@ -24,8 +31,13 @@ public:
Generator *generator { nullptr };
protected:
Fill* clone() const override { return new Filler(*this); }
// Perform the fill.
Polylines fill_surface(const Surface *surface, const FillParams &params) override;
void _fill_surface_single(const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out) override;
// Let the G-code export reoder the infill lines.
bool no_sort() const override { return false; }
};

View File

@ -407,13 +407,15 @@ public:
// for the infill pattern, don't cut the corners.
// default miterLimt = 3
//double miterLimit = 10.;
assert(aoffset1 < 0);
// FIXME: Resolve properly the cases when it is constructed with aoffset1 = 0 and aoffset2 = 0,
// that is used in sample_grid_pattern() for Lightning infill.
//assert(aoffset1 < 0);
assert(aoffset2 <= 0);
assert(aoffset2 == 0 || aoffset2 < aoffset1);
// assert(aoffset2 == 0 || aoffset2 < aoffset1);
// bool sticks_removed =
remove_sticks(polygons_src);
// if (sticks_removed) BOOST_LOG_TRIVIAL(error) << "Sticks removed!";
polygons_outer = offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit);
polygons_outer = aoffset1 == 0 ? polygons_src : offset(polygons_src, float(aoffset1), ClipperLib::jtMiter, miterLimit);
if (aoffset2 < 0)
polygons_inner = shrink(polygons_outer, float(aoffset1 - aoffset2), ClipperLib::jtMiter, miterLimit);
// Filter out contours with zero area or small area, contours with 2 points only.
@ -3053,14 +3055,18 @@ Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams
return polylines_out;
}
Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing)
// Lightning infill assumes that the distance between any two sampled points is always
// at least equal to the value of spacing. To meet this assumption, we need to use
// BoundingBox for whole layers instead of bounding box just around processing ExPolygon.
// Using just BoundingBox around processing ExPolygon could produce two points closer
// than spacing (in cases where two ExPolygon are closer than spacing).
Points sample_grid_pattern(const ExPolygon& expolygon, coord_t spacing, const BoundingBox& global_bounding_box)
{
ExPolygonWithOffset poly_with_offset(expolygon, 0, 0, 0);
BoundingBox bounding_box = poly_with_offset.bounding_box_src();
std::vector<SegmentedIntersectionLine> segs = slice_region_by_vertical_lines(
poly_with_offset,
(bounding_box.max.x() - bounding_box.min.x() + spacing - 1) / spacing,
bounding_box.min.x(),
(global_bounding_box.max.x() - global_bounding_box.min.x() + spacing - 1) / spacing,
global_bounding_box.min.x(),
spacing);
Points out;
@ -3076,17 +3082,17 @@ Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing)
return out;
}
Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing)
Points sample_grid_pattern(const ExPolygons& expolygons, coord_t spacing, const BoundingBox& global_bounding_box)
{
Points out;
for (const ExPolygon &expoly : expolygons)
append(out, sample_grid_pattern(expoly, spacing));
for (const ExPolygon& expoly : expolygons)
append(out, sample_grid_pattern(expoly, spacing, global_bounding_box));
return out;
}
Points sample_grid_pattern(const Polygons &polygons, coord_t spacing)
Points sample_grid_pattern(const Polygons& polygons, coord_t spacing, const BoundingBox& global_bounding_box)
{
return sample_grid_pattern(union_ex(polygons), spacing);
return sample_grid_pattern(union_ex(polygons), spacing, global_bounding_box);
}
void FillMonotonicLineWGapFill::fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out)

View File

@ -132,9 +132,9 @@ private:
void fill_surface_by_lines(const Surface* surface, const FillParams& params, Polylines& polylines_out);
};
Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing);
Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing);
Points sample_grid_pattern(const Polygons &polygons, coord_t spacing);
Points sample_grid_pattern(const ExPolygon& expolygon, coord_t spacing, const BoundingBox& global_bounding_box);
Points sample_grid_pattern(const ExPolygons& expolygons, coord_t spacing, const BoundingBox& global_bounding_box);
Points sample_grid_pattern(const Polygons& polygons, coord_t spacing, const BoundingBox& global_bounding_box);
} // namespace Slic3r

View File

@ -5,48 +5,99 @@
#include "../FillRectilinear.hpp"
#include "../../ClipperUtils.hpp"
#include <tbb/parallel_for.h>
#ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT
#include "../../SVG.hpp"
#endif
namespace Slic3r::FillLightning
{
constexpr coord_t radius_per_cell_size = 6; // The cell-size should be small compared to the radius, but not so small as to be inefficient.
DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang) :
m_cell_size(radius / radius_per_cell_size),
m_supporting_radius(radius)
#ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT
void export_distance_field_to_svg(const std::string &path, const Polygons &outline, const Polygons &overhang, const std::list<DistanceField::UnsupportedCell> &unsupported_points, const Points &points = {})
{
m_supporting_radius2 = double(radius) * double(radius);
coordf_t stroke_width = scaled<coordf_t>(0.01);
BoundingBox bbox = get_extents(outline);
bbox.offset(SCALED_EPSILON);
SVG svg(path, bbox);
svg.draw_outline(outline, "green", stroke_width);
svg.draw_outline(overhang, "blue", stroke_width);
for (const DistanceField::UnsupportedCell &cell : unsupported_points)
svg.draw(cell.loc, "cyan", coord_t(stroke_width));
for (const Point &pt : points)
svg.draw(pt, "red", coord_t(stroke_width));
}
#endif
DistanceField::DistanceField(const coord_t& radius, const Polygons& current_outline, const BoundingBox& current_outlines_bbox, const Polygons& current_overhang) :
m_cell_size(radius / radius_per_cell_size),
m_supporting_radius(radius),
m_unsupported_points_bbox(current_outlines_bbox)
{
m_supporting_radius2 = Slic3r::sqr(int64_t(radius));
// Sample source polygons with a regular grid sampling pattern.
for (const ExPolygon &expoly : union_ex(current_outline)) {
for (const Point &p : sample_grid_pattern(expoly, m_cell_size)) {
// Find a squared distance to the source expolygon boundary.
double d2 = std::numeric_limits<double>::max();
for (size_t icontour = 0; icontour <= expoly.holes.size(); ++ icontour) {
const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1];
if (contour.size() > 2) {
Point prev = contour.points.back();
for (const Point &p2 : contour.points) {
d2 = std::min(d2, Line::distance_to_squared(p, prev, p2));
prev = p2;
const BoundingBox overhang_bbox = get_extents(current_overhang);
for (const ExPolygon &expoly : union_ex(current_overhang)) {
const Points sampled_points = sample_grid_pattern(expoly, m_cell_size, overhang_bbox);
const size_t unsupported_points_prev_size = m_unsupported_points.size();
m_unsupported_points.resize(unsupported_points_prev_size + sampled_points.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, sampled_points.size()), [&self = *this, &expoly = std::as_const(expoly), &sampled_points = std::as_const(sampled_points), &unsupported_points_prev_size = std::as_const(unsupported_points_prev_size)](const tbb::blocked_range<size_t> &range) -> void {
for (size_t sp_idx = range.begin(); sp_idx < range.end(); ++sp_idx) {
const Point &sp = sampled_points[sp_idx];
// Find a squared distance to the source expolygon boundary.
double d2 = std::numeric_limits<double>::max();
for (size_t icontour = 0; icontour <= expoly.holes.size(); ++icontour) {
const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1];
if (contour.size() > 2) {
Point prev = contour.points.back();
for (const Point &p2 : contour.points) {
d2 = std::min(d2, Line::distance_to_squared(sp, prev, p2));
prev = p2;
}
}
}
self.m_unsupported_points[unsupported_points_prev_size + sp_idx] = {sp, coord_t(std::sqrt(d2))};
assert(self.m_unsupported_points_bbox.contains(sp));
}
m_unsupported_points.emplace_back(p, sqrt(d2));
}
}); // end of parallel_for
}
m_unsupported_points.sort([&radius](const UnsupportedCell &a, const UnsupportedCell &b) {
std::stable_sort(m_unsupported_points.begin(), m_unsupported_points.end(), [&radius](const UnsupportedCell &a, const UnsupportedCell &b) {
constexpr coord_t prime_for_hash = 191;
return std::abs(b.dist_to_boundary - a.dist_to_boundary) > radius ?
a.dist_to_boundary < b.dist_to_boundary :
(PointHash{}(a.loc) % prime_for_hash) < (PointHash{}(b.loc) % prime_for_hash);
});
for (auto it = m_unsupported_points.begin(); it != m_unsupported_points.end(); ++it) {
UnsupportedCell& cell = *it;
m_unsupported_points_grid.emplace(Point{ cell.loc.x() / m_cell_size, cell.loc.y() / m_cell_size }, it);
m_unsupported_points_erased.resize(m_unsupported_points.size());
std::fill(m_unsupported_points_erased.begin(), m_unsupported_points_erased.end(), false);
m_unsupported_points_grid.initialize(m_unsupported_points, [&self = std::as_const(*this)](const Point &p) -> Point { return self.to_grid_point(p); });
// Because the distance between two points is at least one axis equal to m_cell_size, every cell
// in m_unsupported_points_grid contains exactly one point.
assert(m_unsupported_points.size() == m_unsupported_points_grid.size());
#ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT
{
static int iRun = 0;
export_distance_field_to_svg(debug_out_path("FillLightning-DistanceField-%d.svg", iRun++), current_outline, current_overhang, m_unsupported_points);
}
#endif
}
void DistanceField::update(const Point& to_node, const Point& added_leaf)
{
std::ofstream out1("z:/misc/lightning.txt", std::ios::app);
out1 << m_unsupported_points.size() << std::endl;
out1.close();
Vec2d v = (added_leaf - to_node).cast<double>();
auto l2 = v.squaredNorm();
Vec2d extent = Vec2d(-v.y(), v.x()) * m_supporting_radius / sqrt(l2);
@ -60,17 +111,24 @@ void DistanceField::update(const Point& to_node, const Point& added_leaf)
grid.merge(to_node + iextent);
grid.merge(added_leaf - iextent);
grid.merge(added_leaf + iextent);
grid.min /= m_cell_size;
grid.max /= m_cell_size;
// Clip grid by m_unsupported_points_bbox. Mainly to ensure that grid.min is a non-negative value.
grid.min.x() = std::max(grid.min.x(), m_unsupported_points_bbox.min.x());
grid.min.y() = std::max(grid.min.y(), m_unsupported_points_bbox.min.y());
grid.max.x() = std::min(grid.max.x(), m_unsupported_points_bbox.max.x());
grid.max.y() = std::min(grid.max.y(), m_unsupported_points_bbox.max.y());
grid.min = this->to_grid_point(grid.min);
grid.max = this->to_grid_point(grid.max);
}
Point grid_addr;
Point grid_loc;
for (coord_t row = grid.min.y(); row <= grid.max.y(); ++ row) {
grid_loc.y() = row * m_cell_size;
for (coord_t col = grid.min.x(); col <= grid.max.y(); ++ col) {
grid_loc.x() = col * m_cell_size;
for (grid_addr.y() = grid.min.y(); grid_addr.y() <= grid.max.y(); ++grid_addr.y()) {
for (grid_addr.x() = grid.min.x(); grid_addr.x() <= grid.max.x(); ++grid_addr.x()) {
grid_loc = this->from_grid_point(grid_addr);
// Test inside a circle at the new leaf.
if ((grid_loc - added_leaf).cast<double>().squaredNorm() > m_supporting_radius2) {
if ((grid_loc - added_leaf).cast<int64_t>().squaredNorm() > m_supporting_radius2) {
// Not inside a circle at the end of the new leaf.
// Test inside a rotated rectangle.
Vec2d vx = (grid_loc - to_node).cast<double>();
@ -84,10 +142,29 @@ void DistanceField::update(const Point& to_node, const Point& added_leaf)
}
// Inside a circle at the end of the new leaf, or inside a rotated rectangle.
// Remove unsupported leafs at this grid location.
if (auto it = m_unsupported_points_grid.find(grid_loc); it != m_unsupported_points_grid.end()) {
std::list<UnsupportedCell>::iterator& list_it = it->second;
UnsupportedCell& cell = *list_it;
if ((cell.loc - added_leaf).cast<double>().squaredNorm() <= m_supporting_radius2) {
if (const size_t cell_idx = m_unsupported_points_grid.find_cell_idx(grid_addr); cell_idx != std::numeric_limits<size_t>::max()) {
const UnsupportedCell &cell = m_unsupported_points[cell_idx];
if ((cell.loc - added_leaf).cast<int64_t>().squaredNorm() <= m_supporting_radius2) {
m_unsupported_points_erased[cell_idx] = true;
m_unsupported_points_grid.mark_erased(grid_addr);
}
}
}
}
}
#if 0
void DistanceField::update(const Point &to_node, const Point &added_leaf)
{
const Point supporting_radius_point(m_supporting_radius, m_supporting_radius);
const BoundingBox grid(this->to_grid_point(added_leaf - supporting_radius_point), this->to_grid_point(added_leaf + supporting_radius_point));
for (coord_t grid_y = grid.min.y(); grid_y <= grid.max.y(); ++grid_y) {
for (coord_t grid_x = grid.min.x(); grid_x <= grid.max.x(); ++grid_x) {
if (auto it = m_unsupported_points_grid.find({grid_x, grid_y}); it != m_unsupported_points_grid.end()) {
std::list<UnsupportedCell>::iterator &list_it = it->second;
UnsupportedCell &cell = *list_it;
if ((cell.loc - added_leaf).cast<int64_t>().squaredNorm() <= m_supporting_radius2) {
m_unsupported_points.erase(list_it);
m_unsupported_points_grid.erase(it);
}
@ -95,5 +172,6 @@ void DistanceField::update(const Point& to_node, const Point& added_leaf)
}
}
}
#endif
} // namespace Slic3r::FillLightning

View File

@ -4,9 +4,12 @@
#ifndef LIGHTNING_DISTANCE_FIELD_H
#define LIGHTNING_DISTANCE_FIELD_H
#include "../../BoundingBox.hpp"
#include "../../Point.hpp"
#include "../../Polygon.hpp"
//#define LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT
namespace Slic3r::FillLightning
{
@ -29,7 +32,7 @@ public:
* \param current_overhang The overhang that needs to be supported on this
* layer.
*/
DistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang);
DistanceField(const coord_t& radius, const Polygons& current_outline, const BoundingBox& current_outlines_bbox, const Polygons& current_overhang);
/*!
* Gets the next unsupported location to be supported by a new branch.
@ -37,11 +40,17 @@ public:
* \return ``true`` if successful, or ``false`` if there are no more points
* to consider.
*/
bool tryGetNextPoint(Point* p) const {
if (m_unsupported_points.empty())
return false;
*p = m_unsupported_points.front().loc;
return true;
bool tryGetNextPoint(Point *out_unsupported_location, size_t *out_unsupported_cell_idx, const size_t start_idx = 0) const
{
for (size_t point_idx = start_idx; point_idx < m_unsupported_points.size(); ++point_idx) {
if (!m_unsupported_points_erased[point_idx]) {
*out_unsupported_cell_idx = point_idx;
*out_unsupported_location = m_unsupported_points[point_idx].loc;
return true;
}
}
return false;
}
/*!
@ -69,14 +78,13 @@ protected:
* branch of a tree.
*/
coord_t m_supporting_radius;
double m_supporting_radius2;
int64_t m_supporting_radius2;
/*!
* Represents a small discrete area of infill that needs to be supported.
*/
struct UnsupportedCell
{
UnsupportedCell(Point loc, coord_t dist_to_boundary) : loc(loc), dist_to_boundary(dist_to_boundary) {}
// The position of the center of this cell.
Point loc;
// How far this cell is removed from the ``current_outline`` polygon, the edge of the infill area.
@ -86,13 +94,114 @@ protected:
/*!
* Cells which still need to be supported at some point.
*/
std::list<UnsupportedCell> m_unsupported_points;
std::vector<UnsupportedCell> m_unsupported_points;
std::vector<bool> m_unsupported_points_erased;
/*!
* BoundingBox of all points in m_unsupported_points. Used for mapping of sign integer numbers to positive integer numbers.
*/
const BoundingBox m_unsupported_points_bbox;
/*!
* Links the unsupported points to a grid point, so that we can quickly look
* up the cell belonging to a certain position in the grid.
*/
std::unordered_map<Point, std::list<UnsupportedCell>::iterator, PointHash> m_unsupported_points_grid;
class UnsupportedPointsGrid
{
public:
UnsupportedPointsGrid() = default;
void initialize(const std::vector<UnsupportedCell> &unsupported_points, const std::function<Point(const Point &)> &map_cell_to_grid)
{
if (unsupported_points.empty())
return;
BoundingBox unsupported_points_bbox;
for (const UnsupportedCell &cell : unsupported_points)
unsupported_points_bbox.merge(cell.loc);
m_size = unsupported_points.size();
m_grid_range = BoundingBox(map_cell_to_grid(unsupported_points_bbox.min), map_cell_to_grid(unsupported_points_bbox.max));
m_grid_size = m_grid_range.size() + Point::Ones();
m_data.assign(m_grid_size.y() * m_grid_size.x(), std::numeric_limits<size_t>::max());
m_data_erased.assign(m_grid_size.y() * m_grid_size.x(), true);
for (size_t cell_idx = 0; cell_idx < unsupported_points.size(); ++cell_idx) {
const size_t flat_idx = map_to_flat_array(map_cell_to_grid(unsupported_points[cell_idx].loc));
assert(m_data[flat_idx] == std::numeric_limits<size_t>::max());
m_data[flat_idx] = cell_idx;
m_data_erased[flat_idx] = false;
}
}
size_t size() const { return m_size; }
size_t find_cell_idx(const Point &grid_addr)
{
if (!m_grid_range.contains(grid_addr))
return std::numeric_limits<size_t>::max();
if (const size_t flat_idx = map_to_flat_array(grid_addr); !m_data_erased[flat_idx]) {
assert(m_data[flat_idx] != std::numeric_limits<size_t>::max());
return m_data[flat_idx];
}
return std::numeric_limits<size_t>::max();
}
void mark_erased(const Point &grid_addr)
{
assert(m_grid_range.contains(grid_addr));
if (!m_grid_range.contains(grid_addr))
return;
const size_t flat_idx = map_to_flat_array(grid_addr);
assert(!m_data_erased[flat_idx] && m_data[flat_idx] != std::numeric_limits<size_t>::max());
assert(m_size != 0);
m_data_erased[flat_idx] = true;
--m_size;
}
private:
size_t m_size = 0;
BoundingBox m_grid_range;
Point m_grid_size;
std::vector<size_t> m_data;
std::vector<bool> m_data_erased;
inline size_t map_to_flat_array(const Point &loc) const
{
const Point offset_loc = loc - m_grid_range.min;
const size_t flat_idx = m_grid_size.x() * offset_loc.y() + offset_loc.x();
assert(offset_loc.x() >= 0 && offset_loc.y() >= 0);
assert(flat_idx < size_t(m_grid_size.y() * m_grid_size.x()));
return flat_idx;
}
};
UnsupportedPointsGrid m_unsupported_points_grid;
/*!
* Maps the point to the grid coordinates.
*/
Point to_grid_point(const Point &point) const {
return (point - m_unsupported_points_bbox.min) / m_cell_size;
}
/*!
* Maps the point to the grid coordinates.
*/
Point from_grid_point(const Point &point) const {
return point * m_cell_size + m_unsupported_points_bbox.min;
}
#ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT
friend void export_distance_field_to_svg(const std::string &path, const Polygons &outline, const Polygons &overhang, const std::list<DistanceField::UnsupportedCell> &unsupported_points, const Points &points);
#endif
};
} // namespace Slic3r::FillLightning

View File

@ -7,7 +7,8 @@
#include "../../ClipperUtils.hpp"
#include "../../Layer.hpp"
#include "../../Print.hpp"
#include "../../Surface.hpp"
#include "ExPolygon.hpp"
/* Possible future tasks/optimizations,etc.:
* - Improve connecting heuristic to favor connecting to shorter trees
@ -23,6 +24,43 @@
* - Move more complex computations from Generator constructor to elsewhere.
*/
namespace Slic3r
{
static std::string get_svg_filename(std::string layer_nr_or_z, std::string tag = "bbl_ts")
{
static bool rand_init = false;
if (!rand_init) {
srand(time(NULL));
rand_init = true;
}
int rand_num = rand() % 1000000;
//makedir("./SVG");
std::string prefix = "./SVG/";
std::string suffix = ".svg";
return prefix + tag + "_" + layer_nr_or_z /*+ "_" + std::to_string(rand_num)*/ + suffix;
}
Slic3r::SVG draw_two_overhangs_to_svg(size_t ts_layer, const ExPolygons& overhangs1, const ExPolygons& overhangs2)
{
//if (overhangs1.empty() && overhangs2.empty())
// return ;
BoundingBox bbox1 = get_extents(overhangs1);
BoundingBox bbox2 = get_extents(overhangs2);
bbox1.merge(bbox2);
Slic3r::SVG svg(get_svg_filename(std::to_string(ts_layer), "two_overhangs_generator"), bbox1);
//if (!svg.is_opened()) return;
svg.draw(union_ex(overhangs1), "blue");
svg.draw(union_ex(overhangs2), "red");
return svg;
}
}
namespace Slic3r::FillLightning {
Generator::Generator(const PrintObject &print_object)
@ -32,13 +70,46 @@ Generator::Generator(const PrintObject &print_object)
const PrintRegionConfig &region_config = print_object.shared_regions()->all_regions.front()->config();
const std::vector<double> &nozzle_diameters = print_config.nozzle_diameter.values;
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
// const int sparse_infill_filament = region_config.sparse_infill_filament.value;
// const int infill_extruder = region_config.infill_extruder.value;
const double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter));
// Note: There's not going to be a layer below the first one, so the 'initial layer height' doesn't have to be taken into account.
const double layer_thickness = object_config.layer_height;
const double layer_thickness = scaled<double>(object_config.layer_height.value);
//m_infill_extrusion_width = scaled<float>(region_config.infill_extrusion_width.percent ? default_infill_extrusion_width * 0.01 * region_config.infill_extrusion_width : region_config.infill_extrusion_width);
//m_supporting_radius = coord_t(m_infill_extrusion_width) * 100 / coord_t(region_config.fill_density.value);
m_infill_extrusion_width = scaled<float>(region_config.sparse_infill_line_width.value);
m_supporting_radius = coord_t(m_infill_extrusion_width) * 100 / region_config.sparse_infill_density;
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
const double lightning_infill_straightening_angle = M_PI / 4; // 45 degrees
m_wall_supporting_radius = coord_t(layer_thickness * std::tan(lightning_infill_overhang_angle));
m_prune_length = coord_t(layer_thickness * std::tan(lightning_infill_prune_angle));
m_straightening_max_distance = coord_t(layer_thickness * std::tan(lightning_infill_straightening_angle));
generateInitialInternalOverhangs(print_object);
generateTrees(print_object);
}
Generator::Generator(PrintObject* m_object, std::vector<Polygons>& contours, std::vector<Polygons>& overhangs, float density)
{
const PrintConfig &print_config = m_object->print()->config();
const PrintObjectConfig &object_config = m_object->config();
const PrintRegionConfig &region_config = m_object->shared_regions()->all_regions.front()->config();
const std::vector<double> &nozzle_diameters = print_config.nozzle_diameter.values;
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
// const int infill_extruder = region_config.infill_extruder.value;
const double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter));
// Note: There's not going to be a layer below the first one, so the 'initial layer height' doesn't have to be taken into account.
const double layer_thickness = scaled<double>(object_config.layer_height.value);
m_infill_extrusion_width = scaled<float>(region_config.sparse_infill_line_width.value);
m_supporting_radius = scaled<coord_t>(m_infill_extrusion_width * 0.001 / region_config.sparse_infill_density);
//m_supporting_radius: against to the density of lightning, failures may happen if set to high density
//higher density lightning makes support harder, more time-consuming on computing and printing, but more reliable on supporting overhangs
//lower density lightning performs opposite
//TODO: decide whether enable density controller in advanced options or not
density = std::max(0.15f, density);
m_supporting_radius = coord_t(m_infill_extrusion_width) / density;
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
@ -47,26 +118,33 @@ Generator::Generator(const PrintObject &print_object)
m_prune_length = layer_thickness * std::tan(lightning_infill_prune_angle);
m_straightening_max_distance = layer_thickness * std::tan(lightning_infill_straightening_angle);
generateInitialInternalOverhangs(print_object);
generateTrees(print_object);
m_overhang_per_layer = overhangs;
generateTreesforSupport(contours);
//for (size_t i = 0; i < overhangs.size(); i++)
//{
// auto svg = draw_two_overhangs_to_svg(i, to_expolygons(contours[i]), to_expolygons(overhangs[i]));
// for (auto& root : m_lightning_layers[i].tree_roots)
// root->draw_tree(svg);
//}
}
void Generator::generateInitialInternalOverhangs(const PrintObject &print_object)
{
m_overhang_per_layer.resize(print_object.layers().size());
const float infill_wall_offset = - m_infill_extrusion_width;
Polygons infill_area_above;
//Iterate from top to bottom, to subtract the overhang areas above from the overhang areas on the layer below, to get only overhang in the top layer where it is overhanging.
for (int layer_nr = print_object.layers().size() - 1; layer_nr >= 0; layer_nr--) {
for (int layer_nr = int(print_object.layers().size()) - 1; layer_nr >= 0; --layer_nr) {
Polygons infill_area_here;
for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions())
for (const Surface& surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == stInternal)
append(infill_area_here, offset(surface.expolygon, infill_wall_offset));
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
append(infill_area_here, to_polygons(surface.expolygon));
//Remove the part of the infill area that is already supported by the walls.
Polygons overhang = diff(offset(infill_area_here, -m_wall_supporting_radius), infill_area_above);
Polygons overhang = diff(offset(infill_area_here, -float(m_wall_supporting_radius)), infill_area_above);
m_overhang_per_layer[layer_nr] = overhang;
infill_area_above = std::move(infill_area_here);
@ -82,16 +160,16 @@ const Layer& Generator::getTreesForLayer(const size_t& layer_id) const
void Generator::generateTrees(const PrintObject &print_object)
{
m_lightning_layers.resize(print_object.layers().size());
const coord_t infill_wall_offset = - m_infill_extrusion_width;
bboxs.resize(print_object.layers().size());
std::vector<Polygons> infill_outlines(print_object.layers().size(), Polygons());
// For-each layer from top to bottom:
for (int layer_id = print_object.layers().size() - 1; layer_id >= 0; layer_id--)
for (int layer_id = int(print_object.layers().size()) - 1; layer_id >= 0; layer_id--) {
for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions())
for (const Surface &surface : layerm->fill_surfaces.surfaces)
if (surface.surface_type == stInternal)
append(infill_outlines[layer_id], offset(surface.expolygon, infill_wall_offset));
if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid)
append(infill_outlines[layer_id], to_polygons(surface.expolygon));
}
// For various operations its beneficial to quickly locate nearby features on the polygon:
const size_t top_layer_id = print_object.layers().size() - 1;
@ -99,23 +177,77 @@ void Generator::generateTrees(const PrintObject &print_object)
outlines_locator.create(infill_outlines[top_layer_id], locator_cell_size);
// For-each layer from top to bottom:
for (int layer_id = top_layer_id; layer_id >= 0; layer_id--)
{
Layer& current_lightning_layer = m_lightning_layers[layer_id];
Polygons& current_outlines = infill_outlines[layer_id];
for (int layer_id = int(top_layer_id); layer_id >= 0; layer_id--) {
Layer &current_lightning_layer = m_lightning_layers[layer_id];
const Polygons &current_outlines = infill_outlines[layer_id];
const BoundingBox &current_outlines_bbox = get_extents(current_outlines);
bboxs[layer_id] = get_extents(current_outlines);
// register all trees propagated from the previous layer as to-be-reconnected
std::vector<NodeSPtr> to_be_reconnected_tree_roots = current_lightning_layer.tree_roots;
current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
current_lightning_layer.reconnectRoots(to_be_reconnected_tree_roots, current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, current_outlines_bbox, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
current_lightning_layer.reconnectRoots(to_be_reconnected_tree_roots, current_outlines, current_outlines_bbox, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
// Initialize trees for next lower layer from the current one.
if (layer_id == 0)
return;
const Polygons& below_outlines = infill_outlines[layer_id - 1];
outlines_locator.set_bbox(get_extents(below_outlines).inflated(SCALED_EPSILON));
const Polygons &below_outlines = infill_outlines[layer_id - 1];
BoundingBox below_outlines_bbox = get_extents(below_outlines).inflated(SCALED_EPSILON);
if (const BoundingBox &outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined)
below_outlines_bbox.merge(outlines_locator_bbox);
if (!current_lightning_layer.tree_roots.empty())
below_outlines_bbox.merge(get_extents(current_lightning_layer.tree_roots).inflated(SCALED_EPSILON));
outlines_locator.set_bbox(below_outlines_bbox);
outlines_locator.create(below_outlines, locator_cell_size);
std::vector<NodeSPtr>& lower_trees = m_lightning_layers[layer_id - 1].tree_roots;
for (auto& tree : current_lightning_layer.tree_roots)
tree->propagateToNextLayer(lower_trees, below_outlines, outlines_locator, m_prune_length, m_straightening_max_distance, locator_cell_size / 2);
}
}
void Generator::generateTreesforSupport(std::vector<Polygons>& contours)
{
m_lightning_layers.resize(contours.size());
bboxs.resize(contours.size());
// For various operations its beneficial to quickly locate nearby features on the polygon:
const size_t top_layer_id = contours.size() - 1;
EdgeGrid::Grid outlines_locator(get_extents(contours[top_layer_id]).inflated(SCALED_EPSILON));
outlines_locator.create(contours[top_layer_id], locator_cell_size);
// For-each layer from top to bottom:
for (int layer_id = int(top_layer_id); layer_id >= 0; layer_id--) {
Layer& current_lightning_layer = m_lightning_layers[layer_id];
const Polygons& current_outlines = contours[layer_id];
const BoundingBox& current_outlines_bbox = get_extents(current_outlines);
bboxs[layer_id] = get_extents(current_outlines);
// register all trees propagated from the previous layer as to-be-reconnected
std::vector<NodeSPtr> to_be_reconnected_tree_roots = current_lightning_layer.tree_roots;
current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, current_outlines_bbox, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
current_lightning_layer.reconnectRoots(to_be_reconnected_tree_roots, current_outlines, current_outlines_bbox, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
// Initialize trees for next lower layer from the current one.
if (layer_id == 0)
return;
const Polygons& below_outlines = contours[layer_id - 1];
BoundingBox below_outlines_bbox = get_extents(below_outlines).inflated(SCALED_EPSILON);
if (const BoundingBox& outlines_locator_bbox = outlines_locator.bbox(); outlines_locator_bbox.defined)
below_outlines_bbox.merge(outlines_locator_bbox);
if (!current_lightning_layer.tree_roots.empty())
below_outlines_bbox.merge(get_extents(current_lightning_layer.tree_roots).inflated(SCALED_EPSILON));
outlines_locator.set_bbox(below_outlines_bbox);
outlines_locator.create(below_outlines, locator_cell_size);
std::vector<NodeSPtr>& lower_trees = m_lightning_layers[layer_id - 1].tree_roots;

View File

@ -43,9 +43,8 @@ public:
* This generator will pre-compute things in preparation of generating
* Lightning Infill for the infill areas in that mesh. The infill areas must
* already be calculated at this point.
* \param mesh The mesh to generate infill for.
*/
Generator(const PrintObject &print_object);
explicit Generator(const PrintObject &print_object);
/*!
* Get a tree of paths generated for a certain layer of the mesh.
@ -58,8 +57,12 @@ public:
*/
const Layer& getTreesForLayer(const size_t& layer_id) const;
std::vector<Polygons>& Overhangs() { return m_overhang_per_layer; }
float infilll_extrusion_width() const { return m_infill_extrusion_width; }
Generator(PrintObject* m_object, std::vector<Polygons>& contours, std::vector<Polygons>& overhangs, float density = 0.15);
protected:
/*!
* Calculate the overhangs above the infill areas that need to be supported
@ -75,6 +78,7 @@ protected:
* Calculate the tree structure of all layers.
*/
void generateTrees(const PrintObject &print_object);
void generateTreesforSupport(std::vector<Polygons>& contours);
float m_infill_extrusion_width;
@ -124,6 +128,8 @@ protected:
* This is generated by \ref generateTrees.
*/
std::vector<Layer> m_lightning_layers;
std::vector<BoundingBox> bboxs;
};
} // namespace FillLightning

View File

@ -3,12 +3,16 @@
#include "Layer.hpp" //The class we're implementing.
#include <iterator> // advance
#include "DistanceField.hpp"
#include "TreeNode.hpp"
#include "../../ClipperUtils.hpp"
#include "../../Geometry.hpp"
#include "Utils.hpp"
#include <tbb/parallel_for.h>
#include <tbb/blocked_range2d.h>
#include <mutex>
namespace Slic3r::FillLightning {
@ -23,10 +27,15 @@ Point GroundingLocation::p() const
return tree_node ? tree_node->getLocation() : *boundary_location;
}
void Layer::fillLocator(SparseNodeGrid &tree_node_locator)
inline static Point to_grid_point(const Point &point, const BoundingBox &bbox)
{
std::function<void(NodeSPtr)> add_node_to_locator_func = [&tree_node_locator](NodeSPtr node) {
tree_node_locator.insert(std::make_pair(Point(node->getLocation().x() / locator_cell_size, node->getLocation().y() / locator_cell_size), node));
return (point - bbox.min) / locator_cell_size;
}
void Layer::fillLocator(SparseNodeGrid &tree_node_locator, const BoundingBox& current_outlines_bbox)
{
std::function<void(NodeSPtr)> add_node_to_locator_func = [&tree_node_locator, &current_outlines_bbox](const NodeSPtr &node) {
tree_node_locator.insert(std::make_pair(to_grid_point(node->getLocation(), current_outlines_bbox), node));
};
for (auto& tree : tree_roots)
tree->visitNodes(add_node_to_locator_func);
@ -36,38 +45,47 @@ void Layer::generateNewTrees
(
const Polygons& current_overhang,
const Polygons& current_outlines,
const BoundingBox& current_outlines_bbox,
const EdgeGrid::Grid& outlines_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius
)
{
DistanceField distance_field(supporting_radius, current_outlines, current_overhang);
DistanceField distance_field(supporting_radius, current_outlines, current_outlines_bbox, current_overhang);
SparseNodeGrid tree_node_locator;
fillLocator(tree_node_locator);
fillLocator(tree_node_locator, current_outlines_bbox);
// Until no more points need to be added to support all:
// Determine next point from tree/outline areas via distance-field
Point unsupported_location;
while (distance_field.tryGetNextPoint(&unsupported_location)) {
size_t unsupported_cell_idx = 0;
Point unsupported_location;
while (distance_field.tryGetNextPoint(&unsupported_location, &unsupported_cell_idx, unsupported_cell_idx)) {
GroundingLocation grounding_loc = getBestGroundingLocation(
unsupported_location, current_outlines, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator);
unsupported_location, current_outlines, current_outlines_bbox, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator);
NodeSPtr new_parent;
NodeSPtr new_child;
this->attach(unsupported_location, grounding_loc, new_child, new_parent);
tree_node_locator.insert(std::make_pair(Point(new_child->getLocation().x() / locator_cell_size, new_child->getLocation().y() / locator_cell_size), new_child));
tree_node_locator.insert(std::make_pair(to_grid_point(new_child->getLocation(), current_outlines_bbox), new_child));
if (new_parent)
tree_node_locator.insert(std::make_pair(Point(new_parent->getLocation().x() / locator_cell_size, new_parent->getLocation().y() / locator_cell_size), new_parent));
tree_node_locator.insert(std::make_pair(to_grid_point(new_parent->getLocation(), current_outlines_bbox), new_parent));
// update distance field
distance_field.update(grounding_loc.p(), unsupported_location);
}
#ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT
{
static int iRun = 0;
export_to_svg(debug_out_path("FillLightning-TreeNodes-%d.svg", iRun++), current_outlines, this->tree_roots);
}
#endif /* LIGHTNING_TREE_NODE_DEBUG_OUTPUT */
}
static bool polygonCollidesWithLineSegment(const Point from, const Point to, const EdgeGrid::Grid &loc_to_line)
static bool polygonCollidesWithLineSegment(const Point &from, const Point &to, const EdgeGrid::Grid &loc_to_line)
{
struct Visitor {
explicit Visitor(const EdgeGrid::Grid &grid) : grid(grid) {}
explicit Visitor(const EdgeGrid::Grid &grid, const Line &line) : grid(grid), line(line) {}
bool operator()(coord_t iy, coord_t ix) {
// Called with a row and colum of the grid cell, which is intersected by a line.
@ -87,7 +105,7 @@ static bool polygonCollidesWithLineSegment(const Point from, const Point to, con
const EdgeGrid::Grid& grid;
Line line;
bool intersect = false;
} visitor(loc_to_line);
} visitor(loc_to_line, {from, to});
loc_to_line.visit_cells_intersecting_line(from, to, visitor);
return visitor.intersect;
@ -97,6 +115,7 @@ GroundingLocation Layer::getBestGroundingLocation
(
const Point& unsupported_location,
const Polygons& current_outlines,
const BoundingBox& current_outlines_bbox,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius,
@ -112,9 +131,10 @@ GroundingLocation Layer::getBestGroundingLocation
if (contour.size() > 2) {
Point prev = contour.points.back();
for (const Point &p2 : contour.points) {
if (double d = Line::distance_to_squared(unsupported_location, prev, p2); d < d2) {
Point closest_point;
if (double d = line_alg::distance_to_squared(Line{prev, p2}, unsupported_location, &closest_point); d < d2) {
d2 = d;
node_location = Geometry::foot_pt({ prev, p2 }, unsupported_location).cast<coord_t>();
node_location = closest_point;
}
prev = p2;
}
@ -123,30 +143,52 @@ GroundingLocation Layer::getBestGroundingLocation
const auto within_dist = coord_t((node_location - unsupported_location).cast<double>().norm());
NodeSPtr sub_tree{ nullptr };
coord_t current_dist = getWeightedDistance(node_location, unsupported_location);
NodeSPtr sub_tree{nullptr};
coord_t current_dist = getWeightedDistance(node_location, unsupported_location);
if (current_dist >= wall_supporting_radius) { // Only reconnect tree roots to other trees if they are not already close to the outlines.
const coord_t search_radius = std::min(current_dist, within_dist);
BoundingBox region(unsupported_location - Point(search_radius, search_radius), unsupported_location + Point(search_radius + locator_cell_size, search_radius + locator_cell_size));
region.min /= locator_cell_size;
region.max /= locator_cell_size;
Point grid_addr;
for (grid_addr.y() = region.min.y(); grid_addr.y() < region.max.y(); ++ grid_addr.y())
for (grid_addr.x() = region.min.x(); grid_addr.x() < region.max.x(); ++ grid_addr.x()) {
auto it_range = tree_node_locator.equal_range(grid_addr);
for (auto it = it_range.first; it != it_range.second; ++ it) {
auto candidate_sub_tree = it->second.lock();
if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) &&
!(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) &&
!polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) {
const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius);
if (candidate_dist < current_dist) {
current_dist = candidate_dist;
sub_tree = candidate_sub_tree;
region.min = to_grid_point(region.min, current_outlines_bbox);
region.max = to_grid_point(region.max, current_outlines_bbox);
Point current_dist_grid_addr{std::numeric_limits<coord_t>::lowest(), std::numeric_limits<coord_t>::lowest()};
std::mutex current_dist_mutex;
tbb::parallel_for(tbb::blocked_range2d<coord_t>(region.min.y(), region.max.y(), region.min.x(), region.max.x()), [&current_dist, current_dist_copy = current_dist, &current_dist_mutex, &sub_tree, &current_dist_grid_addr, &exclude_tree = std::as_const(exclude_tree), &outline_locator = std::as_const(outline_locator), &supporting_radius = std::as_const(supporting_radius), &tree_node_locator = std::as_const(tree_node_locator), &unsupported_location = std::as_const(unsupported_location)](const tbb::blocked_range2d<coord_t> &range) -> void {
for (coord_t grid_addr_y = range.rows().begin(); grid_addr_y < range.rows().end(); ++grid_addr_y)
for (coord_t grid_addr_x = range.cols().begin(); grid_addr_x < range.cols().end(); ++grid_addr_x) {
const Point local_grid_addr{grid_addr_x, grid_addr_y};
NodeSPtr local_sub_tree{nullptr};
coord_t local_current_dist = current_dist_copy;
const auto it_range = tree_node_locator.equal_range(local_grid_addr);
for (auto it = it_range.first; it != it_range.second; ++it) {
const NodeSPtr candidate_sub_tree = it->second.lock();
if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) &&
!(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) &&
!polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) {
if (const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius); candidate_dist < local_current_dist) {
local_current_dist = candidate_dist;
local_sub_tree = candidate_sub_tree;
}
}
}
// To always get the same result in a parallel version as in a non-parallel version,
// we need to preserve that for the same current_dist, we select the same sub_tree
// as in the non-parallel version. For this purpose, inside the variable
// current_dist_grid_addr is stored from with 2D grid position assigned sub_tree comes.
// And when there are two sub_tree with the same current_dist, one which will be found
// the first in the non-parallel version is selected.
{
std::lock_guard<std::mutex> lock(current_dist_mutex);
if (local_current_dist < current_dist ||
(local_current_dist == current_dist && (grid_addr_y < current_dist_grid_addr.y() ||
(grid_addr_y == current_dist_grid_addr.y() && grid_addr_x < current_dist_grid_addr.x())))) {
current_dist = local_current_dist;
sub_tree = local_sub_tree;
current_dist_grid_addr = local_grid_addr;
}
}
}
}
}); // end of parallel_for
}
return ! sub_tree ?
@ -176,6 +218,7 @@ void Layer::reconnectRoots
(
std::vector<NodeSPtr>& to_be_reconnected_tree_roots,
const Polygons& current_outlines,
const BoundingBox& current_outlines_bbox,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius
@ -184,10 +227,10 @@ void Layer::reconnectRoots
constexpr coord_t tree_connecting_ignore_offset = 100;
SparseNodeGrid tree_node_locator;
fillLocator(tree_node_locator);
fillLocator(tree_node_locator, current_outlines_bbox);
const coord_t within_max_dist = outline_locator.resolution() * 2;
for (auto root_ptr : to_be_reconnected_tree_roots)
for (const auto &root_ptr : to_be_reconnected_tree_roots)
{
auto old_root_it = std::find(tree_roots.begin(), tree_roots.end(), root_ptr);
@ -203,7 +246,7 @@ void Layer::reconnectRoots
root_ptr->addChild(new_root);
new_root->reroot();
tree_node_locator.insert(std::make_pair(Point(new_root->getLocation().x() / locator_cell_size, new_root->getLocation().y() / locator_cell_size), new_root));
tree_node_locator.insert(std::make_pair(to_grid_point(new_root->getLocation(), current_outlines_bbox), new_root));
*old_root_it = std::move(new_root); // replace old root with new root
continue;
@ -217,6 +260,7 @@ void Layer::reconnectRoots
(
root_ptr->getLocation(),
current_outlines,
current_outlines_bbox,
outline_locator,
supporting_radius,
tree_connecting_ignore_width,
@ -233,7 +277,7 @@ void Layer::reconnectRoots
attach_ptr->reroot();
new_root->addChild(attach_ptr);
tree_node_locator.insert(std::make_pair(new_root->getLocation(), new_root));
tree_node_locator.insert(std::make_pair(to_grid_point(new_root->getLocation(), current_outlines_bbox), new_root));
*old_root_it = std::move(new_root); // replace old root with new root
}
@ -256,15 +300,26 @@ void Layer::reconnectRoots
}
}
/*
* Implementation assumes moving inside, but moving outside should just as well be possible.
#if 0
/*!
* Moves the point \p from onto the nearest polygon or leaves the point as-is, when the comb boundary is not within the root of \p max_dist2 distance.
* Given a \p distance more than zero, the point will end up inside, and conversely outside.
* When the point is already in/outside by more than \p distance, \p from is unaltered, but the polygon is returned.
* When the point is in/outside by less than \p distance, \p from is moved to the correct place.
* Implementation assumes moving inside, but moving outside should just as well be possible.
*
* \param polygons The polygons onto which to move the point
* \param from[in,out] The point to move.
* \param distance The distance by which to move the point.
* \param max_dist2 The squared maximal allowed distance from the point to the nearest polygon.
* \return The index to the polygon onto which we have moved the point.
*/
static unsigned int moveInside(const Polygons& polygons, Point& from, int distance, int64_t maxDist2)
{
Point ret = from;
int64_t bestDist2 = std::numeric_limits<int64_t>::max();
unsigned int bestPoly = static_cast<unsigned int>(-1);
bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
Point ret = from;
int64_t bestDist2 = std::numeric_limits<int64_t>::max();
auto bestPoly = static_cast<unsigned int>(-1);
bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
{
const Polygon &poly = polygons[poly_idx];
@ -333,7 +388,7 @@ static unsigned int moveInside(const Polygons& polygons, Point& from, int distan
else
{ // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
projected_p_beyond_prev_segment = false;
Point x = a + ab * dot_prod / ab_length2;
Point x = (a.cast<int64_t>() + ab.cast<int64_t>() * dot_prod / ab_length2).cast<coord_t>();
int64_t dist2 = (p - x).cast<int64_t>().squaredNorm();
if (dist2 < bestDist2)
@ -373,38 +428,18 @@ static unsigned int moveInside(const Polygons& polygons, Point& from, int distan
}
return static_cast<unsigned int>(-1);
}
#endif
// Returns 'added someting'.
Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const
Polylines Layer::convertToLines(const Polygons& limit_to_outline, const coord_t line_overlap) const
{
if (tree_roots.empty())
return {};
Polygons result_lines;
for (const auto& tree : tree_roots) {
// If even the furthest location in the tree is inside the polygon, the entire tree must be inside of the polygon.
// (Don't take the root as that may be on the edge and cause rounding errors to register as 'outside'.)
constexpr coord_t epsilon = 5;
Point should_be_inside = tree->getLocation();
moveInside(limit_to_outline, should_be_inside, epsilon, epsilon * epsilon);
if (inside(limit_to_outline, should_be_inside))
tree->convertToPolylines(result_lines, line_width);
}
Polylines result_lines;
for (const auto &tree : tree_roots)
tree->convertToPolylines(result_lines, line_overlap);
// TODO: allow for polylines!
Polylines split_lines;
for (Polygon &line : result_lines) {
if (line.size() <= 1)
continue;
Point last = line[0];
for (size_t point_idx = 1; point_idx < line.size(); point_idx++) {
Point here = line[point_idx];
split_lines.push_back({ last, here });
last = here;
}
}
return split_lines;
return intersection_pl(result_lines, limit_to_outline);
}
} // namespace Slic3r::Lightning

View File

@ -41,9 +41,10 @@ public:
(
const Polygons& current_overhang,
const Polygons& current_outlines,
const BoundingBox& current_outlines_bbox,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius
coord_t supporting_radius,
coord_t wall_supporting_radius
);
/*! Determine & connect to connection point in tree/outline.
@ -53,9 +54,10 @@ public:
(
const Point& unsupported_location,
const Polygons& current_outlines,
const BoundingBox& current_outlines_bbox,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius,
coord_t supporting_radius,
coord_t wall_supporting_radius,
const SparseNodeGrid& tree_node_locator,
const NodeSPtr& exclude_tree = nullptr
);
@ -71,16 +73,17 @@ public:
(
std::vector<NodeSPtr>& to_be_reconnected_tree_roots,
const Polygons& current_outlines,
const BoundingBox& current_outlines_bbox,
const EdgeGrid::Grid& outline_locator,
const coord_t supporting_radius,
const coord_t wall_supporting_radius
coord_t supporting_radius,
coord_t wall_supporting_radius
);
Polylines convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const;
Polylines convertToLines(const Polygons& limit_to_outline, coord_t line_overlap) const;
coord_t getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location);
void fillLocator(SparseNodeGrid& tree_node_locator);
void fillLocator(SparseNodeGrid& tree_node_locator, const BoundingBox& current_outlines_bbox);
};
} // namespace Slic3r::FillLightning

View File

@ -4,7 +4,6 @@
#include "TreeNode.hpp"
#include "../../Geometry.hpp"
#include "../../ClipperUtils.hpp"
namespace Slic3r::FillLightning {
@ -107,7 +106,7 @@ NodeSPtr Node::deepCopy() const
return local_root;
}
void Node::reroot(NodeSPtr new_parent /*= nullptr*/)
void Node::reroot(const NodeSPtr &new_parent)
{
if (! m_is_root) {
auto old_parent = m_parent.lock();
@ -142,7 +141,7 @@ NodeSPtr Node::closestNode(const Point& loc)
return result;
}
bool inside(const Polygons &polygons, const Point p)
bool inside(const Polygons &polygons, const Point &p)
{
int poly_count_inside = 0;
for (const Polygon &poly : polygons) {
@ -181,7 +180,11 @@ bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeG
} visitor { outline_locator, a.cast<double>(), b.cast<double>() };
outline_locator.visit_cells_intersecting_line(a, b, visitor);
return visitor.d2min < within_max_dist * within_max_dist;
if (visitor.d2min < double(within_max_dist) * double(within_max_dist)) {
result = Point(visitor.intersection_pt);
return true;
}
return false;
}
bool Node::realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector<NodeSPtr>& rerooted_parts)
@ -226,14 +229,14 @@ bool Node::realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locat
void Node::straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist)
{
straighten(magnitude, m_p, 0, max_remove_colinear_dist * max_remove_colinear_dist);
straighten(magnitude, m_p, 0, int64_t(max_remove_colinear_dist) * int64_t(max_remove_colinear_dist));
}
Node::RectilinearJunction Node::straighten(
const coord_t magnitude,
const Point& junction_above,
const coord_t accumulated_dist,
const coord_t max_remove_colinear_dist2)
const int64_t max_remove_colinear_dist2)
{
constexpr coord_t junction_magnitude_factor_numerator = 3;
constexpr coord_t junction_magnitude_factor_denominator = 4;
@ -245,13 +248,13 @@ Node::RectilinearJunction Node::straighten(
auto child_dist = coord_t((m_p - child_p->m_p).cast<double>().norm());
RectilinearJunction junction_below = child_p->straighten(magnitude, junction_above, accumulated_dist + child_dist, max_remove_colinear_dist2);
coord_t total_dist_to_junction_below = junction_below.total_recti_dist;
Point a = junction_above;
Point b = junction_below.junction_loc;
const Point& a = junction_above;
Point b = junction_below.junction_loc;
if (a != b) // should always be true!
{
Point ab = b - a;
Point destination = a + ab * accumulated_dist / std::max(coord_t(1), total_dist_to_junction_below);
if ((destination - m_p).cast<double>().squaredNorm() <= magnitude * magnitude)
Point destination = (a.cast<int64_t>() + ab.cast<int64_t>() * int64_t(accumulated_dist) / std::max(int64_t(1), int64_t(total_dist_to_junction_below))).cast<coord_t>();
if ((destination - m_p).cast<int64_t>().squaredNorm() <= int64_t(magnitude) * int64_t(magnitude))
m_p = destination;
else
m_p += ((destination - m_p).cast<double>().normalized() * magnitude).cast<coord_t>();
@ -262,7 +265,7 @@ Node::RectilinearJunction Node::straighten(
child_p = m_children.front(); //recursive call to straighten might have removed the child
const NodeSPtr& parent_node = m_parent.lock();
if (parent_node &&
(child_p->m_p - parent_node->m_p).cast<double>().squaredNorm() < max_remove_colinear_dist2 &&
(child_p->m_p - parent_node->m_p).cast<int64_t>().squaredNorm() < max_remove_colinear_dist2 &&
Line::distance_to_squared(m_p, parent_node->m_p, child_p->m_p) < close_enough * close_enough) {
child_p->m_parent = m_parent;
for (auto& sibling : parent_node->m_children)
@ -344,16 +347,16 @@ coord_t Node::prune(const coord_t& pruning_distance)
return max_distance_pruned;
}
void Node::convertToPolylines(Polygons& output, const coord_t line_width) const
void Node::convertToPolylines(Polylines &output, const coord_t line_overlap) const
{
Polygons result;
output.emplace_back();
Polylines result;
result.emplace_back();
convertToPolylines(0, result);
removeJunctionOverlap(result, line_width);
removeJunctionOverlap(result, line_overlap);
append(output, std::move(result));
}
void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const
void Node::convertToPolylines(size_t long_line_idx, Polylines &output) const
{
if (m_children.empty()) {
output[long_line_idx].points.push_back(m_p);
@ -373,11 +376,12 @@ void Node::convertToPolylines(size_t long_line_idx, Polygons& output) const
}
}
void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_width) const
void Node::removeJunctionOverlap(Polylines &result_lines, const coord_t line_overlap) const
{
const coord_t reduction = line_width / 2; // TODO make configurable?
for (auto poly_it = result_lines.begin(); poly_it != result_lines.end(); ) {
Polygon &polyline = *poly_it;
const coord_t reduction = line_overlap;
size_t res_line_idx = 0;
while (res_line_idx < result_lines.size()) {
Polyline &polyline = result_lines[res_line_idx];
if (polyline.size() <= 1) {
polyline = std::move(result_lines.back());
result_lines.pop_back();
@ -386,8 +390,8 @@ void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_widt
coord_t to_be_reduced = reduction;
Point a = polyline.back();
for (int point_idx = polyline.size() - 2; point_idx >= 0; point_idx--) {
const Point b = polyline[point_idx];
for (int point_idx = int(polyline.size()) - 2; point_idx >= 0; point_idx--) {
const Point b = polyline.points[point_idx];
const Point ab = b - a;
const auto ab_len = coord_t(ab.cast<double>().norm());
if (ab_len >= to_be_reduced) {
@ -404,8 +408,33 @@ void Node::removeJunctionOverlap(Polygons& result_lines, const coord_t line_widt
polyline = std::move(result_lines.back());
result_lines.pop_back();
} else
++ poly_it;
++ res_line_idx;
}
}
#ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT
void export_to_svg(const NodeSPtr &root_node, SVG &svg)
{
for (const NodeSPtr &children : root_node->m_children) {
svg.draw(Line(root_node->getLocation(), children->getLocation()), "red");
export_to_svg(children, svg);
}
}
void export_to_svg(const std::string &path, const Polygons &contour, const std::vector<NodeSPtr> &root_nodes) {
BoundingBox bbox = get_extents(contour);
bbox.offset(SCALED_EPSILON);
SVG svg(path, bbox);
svg.draw_outline(contour, "blue");
for (const NodeSPtr &root_node: root_nodes) {
for (const NodeSPtr &children: root_node->m_children) {
svg.draw(Line(root_node->getLocation(), children->getLocation()), "red");
export_to_svg(children, svg);
}
}
}
#endif /* LIGHTNING_TREE_NODE_DEBUG_OUTPUT */
} // namespace Slic3r::FillLightning

View File

@ -11,6 +11,9 @@
#include "../../EdgeGrid.hpp"
#include "../../Polygon.hpp"
#include "SVG.hpp"
//#define LIGHTNING_TREE_NODE_DEBUG_OUTPUT
namespace Slic3r::FillLightning
{
@ -43,7 +46,7 @@ public:
{
struct EnableMakeShared : public Node
{
EnableMakeShared(Arg&&...arg) : Node(std::forward<Arg>(arg)...) {}
explicit EnableMakeShared(Arg&&...arg) : Node(std::forward<Arg>(arg)...) {}
};
return std::make_shared<EnableMakeShared>(std::forward<Arg>(arg)...);
}
@ -99,9 +102,9 @@ public:
std::vector<NodeSPtr>& next_trees,
const Polygons& next_outlines,
const EdgeGrid::Grid& outline_locator,
const coord_t prune_distance,
const coord_t smooth_magnitude,
const coord_t max_remove_colinear_dist
coord_t prune_distance,
coord_t smooth_magnitude,
coord_t max_remove_colinear_dist
) const;
/*!
@ -156,7 +159,7 @@ public:
* This is then recursively bubbled up until it reaches the (former) root, which then will become a leaf.
* \param new_parent The (new) parent-node of the root, useful for recursing or immediately attaching the node to another tree.
*/
void reroot(NodeSPtr new_parent = nullptr);
void reroot(const NodeSPtr &new_parent = nullptr);
/*!
* Retrieves the closest node to the specified location.
@ -176,16 +179,16 @@ public:
*/
bool hasOffspring(const NodeSPtr& to_be_checked) const;
protected:
Node() = delete; // Don't allow empty contruction
protected:
/*!
* Construct a new node, either for insertion in a tree or as root.
* \param p The physical location in the 2D layer that this node represents.
* Connecting other nodes to this node indicates that a line segment should
* be drawn between those two physical positions.
*/
Node(const Point& p, const std::optional<Point>& last_grounding_location = std::nullopt);
explicit Node(const Point& p, const std::optional<Point>& last_grounding_location = std::nullopt);
/*!
* Copy this node and its entire sub-tree.
@ -211,7 +214,7 @@ protected:
* \param magnitude The maximum allowed distance to move the node.
* \param max_remove_colinear_dist Maximum distance of the (compound) line-segment from which a co-linear point may be removed.
*/
void straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist);
void straighten(coord_t magnitude, coord_t max_remove_colinear_dist);
/*! Recursive part of \ref straighten(.)
* \param junction_above The last seen junction with multiple children above
@ -219,7 +222,7 @@ protected:
* \param max_remove_colinear_dist2 Maximum distance _squared_ of the (compound) line-segment from which a co-linear point may be removed.
* \return the total distance along the tree from the last junction above to the first next junction below and the location of the next junction below
*/
RectilinearJunction straighten(const coord_t magnitude, const Point& junction_above, const coord_t accumulated_dist, const coord_t max_remove_colinear_dist2);
RectilinearJunction straighten(coord_t magnitude, const Point& junction_above, coord_t accumulated_dist, int64_t max_remove_colinear_dist2);
/*! Prune the tree from the extremeties (leaf-nodes) until the pruning distance is reached.
* \return The distance that has been pruned. If less than \p distance, then the whole tree was puned away.
@ -236,7 +239,7 @@ public:
*
* \param output all branches in this tree connected into polylines
*/
void convertToPolylines(Polygons& output, const coord_t line_width) const;
void convertToPolylines(Polylines &output, coord_t line_overlap) const;
/*! If this was ever a direct child of the root, it'll have a previous grounding location.
*
@ -244,6 +247,8 @@ public:
*/
const std::optional<Point>& getLastGroundingLocation() const { return m_last_grounding_location; }
void draw_tree(SVG& svg) { for (auto& child : m_children) { svg.draw(Line(m_p, child->getLocation()), "yellow"); child->draw_tree(svg); } }
protected:
/*!
* Convert the tree into polylines
@ -255,9 +260,9 @@ protected:
* \param long_line a reference to a polyline in \p output which to continue building on in the recursion
* \param output all branches in this tree connected into polylines
*/
void convertToPolylines(size_t long_line_idx, Polygons& output) const;
void convertToPolylines(size_t long_line_idx, Polylines &output) const;
void removeJunctionOverlap(Polygons& polylines, const coord_t line_width) const;
void removeJunctionOverlap(Polylines &polylines, coord_t line_overlap) const;
bool m_is_root;
Point m_p;
@ -265,10 +270,40 @@ protected:
std::vector<NodeSPtr> m_children;
std::optional<Point> m_last_grounding_location; //<! The last known grounding location, see 'getLastGroundingLocation()'.
friend BoundingBox get_extents(const NodeSPtr &root_node);
friend BoundingBox get_extents(const std::vector<NodeSPtr> &tree_roots);
#ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT
friend void export_to_svg(const NodeSPtr &root_node, Slic3r::SVG &svg);
friend void export_to_svg(const std::string &path, const Polygons &contour, const std::vector<NodeSPtr> &root_nodes);
#endif /* LIGHTNING_TREE_NODE_DEBUG_OUTPUT */
};
bool inside(const Polygons &polygons, const Point p);
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist);
bool inside(const Polygons &polygons, const Point &p);
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, coord_t within_max_dist);
inline BoundingBox get_extents(const NodeSPtr &root_node)
{
BoundingBox bbox;
for (const NodeSPtr &children : root_node->m_children)
bbox.merge(get_extents(children));
bbox.merge(root_node->getLocation());
return bbox;
}
inline BoundingBox get_extents(const std::vector<NodeSPtr> &tree_roots)
{
BoundingBox bbox;
for (const NodeSPtr &root_node : tree_roots)
bbox.merge(get_extents(root_node));
return bbox;
}
#ifdef LIGHTNING_TREE_NODE_DEBUG_OUTPUT
void export_to_svg(const NodeSPtr &root_node, SVG &svg);
void export_to_svg(const std::string &path, const Polygons &contour, const std::vector<NodeSPtr> &root_nodes);
#endif /* LIGHTNING_TREE_NODE_DEBUG_OUTPUT */
} // namespace Slic3r::FillLightning

View File

@ -3832,12 +3832,20 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
if (role == erSupportMaterial || role == erSupportTransition) {
const SupportLayer* support_layer = dynamic_cast<const SupportLayer*>(m_layer);
//FIXME support_layer->support_islands.contains should use some search structure!
if (support_layer != NULL && support_layer->support_islands.contains(travel))
// skip retraction if this is a travel move inside a support material island
//FIXME not retracting over a long path may cause oozing, which in turn may result in missing material
// at the end of the extrusion path!
return false;
//reduce the retractions in lightning infills for tree support
const TreeSupportLayer* ts_layer = dynamic_cast<const TreeSupportLayer*>(m_layer);
if (ts_layer != NULL)
for (auto& area : ts_layer->base_areas)
if(area.contains(travel))
return false;
}
//BBS: need retract when long moving to print perimeter to avoid dropping of material

View File

@ -19,6 +19,10 @@ namespace FillAdaptive {
struct Octree;
};
namespace FillLightning {
class Generator;
};
class LayerRegion
{
public:
@ -166,7 +170,7 @@ public:
void make_perimeters();
// Phony version of make_fills() without parameters for Perl integration only.
void make_fills() { this->make_fills(nullptr, nullptr); }
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree);
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator = nullptr);
void make_ironing();
void export_region_slices_to_svg(const char *path) const;
@ -245,7 +249,7 @@ public:
ExPolygons floor_areas;
ExPolygons base_areas;
enum AreaType {
enum AreaType {
BaseType=0,
RoofType=1,
FloorType=2,

View File

@ -180,6 +180,11 @@ inline bool operator<(const Point &l, const Point &r)
return l.x() < r.x() || (l.x() == r.x() && l.y() < r.y());
}
inline Point operator* (const Point& l, const double& r)
{
return { coord_t(l.x() * r), coord_t(l.y() * r) };
}
inline bool is_approx(const Point &p1, const Point &p2, coord_t epsilon = coord_t(SCALED_EPSILON))
{
Point d = (p2 - p1).cwiseAbs();

View File

@ -61,6 +61,12 @@ namespace FillAdaptive {
using OctreePtr = std::unique_ptr<Octree, OctreeDeleter>;
};
namespace FillLightning {
class Generator;
struct GeneratorDeleter;
using GeneratorPtr = std::unique_ptr<Generator, GeneratorDeleter>;
}; // namespace FillLightning
// Print step IDs for keeping track of the print state.
// The Print steps are applied in this order.
enum PrintStep {
@ -466,6 +472,7 @@ private:
void combine_infill();
void _generate_support_material();
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> prepare_adaptive_infill_data();
FillLightning::GeneratorPtr prepare_lightning_infill_data();
// BBS
SupportNecessaryType is_support_necessary();

View File

@ -171,7 +171,10 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SlicingMode)
static t_config_enum_values s_keys_map_SupportMaterialPattern {
{ "rectilinear", smpRectilinear },
{ "rectilinear-grid", smpRectilinearGrid },
{ "honeycomb", smpHoneycomb }
{ "honeycomb", smpHoneycomb },
#if HAS_LIGHTNING_INFILL
{ "lightning", smpLightning },
#endif
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportMaterialPattern)
@ -1201,7 +1204,7 @@ void PrintConfigDef::init_fff_params()
//def->enum_values.push_back("octagramspiral");
//def->enum_values.push_back("supportcubic");
#if HAS_LIGHTNING_INFILL
//def->enum_values.push_back("lightning");
def->enum_values.push_back("lightning");
#endif // HAS_LIGHTNING_INFILL
def->enum_labels.push_back(L("Concentric"));
def->enum_labels.push_back(L("Rectilinear"));
@ -1220,7 +1223,7 @@ void PrintConfigDef::init_fff_params()
//def->enum_labels.push_back(L("Octagram Spiral"));
//def->enum_labels.push_back(L("Support Cubic"));
#if HAS_LIGHTNING_INFILL
//def->enum_labels.push_back(L("Lightning"));
def->enum_labels.push_back(L("Lightning"));
#endif // HAS_LIGHTNING_INFILL
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipCubic));
@ -2442,9 +2445,15 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("rectilinear");
def->enum_values.push_back("rectilinear-grid");
def->enum_values.push_back("honeycomb");
#if HAS_LIGHTNING_INFILL
def->enum_values.push_back("lightning");
#endif
def->enum_labels.push_back(L("Rectilinear"));
def->enum_labels.push_back(L("Rectilinear grid"));
def->enum_labels.push_back(L("Honeycomb"));
#if HAS_LIGHTNING_INFILL
def->enum_labels.push_back(L("Lightning"));
#endif
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<SupportMaterialPattern>(smpRectilinear));

View File

@ -50,7 +50,7 @@ enum AuthorizationType {
atKeyPassword, atUserPassword
};
#define HAS_LIGHTNING_INFILL 0
#define HAS_LIGHTNING_INFILL 1
enum InfillPattern : int {
ipConcentric, ipRectilinear, ipGrid, ipLine, ipCubic, ipTriangles, ipStars, ipGyroid, ipHoneycomb, ipAdaptiveCubic, ipMonotonic, ipMonotonicLine, ipAlignedRectilinear, ip3DHoneycomb,
@ -96,6 +96,9 @@ enum class SlicingMode
enum SupportMaterialPattern {
smpRectilinear, smpRectilinearGrid, smpHoneycomb,
#if HAS_LIGHTNING_INFILL
smpLightning,
#endif // HAS_LIGHTNING_INFILL
};
enum SupportMaterialStyle {

View File

@ -14,6 +14,7 @@
#include "TriangleMeshSlicer.hpp"
#include "Utils.hpp"
#include "Fill/FillAdaptive.hpp"
#include "Fill/FillLightning.hpp"
#include "Format/STL.hpp"
#include "InternalBridgeDetector.hpp"
#include "TreeSupport.hpp"
@ -367,14 +368,16 @@ void PrintObject::infill()
m_print->set_status(35, L("Generating infill toolpath"));
auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data();
auto lightning_generator = this->prepare_lightning_infill_data();
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, m_layers.size()),
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range<size_t>& range) {
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree, &lightning_generator](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
m_print->throw_if_canceled();
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get());
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), lightning_generator.get());
}
}
);
@ -534,6 +537,18 @@ std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare
support_line_spacing ? build_octree(mesh, overhangs.front(), support_line_spacing, true) : OctreePtr());
}
FillLightning::GeneratorPtr PrintObject::prepare_lightning_infill_data()
{
bool has_lightning_infill = false;
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id)
if (const PrintRegionConfig& config = this->printing_region(region_id).config(); config.sparse_infill_density > 0 && config.sparse_infill_pattern == ipLightning) {
has_lightning_infill = true;
break;
}
return has_lightning_infill ? FillLightning::build_generator(std::as_const(*this)) : FillLightning::GeneratorPtr();
}
void PrintObject::clear_layers()
{
if (!m_shared_object) {

View File

@ -11,6 +11,8 @@
#include "I18N.hpp"
#include <libnest2d/backends/libslic3r/geometries.hpp>
#include "Fill/FillBase.hpp"
#define _L(s) Slic3r::I18N::translate(s)
@ -283,6 +285,49 @@ static void draw_layer_mst
svg.draw(lines, "blue", coord_t(scale_(0.05)));
svg.draw_outline(outline, "yellow");
}
static void draw_two_overhangs_to_svg(TreeSupportLayer* ts_layer, const ExPolygons& overhangs1, const ExPolygons& overhangs2)
{
if (overhangs1.empty() && overhangs2.empty())
return;
BoundingBox bbox1 = get_extents(overhangs1);
BoundingBox bbox2 = get_extents(overhangs2);
bbox1.merge(bbox2);
SVG svg(get_svg_filename(std::to_string(ts_layer->print_z), "two_overhangs"), bbox1);
if (!svg.is_opened()) return;
svg.draw(union_ex(overhangs1), "blue");
svg.draw(union_ex(overhangs2), "red");
}
static void draw_polylines(TreeSupportLayer* ts_layer, Polylines& polylines)
{
if (polylines.empty())
return;
BoundingBox bbox = get_extents(polylines);
SVG svg(get_svg_filename(std::to_string(ts_layer->print_z), "lightnings"), bbox);
if (!svg.is_opened()) return;
int id = 0;
for (Polyline& pline : polylines)
{
int i1, i2;
for (size_t i = 0; i < pline.size() - 1; i++)
{
i1 = i;
i2 = i + 1;
svg.draw(Line(pline.points[i1], pline.points[i2]), "blue");
svg.draw(pline.points[i1], "red");
id++;
svg.draw_text(pline.points[i1], std::to_string(id).c_str(), "black", 1);
}
svg.draw(pline.points[i2], "red");
id++;
svg.draw_text(pline.points[i2], std::to_string(id).c_str(), "black", 1);
}
}
#endif
// Move point from inside polygon if distance>0, outside if distance<0.
@ -641,7 +686,11 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p
m_raft_layers = slicing_params.base_raft_layers + slicing_params.interface_raft_layers;
SupportMaterialPattern support_pattern = m_object_config->support_base_pattern;
m_support_params.base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb :
m_support_params.base_fill_pattern =
#if HAS_LIGHTNING_INFILL
support_pattern == smpLightning ? ipLightning :
#endif
support_pattern == smpHoneycomb ? ipHoneycomb :
m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear :
ipSupportBase;
m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
@ -1507,7 +1556,7 @@ void TreeSupport::generate_toolpaths()
ExtrusionRole role;
Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_object->print()->brim_flow() :
(m_support_params.base_fill_pattern == ipRectilinear && (layer_id % num_layers_to_change_infill_direction == 0) ? support_transition_flow(m_object) : support_flow);
if (with_infill && layer_id > 0) {
if (with_infill && layer_id > 0 && m_support_params.base_fill_pattern != ipLightning) {
if (m_support_params.base_fill_pattern == ipRectilinear) {
role = erSupportMaterial;// layer_id% num_layers_to_change_infill_direction == 0 ? erSupportTransition : erSupportMaterial;
filler_support->angle = Geometry::deg2rad(object_config.support_angle.value);// obj_is_vertical* M_PI_2;// (obj_is_vertical + int(layer_id / num_layers_to_change_infill_direction))* M_PI_2;
@ -1541,6 +1590,67 @@ void TreeSupport::generate_toolpaths()
}
}
}
if (with_infill && m_support_params.base_fill_pattern == ipLightning)
{
double print_z = ts_layer->print_z;
if (printZ_to_lightninglayer.find(print_z) == printZ_to_lightninglayer.end())
continue;
//TODO:
//1.the second parameter of convertToLines seems to decide how long the lightning should be trimmed from its root, so that the root wont overlap/detach the support contour.
// whether current value works correctly remained to be tested
//2.related to previous one, that lightning roots need to be trimed more when support has multiple walls
//3.function connect_infill() and variable 'params' helps create connection pattern along contours between two lightning roots,
// strengthen lightnings while it may make support harder. decide to enable it or not. if yes, proper values for params are remained to be tested
auto& lightning_layer = generator->getTreesForLayer(printZ_to_lightninglayer[print_z]);
Flow flow = (layer_id == 0 && m_raft_layers == 0) ?
m_object->print()->brim_flow() :
(m_support_params.base_fill_pattern == ipRectilinear && (layer_id % num_layers_to_change_infill_direction == 0) ?
support_transition_flow(m_object) :
support_flow);
ExPolygons areas = offset_ex(ts_layer->base_areas, -flow.scaled_spacing());
for (auto& area : areas)
{
Polylines polylines = lightning_layer.convertToLines(to_polygons(area), 0);
for (auto itr = polylines.begin(); itr != polylines.end();)
{
if (itr->length() < scale_(1.0))
itr = polylines.erase(itr);
else
itr++;
}
Polylines opt_polylines;
#if 1
//this wont create connection patterns along contours
append(opt_polylines, chain_polylines(std::move(polylines)));
#else
//this will create connection patterns along contours
FillParams params;
params.anchor_length = float(Fill::infill_anchor * 0.01 * flow.spacing());
params.anchor_length_max = Fill::infill_anchor_max;
params.anchor_length = std::min(params.anchor_length, params.anchor_length_max);
Fill::connect_infill(std::move(polylines), area, opt_polylines, flow.spacing(), params);
#endif
extrusion_entities_append_paths(ts_layer->support_fills.entities, opt_polylines, erSupportMaterial,
float(flow.mm3_per_mm()), float(flow.width()), float(flow.height()));
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
std::string prefix = "./SVG/";
std::string suffix = ".svg";
std::string name = prefix + "trees_polyline" + "_" + std::to_string(ts_layer->print_z) /*+ "_" + std::to_string(rand_num)*/ + suffix;
BoundingBox bbox = get_extents(ts_layer->base_areas);
SVG svg(name, bbox);
svg.draw(ts_layer->base_areas, "blue");
svg.draw(generator->Overhangs()[printZ_to_lightninglayer[print_z]], "red");
for (auto& line : opt_polylines)
{
svg.draw(line, "yellow");
}
#endif
}
}
// sort extrusions to reduce travel, also make sure walls go before infills
chain_and_reorder_extrusion_entities(ts_layer->support_fills.entities);
@ -1895,6 +2005,15 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
const coordf_t line_width = config.support_line_width;
const coordf_t line_width_scaled = scale_(line_width);
const bool with_lightning_infill = config.tree_support_with_infill.value && config.support_base_pattern.value == smpLightning;
coordf_t support_extrusion_width = config.support_line_width.value > 0 ? config.support_line_width : config.line_width;
const size_t wall_count = config.tree_support_wall_count.value;
const PrintObjectConfig& object_config = m_object->config();
auto m_support_material_flow = support_material_flow(m_object, float(m_slicing_params.layer_height));
coordf_t support_spacing = object_config.support_base_pattern_spacing.value + m_support_material_flow.spacing();
coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing);
// coconut: previously std::unordered_map in m_collision_cache is not multi-thread safe which may cause programs stuck, here we change to tbb::concurrent_unordered_map
tbb::parallel_for(
tbb::blocked_range<size_t>(0, m_object->layer_count()),
@ -2060,100 +2179,167 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
});
#if 1
// move the holes to contour so they can be well supported
if (!has_infill) {
// 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;
for (auto from : poly.points) {
for (int i = 0; i < expoly.num_contours(); i++) {
const Point *candidate = expoly.contour_or_hole(i).closest_point(from);
double dist2 = vsize2_with_unscale(*candidate - from);
if (dist2 < min_dist) {
min_dist = dist2;
pt_on_poly = from;
pt_on_expoly = *candidate;
}
if (dist2 > max_dist) {
max_dist = dist2;
pt_far_on_poly = from;
}
if (dist2 < dist_thresh) { return true; }
if (with_lightning_infill)
{
const bool global_lightning_infill = true;
std::vector<Polygons> contours;
std::vector<Polygons> overhangs;
for (int layer_nr = 1; layer_nr < m_object->layer_count(); layer_nr++) {
if (print->canceled()) break;
const std::vector<Node*>& curr_layer_nodes = contact_nodes[layer_nr];
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
assert(ts_layer != nullptr);
// skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
if (curr_layer_nodes.empty()) continue;
if (ts_layer->height < EPSILON) continue;
if (ts_layer->area_groups.empty()) continue;
ExPolygons& base_areas = ts_layer->base_areas;
int layer_nr_lower = layer_nr - 1;
for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) {
if (!m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break;
}
TreeSupportLayer* lower_layer = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers);
ExPolygons& base_areas_lower = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->base_areas;
ExPolygons overhang;
if (layer_nr_lower == 0)
continue;
if (global_lightning_infill)
{
//search overhangs globally
overhang = std::move(diff_ex(offset_ex(base_areas_lower, -2.0 * scale_(support_extrusion_width)), base_areas));
}
else
{
//search overhangs only on floating islands
for (auto& base_area : base_areas)
for (auto& hole : base_area.holes)
{
Polygon rev_hole = hole;
rev_hole.make_counter_clockwise();
ExPolygons ex_hole = to_expolygons(ExPolygon(rev_hole));
for (auto& other_area : base_areas)
//if (&other_area != &base_area)
ex_hole = std::move(diff_ex(ex_hole, other_area));
overhang = std::move(union_ex(overhang, ex_hole));
}
overhang = std::move(intersection_ex(overhang, offset_ex(base_areas_lower, -0.5 * scale_(support_extrusion_width))));
}
overhangs.emplace_back(to_polygons(overhang));
contours.emplace_back(to_polygons(base_areas_lower)); //cant guarantee for 100% success probability, infill fails sometimes
printZ_to_lightninglayer[lower_layer->print_z] = overhangs.size() - 1;
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
draw_two_overhangs_to_svg(m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers), base_areas_lower, to_expolygons(overhangs.back()));
#endif
}
return false;
};
// polygon pointer: depth, direction, farPoint
std::map<const Polygon *, std::tuple<int, Point, Point>> holePropagationInfos;
for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
if (print->canceled()) break;
m_object->print()->set_status(66, (boost::format(_L("Support: fix holes at layer %d")) % layer_nr).str());
generator = std::make_unique<FillLightning::Generator>(m_object, contours, overhangs, support_density);
}
const std::vector<Node *> &curr_layer_nodes = contact_nodes[layer_nr];
TreeSupportLayer * ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
assert(ts_layer != nullptr);
else if (!has_infill) {
// move the holes to contour so they can be well supported
// skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
if (curr_layer_nodes.empty()) continue;
if (ts_layer->height < EPSILON) continue;
if (ts_layer->area_groups.empty()) continue;
int layer_nr_lower = layer_nr - 1;
for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) {
if (!m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break;
}
auto &area_groups_lower = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups;
for (const auto &area_group : ts_layer->area_groups) {
if (area_group.second != TreeSupportLayer::BaseType) continue;
const auto area = area_group.first;
for (const auto &hole : area->holes) {
// auto hole_bbox = get_extents(hole).polygon();
for (auto &area_group_lower : area_groups_lower) {
if (area_group.second != TreeSupportLayer::BaseType) continue;
auto &base_area_lower = *area_group_lower.first;
Point pt_on_poly, pt_on_expoly, pt_far_on_poly;
// if a hole doesn't intersect with lower layer's contours, add a hole to lower layer and move it slightly to the contour
if (base_area_lower.contour.contains(hole.points.front()) && !intersects_contour(hole, base_area_lower, pt_on_poly, pt_on_expoly, pt_far_on_poly)) {
Polygon hole_lower = hole;
Point direction = normal(pt_on_expoly - pt_on_poly, line_width_scaled / 2);
hole_lower.translate(direction);
// note to expand a hole, we need to do negative offset
auto hole_expanded = offset(hole_lower, -line_width_scaled / 4, ClipperLib::JoinType::jtSquare);
if (!hole_expanded.empty()) {
base_area_lower.holes.push_back(std::move(hole_expanded[0]));
holePropagationInfos.insert({&base_area_lower.holes.back(), {25, direction, pt_far_on_poly}});
// 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;
for (auto from : poly.points) {
for (int i = 0; i < expoly.num_contours(); i++) {
const Point* candidate = expoly.contour_or_hole(i).closest_point(from);
double dist2 = vsize2_with_unscale(*candidate - from);
if (dist2 < min_dist) {
min_dist = dist2;
pt_on_poly = from;
pt_on_expoly = *candidate;
}
break;
} else if (holePropagationInfos.find(&hole) != holePropagationInfos.end() && std::get<0>(holePropagationInfos[&hole]) > 0 &&
base_area_lower.contour.contains(std::get<2>(holePropagationInfos[&hole]))) {
Polygon hole_lower = hole;
auto && direction = std::get<1>(holePropagationInfos[&hole]);
hole_lower.translate(direction);
// note to shrink a hole, we need to do positive offset
auto hole_expanded = offset(hole_lower, line_width_scaled / 2, ClipperLib::JoinType::jtSquare);
Point farPoint = std::get<2>(holePropagationInfos[&hole]) + direction * 2;
if (!hole_expanded.empty()) {
base_area_lower.holes.push_back(std::move(hole_expanded[0]));
holePropagationInfos.insert({&base_area_lower.holes.back(), {std::get<0>(holePropagationInfos[&hole]) - 1, direction, farPoint}});
if (dist2 > max_dist) {
max_dist = dist2;
pt_far_on_poly = from;
}
break;
if (dist2 < dist_thresh) { return true; }
}
}
{
return false;
};
// polygon pointer: depth, direction, farPoint
std::map<const Polygon*, std::tuple<int, Point, Point>> holePropagationInfos;
for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
if (print->canceled()) break;
m_object->print()->set_status(66, (boost::format(_L("Support: fix holes at layer %d")) % layer_nr).str());
const std::vector<Node*>& curr_layer_nodes = contact_nodes[layer_nr];
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
assert(ts_layer != nullptr);
// skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
if (curr_layer_nodes.empty()) continue;
if (ts_layer->height < EPSILON) continue;
if (ts_layer->area_groups.empty()) continue;
int layer_nr_lower = layer_nr - 1;
for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) {
if (!m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break;
}
auto& area_groups_lower = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups;
for (const auto& area_group : ts_layer->area_groups) {
if (area_group.second != TreeSupportLayer::BaseType) continue;
const auto area = area_group.first;
for (const auto& hole : area->holes) {
// auto hole_bbox = get_extents(hole).polygon();
for (auto& area_group_lower : area_groups_lower) {
if (area_group.second != TreeSupportLayer::BaseType) continue;
auto& base_area_lower = *area_group_lower.first;
Point pt_on_poly, pt_on_expoly, pt_far_on_poly;
// if a hole doesn't intersect with lower layer's contours, add a hole to lower layer and move it slightly to the contour
if (base_area_lower.contour.contains(hole.points.front()) && !intersects_contour(hole, base_area_lower, pt_on_poly, pt_on_expoly, pt_far_on_poly)) {
Polygon hole_lower = hole;
Point direction = normal(pt_on_expoly - pt_on_poly, line_width_scaled / 2);
hole_lower.translate(direction);
// note to expand a hole, we need to do negative offset
auto hole_expanded = offset(hole_lower, -line_width_scaled / 4, ClipperLib::JoinType::jtSquare);
if (!hole_expanded.empty()) {
base_area_lower.holes.push_back(std::move(hole_expanded[0]));
holePropagationInfos.insert({ &base_area_lower.holes.back(), {25, direction, pt_far_on_poly} });
}
break;
}
else if (holePropagationInfos.find(&hole) != holePropagationInfos.end() && std::get<0>(holePropagationInfos[&hole]) > 0 &&
base_area_lower.contour.contains(std::get<2>(holePropagationInfos[&hole]))) {
Polygon hole_lower = hole;
auto&& direction = std::get<1>(holePropagationInfos[&hole]);
hole_lower.translate(direction);
// note to shrink a hole, we need to do positive offset
auto hole_expanded = offset(hole_lower, line_width_scaled / 2, ClipperLib::JoinType::jtSquare);
Point farPoint = std::get<2>(holePropagationInfos[&hole]) + direction * 2;
if (!hole_expanded.empty()) {
base_area_lower.holes.push_back(std::move(hole_expanded[0]));
holePropagationInfos.insert({ &base_area_lower.holes.back(), {std::get<0>(holePropagationInfos[&hole]) - 1, direction, farPoint} });
}
break;
}
}
{
// if roof1 interface is inside a hole, need to expand the interface
for (auto &roof1 : ts_layer->roof_1st_layer) {
for (auto& roof1 : ts_layer->roof_1st_layer) {
//if (hole.contains(roof1.contour.points.front()) && hole.contains(roof1.contour.bounding_box().center()))
bool is_inside_hole = std::all_of(roof1.contour.points.begin(), roof1.contour.points.end(), [&hole](Point &pt) { return hole.contains(pt); });
bool is_inside_hole = std::all_of(roof1.contour.points.begin(), roof1.contour.points.end(), [&hole](Point& pt) { return hole.contains(pt); });
if (is_inside_hole) {
Polygon hole_reoriented = hole;
if (roof1.contour.is_counter_clockwise())
hole_reoriented.make_counter_clockwise();
else if (roof1.contour.is_clockwise())
hole_reoriented.make_clockwise();
auto tmp = union_({roof1.contour}, {hole_reoriented});
auto tmp = union_({ roof1.contour }, { hole_reoriented });
if (!tmp.empty()) roof1.contour = tmp[0];
// make sure 1) roof1 and object 2) roof1 and roof, won't intersect
@ -2163,38 +2349,38 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
tmp1 = diff_ex(tmp1, ts_layer->roof_areas);
if (!tmp1.empty()) {
roof1.contour = std::move(tmp1[0].contour);
roof1.holes = std::move(tmp1[0].holes);
}
roof1.holes = std::move(tmp1[0].holes);
}
break;
}
}
}
}
}
}
}
}
}
#endif
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
ExPolygons& base_areas = ts_layer->base_areas;
ExPolygons& roof_areas = ts_layer->roof_areas;
ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
ExPolygons& floor_areas = ts_layer->floor_areas;
if (base_areas.empty() && roof_areas.empty() && roof_1st_layer.empty()) continue;
char fname[10]; sprintf(fname, "%d_%.2f", layer_nr, ts_layer->print_z);
draw_contours_and_nodes_to_svg(-1, base_areas, roof_areas, roof_1st_layer, {}, {}, get_svg_filename(fname, "circles"), {"base", "roof", "roof1st"});
}
for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
ExPolygons& base_areas = ts_layer->base_areas;
ExPolygons& roof_areas = ts_layer->roof_areas;
ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
ExPolygons& floor_areas = ts_layer->floor_areas;
if (base_areas.empty() && roof_areas.empty() && roof_1st_layer.empty()) continue;
char fname[10]; sprintf(fname, "%d_%.2f", layer_nr, ts_layer->print_z);
draw_contours_and_nodes_to_svg(-1, base_areas, roof_areas, roof_1st_layer, {}, {}, get_svg_filename(fname, "circles"), { "base", "roof", "roof1st" });
}
#endif
TreeSupportLayerPtrs& ts_layers = m_object->tree_support_layers();
auto iter = std::remove_if(ts_layers.begin(), ts_layers.end(), [](TreeSupportLayer* ts_layer) { return ts_layer->height < EPSILON; });
ts_layers.erase(iter, ts_layers.end());
for (int layer_nr = 0; layer_nr < ts_layers.size(); layer_nr++) {
ts_layers[layer_nr]->upper_layer = layer_nr != ts_layers.size() - 1 ? ts_layers[layer_nr + 1] : nullptr;
ts_layers[layer_nr]->lower_layer = layer_nr > 0 ? ts_layers[layer_nr - 1] : nullptr;
}
TreeSupportLayerPtrs& ts_layers = m_object->tree_support_layers();
auto iter = std::remove_if(ts_layers.begin(), ts_layers.end(), [](TreeSupportLayer* ts_layer) { return ts_layer->height < EPSILON; });
ts_layers.erase(iter, ts_layers.end());
for (int layer_nr = 0; layer_nr < ts_layers.size(); layer_nr++) {
ts_layers[layer_nr]->upper_layer = layer_nr != ts_layers.size() - 1 ? ts_layers[layer_nr + 1] : nullptr;
ts_layers[layer_nr]->lower_layer = layer_nr > 0 ? ts_layers[layer_nr - 1] : nullptr;
}
}
void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)

View File

@ -10,6 +10,7 @@
#include "tbb/concurrent_unordered_map.h"
#include "Flow.hpp"
#include "PrintConfig.hpp"
#include "Fill/Lightning/Generator.hpp"
#ifndef SQ
#define SQ(x) ((x)*(x))
@ -350,6 +351,9 @@ public:
int avg_node_per_layer = 0;
float nodes_angle = 0;
bool has_sharp_tail;
std::unique_ptr<FillLightning::Generator> generator;
std::unordered_map<double, size_t> printZ_to_lightninglayer;
private:
/*!
* \brief Generator for model collision, avoidance and internal guide volumes