mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 04:05:52 +08:00
WIP Cura Lightning infill, initial port.
This commit is contained in:
parent
f03f8f52d7
commit
e8697d2fc2
@ -69,6 +69,14 @@ add_library(libslic3r STATIC
|
|||||||
Fill/FillLine.hpp
|
Fill/FillLine.hpp
|
||||||
Fill/FillRectilinear.cpp
|
Fill/FillRectilinear.cpp
|
||||||
Fill/FillRectilinear.hpp
|
Fill/FillRectilinear.hpp
|
||||||
|
Fill/Lightning/DistanceField.cpp
|
||||||
|
Fill/Lightning/DistanceField.hpp
|
||||||
|
Fill/Lightning/Generator.cpp
|
||||||
|
Fill/Lightning/Generator.hpp
|
||||||
|
Fill/Lightning/Layer.cpp
|
||||||
|
Fill/Lightning/Layer.hpp
|
||||||
|
Fill/Lightning/TreeNode.cpp
|
||||||
|
Fill/Lightning/TreeNode.hpp
|
||||||
Flow.cpp
|
Flow.cpp
|
||||||
Flow.hpp
|
Flow.hpp
|
||||||
format.hpp
|
format.hpp
|
||||||
|
@ -3041,5 +3041,40 @@ Polylines FillSupportBase::fill_surface(const Surface *surface, const FillParams
|
|||||||
return polylines_out;
|
return polylines_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Slic3r
|
Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing)
|
||||||
|
{
|
||||||
|
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(),
|
||||||
|
spacing);
|
||||||
|
|
||||||
|
Points out;
|
||||||
|
for (const SegmentedIntersectionLine &sil : segs) {
|
||||||
|
for (size_t i = 0; i < sil.intersections.size(); i += 2) {
|
||||||
|
coord_t a = sil.intersections[i].pos();
|
||||||
|
coord_t b = sil.intersections[i + 1].pos();
|
||||||
|
for (coord_t y = a - (a % spacing) - spacing; y < b; y += spacing)
|
||||||
|
if (y > a)
|
||||||
|
out.emplace_back(sil.pos, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing)
|
||||||
|
{
|
||||||
|
Points out;
|
||||||
|
for (const ExPolygon &expoly : expolygons)
|
||||||
|
append(out, sample_grid_pattern(expoly, spacing));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Points sample_grid_pattern(const Polygons &polygons, coord_t spacing)
|
||||||
|
{
|
||||||
|
return sample_grid_pattern(union_ex(polygons), spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
@ -109,6 +109,10 @@ protected:
|
|||||||
float _layer_angle(size_t idx) const override { return 0.f; }
|
float _layer_angle(size_t idx) const override { return 0.f; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing);
|
||||||
|
Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing);
|
||||||
|
Points sample_grid_pattern(const Polygons &polygons, coord_t spacing);
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif // slic3r_FillRectilinear_hpp_
|
#endif // slic3r_FillRectilinear_hpp_
|
||||||
|
98
src/libslic3r/Fill/Lightning/DistanceField.cpp
Normal file
98
src/libslic3r/Fill/Lightning/DistanceField.cpp
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
//Copyright (c) 2021 Ultimaker B.V.
|
||||||
|
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
#include "DistanceField.hpp" //Class we're implementing.
|
||||||
|
#include "../FillRectilinear.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r
|
||||||
|
{
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
LightningDistanceField::LightningDistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang) :
|
||||||
|
m_cell_size(radius / radius_per_cell_size),
|
||||||
|
m_supporting_radius(radius)
|
||||||
|
{
|
||||||
|
m_supporting_radius2 = double(radius) * double(radius);
|
||||||
|
// Sample source polygons with a regular grid sampling pattern.
|
||||||
|
for (const ExPolygon &expoly : union_ex(current_outline)) {
|
||||||
|
for (const Point &p : sample_grid_pattern(expoly, m_cell_size)) {
|
||||||
|
// Find a squared distance to the source expolygon boundary.
|
||||||
|
double d2 = std::numeric_limits<double>::max();
|
||||||
|
for (size_t icontour = 0; icontour <= expoly.holes.size(); ++ icontour) {
|
||||||
|
const Polygon &contour = icontour == 0 ? expoly.contour : expoly.holes[icontour - 1];
|
||||||
|
if (contour.size() > 2) {
|
||||||
|
Point prev = contour.points.back();
|
||||||
|
for (const Point &p2 : contour.points) {
|
||||||
|
d2 = std::min(d2, Line::distance_to_squared(p, prev, p2));
|
||||||
|
prev = p2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_unsupported_points.emplace_back(p, sqrt(d2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_unsupported_points.sort([&radius](const UnsupportedCell &a, const UnsupportedCell &b) {
|
||||||
|
constexpr coord_t prime_for_hash = 191;
|
||||||
|
return std::abs(b.dist_to_boundary - a.dist_to_boundary) > radius ?
|
||||||
|
a.dist_to_boundary < b.dist_to_boundary :
|
||||||
|
(PointHash{}(a.loc) % prime_for_hash) < (PointHash{}(b.loc) % prime_for_hash);
|
||||||
|
});
|
||||||
|
for (auto it = m_unsupported_points.begin(); it != m_unsupported_points.end(); ++it) {
|
||||||
|
UnsupportedCell& cell = *it;
|
||||||
|
m_unsupported_points_grid.emplace(Point{ cell.loc.x() / m_cell_size, cell.loc.y() / m_cell_size }, it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightningDistanceField::update(const Point& to_node, const Point& added_leaf)
|
||||||
|
{
|
||||||
|
Vec2d v = (added_leaf - to_node).cast<double>();
|
||||||
|
auto l2 = v.squaredNorm();
|
||||||
|
Vec2d extent = Vec2d(-v.y(), v.x()) * m_supporting_radius / sqrt(l2);
|
||||||
|
|
||||||
|
BoundingBox grid;
|
||||||
|
{
|
||||||
|
Point diagonal(m_supporting_radius, m_supporting_radius);
|
||||||
|
Point iextent(extent.cast<coord_t>());
|
||||||
|
grid = BoundingBox(added_leaf - diagonal, added_leaf + diagonal);
|
||||||
|
grid.merge(to_node - iextent);
|
||||||
|
grid.merge(to_node + iextent);
|
||||||
|
grid.merge(added_leaf - iextent);
|
||||||
|
grid.merge(added_leaf + iextent);
|
||||||
|
grid.min /= m_cell_size;
|
||||||
|
grid.max /= m_cell_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point grid_loc;
|
||||||
|
for (coord_t row = grid.min.y(); row <= grid.max.y(); ++ row) {
|
||||||
|
grid_loc.y() = row * m_cell_size;
|
||||||
|
for (coord_t col = grid.min.x(); col <= grid.max.y(); ++ col) {
|
||||||
|
grid_loc.x() = col * m_cell_size;
|
||||||
|
// Test inside a circle at the new leaf.
|
||||||
|
if ((grid_loc - added_leaf).cast<double>().squaredNorm() > m_supporting_radius2) {
|
||||||
|
// Not inside a circle at the end of the new leaf.
|
||||||
|
// Test inside a rotated rectangle.
|
||||||
|
Vec2d vx = (grid_loc - to_node).cast<double>();
|
||||||
|
double d = v.dot(vx);
|
||||||
|
if (d >= 0 && d <= l2) {
|
||||||
|
d = extent.dot(vx);
|
||||||
|
if (d < -1. || d > 1.)
|
||||||
|
// Not inside a rotated rectangle.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Inside a circle at the end of the new leaf, or inside a rotated rectangle.
|
||||||
|
// Remove unsupported leafs at this grid location.
|
||||||
|
if (auto it = m_unsupported_points_grid.find(grid_loc); it != m_unsupported_points_grid.end()) {
|
||||||
|
std::list<UnsupportedCell>::iterator& list_it = it->second;
|
||||||
|
UnsupportedCell& cell = *list_it;
|
||||||
|
if ((cell.loc - added_leaf).cast<double>().squaredNorm() <= m_supporting_radius2) {
|
||||||
|
m_unsupported_points.erase(list_it);
|
||||||
|
m_unsupported_points_grid.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
97
src/libslic3r/Fill/Lightning/DistanceField.hpp
Normal file
97
src/libslic3r/Fill/Lightning/DistanceField.hpp
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
//Copyright (c) 2021 Ultimaker B.V.
|
||||||
|
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
#ifndef LIGHTNING_DISTANCE_FIELD_H
|
||||||
|
#define LIGHTNING_DISTANCE_FIELD_H
|
||||||
|
|
||||||
|
namespace Slic3r
|
||||||
|
{
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* 2D field that maintains locations which need to be supported for Lightning
|
||||||
|
* Infill.
|
||||||
|
*
|
||||||
|
* This field contains a set of "cells", spaced out in a grid. Each cell
|
||||||
|
* maintains how far it is removed from the edge, which is used to determine
|
||||||
|
* how it gets supported by Lightning Infill.
|
||||||
|
*/
|
||||||
|
class LightningDistanceField
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
* Construct a new field to calculate Lightning Infill with.
|
||||||
|
* \param radius The radius of influence that an infill line is expected to
|
||||||
|
* support in the layer above.
|
||||||
|
* \param current_outline The total infill area on this layer.
|
||||||
|
* \param current_overhang The overhang that needs to be supported on this
|
||||||
|
* layer.
|
||||||
|
*/
|
||||||
|
LightningDistanceField(const coord_t& radius, const Polygons& current_outline, const Polygons& current_overhang);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Gets the next unsupported location to be supported by a new branch.
|
||||||
|
* \param p Output variable for the next point to support.
|
||||||
|
* \return ``true`` if successful, or ``false`` if there are no more points
|
||||||
|
* to consider.
|
||||||
|
*/
|
||||||
|
bool tryGetNextPoint(Point* p) const {
|
||||||
|
if (m_unsupported_points.empty())
|
||||||
|
return false;
|
||||||
|
*p = m_unsupported_points.front().loc;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Update the distance field with a newly added branch.
|
||||||
|
*
|
||||||
|
* The branch is a line extending from \p to_node to \p added_leaf . This
|
||||||
|
* function updates the grid cells so that the distance field knows how far
|
||||||
|
* off it is from being supported by the current pattern. Grid points are
|
||||||
|
* updated with sampling points spaced out by the supporting radius along
|
||||||
|
* the line.
|
||||||
|
* \param to_node The node endpoint of the newly added branch.
|
||||||
|
* \param added_leaf The location of the leaf of the newly added branch,
|
||||||
|
* drawing a straight line to the node.
|
||||||
|
*/
|
||||||
|
void update(const Point& to_node, const Point& added_leaf);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/*!
|
||||||
|
* Spacing between grid points to consider supporting.
|
||||||
|
*/
|
||||||
|
coord_t m_cell_size;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* The radius of the area of the layer above supported by a point on a
|
||||||
|
* branch of a tree.
|
||||||
|
*/
|
||||||
|
coord_t m_supporting_radius;
|
||||||
|
double m_supporting_radius2;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Represents a small discrete area of infill that needs to be supported.
|
||||||
|
*/
|
||||||
|
struct UnsupportedCell
|
||||||
|
{
|
||||||
|
UnsupportedCell(Point grid_loc, coord_t dist_to_boundary) : loc(loc), dist_to_boundary(dist_to_boundary) {}
|
||||||
|
// The position of the center of this cell.
|
||||||
|
Point loc;
|
||||||
|
// How far this cell is removed from the ``current_outline`` polygon, the edge of the infill area.
|
||||||
|
coord_t dist_to_boundary;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Cells which still need to be supported at some point.
|
||||||
|
*/
|
||||||
|
std::list<UnsupportedCell> m_unsupported_points;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Links the unsupported points to a grid point, so that we can quickly look
|
||||||
|
* up the cell belonging to a certain position in the grid.
|
||||||
|
*/
|
||||||
|
std::unordered_map<Point, std::list<UnsupportedCell>::iterator, PointHash> m_unsupported_points_grid;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif //LIGHTNING_DISTANCE_FIELD_H
|
125
src/libslic3r/Fill/Lightning/Generator.cpp
Normal file
125
src/libslic3r/Fill/Lightning/Generator.cpp
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
//Copyright (c) 2021 Ultimaker B.V.
|
||||||
|
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
#include "Generator.hpp"
|
||||||
|
#include "TreeNode.hpp"
|
||||||
|
|
||||||
|
#include "../../ClipperUtils.hpp"
|
||||||
|
#include "../../Layer.hpp"
|
||||||
|
#include "../../Print.hpp"
|
||||||
|
#include "../../Surface.hpp"
|
||||||
|
|
||||||
|
/* Possible future tasks/optimizations,etc.:
|
||||||
|
* - Improve connecting heuristic to favor connecting to shorter trees
|
||||||
|
* - Change which node of a tree is the root when that would be better in reconnectRoots.
|
||||||
|
* - (For implementation in Infill classes & elsewhere): Outline offset, infill-overlap & perimeter gaps.
|
||||||
|
* - Allow for polylines, i.e. merge Tims PR about polyline fixes
|
||||||
|
* - Unit Tests?
|
||||||
|
* - Optimization: let the square grid store the closest point on boundary
|
||||||
|
* - Optimization: only compute the closest dist to / point on boundary for the outer cells and flood-fill the rest
|
||||||
|
* - Make a pass with Arachne over the output. Somehow.
|
||||||
|
* - Generate all to-be-supported points at once instead of sequentially: See branch interlocking_gen PolygonUtils::spreadDots (Or work with sparse grids.)
|
||||||
|
* - Lots of magic values ... to many to parameterize. But are they the best?
|
||||||
|
* - Move more complex computations from LightningGenerator constructor to elsewhere.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
LightningGenerator::LightningGenerator(const PrintObject &print_object)
|
||||||
|
{
|
||||||
|
const PrintConfig &print_config = print_object.print()->config();
|
||||||
|
const PrintObjectConfig &object_config = print_object.config();
|
||||||
|
const PrintRegionConfig ®ion_config = print_object.shared_regions()->all_regions.front()->config();
|
||||||
|
const std::vector<double> &nozzle_diameters = print_config.nozzle_diameter.values;
|
||||||
|
double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
|
||||||
|
const int 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;
|
||||||
|
|
||||||
|
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 = scaled<coord_t>(m_infill_extrusion_width * 0.001 / region_config.fill_density);
|
||||||
|
|
||||||
|
const double lightning_infill_overhang_angle = M_PI / 4; // 45 degrees
|
||||||
|
const double lightning_infill_prune_angle = M_PI / 4; // 45 degrees
|
||||||
|
const double lightning_infill_straightening_angle = M_PI / 4; // 45 degrees
|
||||||
|
m_wall_supporting_radius = layer_thickness * std::tan(lightning_infill_overhang_angle);
|
||||||
|
m_prune_length = layer_thickness * std::tan(lightning_infill_prune_angle);
|
||||||
|
m_straightening_max_distance = layer_thickness * std::tan(lightning_infill_straightening_angle);
|
||||||
|
|
||||||
|
generateInitialInternalOverhangs(print_object);
|
||||||
|
generateTrees(print_object);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightningGenerator::generateInitialInternalOverhangs(const PrintObject &print_object)
|
||||||
|
{
|
||||||
|
m_overhang_per_layer.resize(print_object.layers().size());
|
||||||
|
const float infill_wall_offset = - m_infill_extrusion_width;
|
||||||
|
|
||||||
|
Polygons infill_area_above;
|
||||||
|
//Iterate from top to bottom, to subtract the overhang areas above from the overhang areas on the layer below, to get only overhang in the top layer where it is overhanging.
|
||||||
|
for (int layer_nr = print_object.layers().size() - 1; layer_nr >= 0; layer_nr--) {
|
||||||
|
Polygons infill_area_here;
|
||||||
|
for (const LayerRegion* layerm : print_object.get_layer(layer_nr)->regions())
|
||||||
|
for (const Surface& surface : layerm->fill_surfaces.surfaces)
|
||||||
|
if (surface.surface_type == stInternal)
|
||||||
|
append(infill_area_here, offset(surface.expolygon, infill_wall_offset));
|
||||||
|
|
||||||
|
//Remove the part of the infill area that is already supported by the walls.
|
||||||
|
Polygons overhang = diff(offset(infill_area_here, -m_wall_supporting_radius), infill_area_above);
|
||||||
|
|
||||||
|
m_overhang_per_layer[layer_nr] = overhang;
|
||||||
|
infill_area_above = std::move(infill_area_here);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const LightningLayer& LightningGenerator::getTreesForLayer(const size_t& layer_id) const
|
||||||
|
{
|
||||||
|
assert(layer_id < m_lightning_layers.size());
|
||||||
|
return m_lightning_layers[layer_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightningGenerator::generateTrees(const PrintObject &print_object)
|
||||||
|
{
|
||||||
|
m_lightning_layers.resize(print_object.layers().size());
|
||||||
|
const coord_t infill_wall_offset = - m_infill_extrusion_width;
|
||||||
|
|
||||||
|
std::vector<Polygons> infill_outlines(print_object.layers().size(), Polygons());
|
||||||
|
|
||||||
|
// For-each layer from top to bottom:
|
||||||
|
for (int layer_id = print_object.layers().size() - 1; layer_id >= 0; layer_id--)
|
||||||
|
for (const LayerRegion *layerm : print_object.get_layer(layer_id)->regions())
|
||||||
|
for (const Surface &surface : layerm->fill_surfaces.surfaces)
|
||||||
|
if (surface.surface_type == stInternal)
|
||||||
|
append(infill_outlines[layer_id], offset(surface.expolygon, infill_wall_offset));
|
||||||
|
|
||||||
|
// For various operations its beneficial to quickly locate nearby features on the polygon:
|
||||||
|
const size_t top_layer_id = print_object.layers().size() - 1;
|
||||||
|
EdgeGrid::Grid outlines_locator(get_extents(infill_outlines[top_layer_id]).inflated(SCALED_EPSILON));
|
||||||
|
outlines_locator.create(infill_outlines[top_layer_id], locator_cell_size);
|
||||||
|
|
||||||
|
// For-each layer from top to bottom:
|
||||||
|
for (int layer_id = top_layer_id; layer_id >= 0; layer_id--)
|
||||||
|
{
|
||||||
|
LightningLayer& current_lightning_layer = m_lightning_layers[layer_id];
|
||||||
|
Polygons& current_outlines = infill_outlines[layer_id];
|
||||||
|
|
||||||
|
// register all trees propagated from the previous layer as to-be-reconnected
|
||||||
|
std::vector<LightningTreeNodeSPtr> to_be_reconnected_tree_roots = current_lightning_layer.tree_roots;
|
||||||
|
|
||||||
|
current_lightning_layer.generateNewTrees(m_overhang_per_layer[layer_id], current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
|
||||||
|
current_lightning_layer.reconnectRoots(to_be_reconnected_tree_roots, current_outlines, outlines_locator, m_supporting_radius, m_wall_supporting_radius);
|
||||||
|
|
||||||
|
// Initialize trees for next lower layer from the current one.
|
||||||
|
if (layer_id == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Polygons& below_outlines = infill_outlines[layer_id - 1];
|
||||||
|
outlines_locator.set_bbox(get_extents(below_outlines).inflated(SCALED_EPSILON));
|
||||||
|
outlines_locator.create(below_outlines, locator_cell_size);
|
||||||
|
|
||||||
|
std::vector<LightningTreeNodeSPtr>& 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);
|
||||||
|
}
|
||||||
|
}
|
126
src/libslic3r/Fill/Lightning/Generator.hpp
Normal file
126
src/libslic3r/Fill/Lightning/Generator.hpp
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
//Copyright (c) 2021 Ultimaker B.V.
|
||||||
|
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
#ifndef LIGHTNING_GENERATOR_H
|
||||||
|
#define LIGHTNING_GENERATOR_H
|
||||||
|
|
||||||
|
#include "Layer.hpp"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Slic3r
|
||||||
|
{
|
||||||
|
class PrintObject;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Generates the Lightning Infill pattern.
|
||||||
|
*
|
||||||
|
* The lightning infill pattern is designed to use a minimal amount of material
|
||||||
|
* to support the top skin of the print, while still printing with reasonably
|
||||||
|
* consistently flowing lines. It sacrifices strength completely in favour of
|
||||||
|
* top surface quality and reduced print time / material usage.
|
||||||
|
*
|
||||||
|
* Lightning Infill is so named because the patterns it creates resemble a
|
||||||
|
* forked path with one main path and many small lines on the side. These paths
|
||||||
|
* grow out from the sides of the model just below where the top surface needs
|
||||||
|
* to be supported from the inside, so that minimal material is needed.
|
||||||
|
*
|
||||||
|
* This pattern is based on a paper called "Ribbed Support Vaults for 3D
|
||||||
|
* Printing of Hollowed Objects" by Tricard, Claux and Lefebvre:
|
||||||
|
* https://www.researchgate.net/publication/333808588_Ribbed_Support_Vaults_for_3D_Printing_of_Hollowed_Objects
|
||||||
|
*/
|
||||||
|
class LightningGenerator // "Just like Nicola used to make!"
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
* Create a generator to fill a certain mesh with infill.
|
||||||
|
*
|
||||||
|
* This generator will pre-compute things in preparation of generating
|
||||||
|
* Lightning Infill for the infill areas in that mesh. The infill areas must
|
||||||
|
* already be calculated at this point.
|
||||||
|
* \param mesh The mesh to generate infill for.
|
||||||
|
*/
|
||||||
|
LightningGenerator(const PrintObject &print_object);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Get a tree of paths generated for a certain layer of the mesh.
|
||||||
|
*
|
||||||
|
* This tree represents the paths that must be traced to print the infill.
|
||||||
|
* \param layer_id The layer number to get the path tree for. This is within
|
||||||
|
* the range of layers of the mesh (not the global layer numbers).
|
||||||
|
* \return A tree structure representing paths to print to create the
|
||||||
|
* Lightning Infill pattern.
|
||||||
|
*/
|
||||||
|
const LightningLayer& getTreesForLayer(const size_t& layer_id) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/*!
|
||||||
|
* Calculate the overhangs above the infill areas that need to be supported
|
||||||
|
* by infill.
|
||||||
|
*
|
||||||
|
* Normally, overhangs are only generated for the outside of the model and
|
||||||
|
* only when support is generated. For this pattern, we also need to
|
||||||
|
* generate overhang areas for the inside of the model.
|
||||||
|
*/
|
||||||
|
void generateInitialInternalOverhangs(const PrintObject &print_object);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Calculate the tree structure of all layers.
|
||||||
|
*/
|
||||||
|
void generateTrees(const PrintObject &print_object);
|
||||||
|
|
||||||
|
float m_infill_extrusion_width;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* How far each piece of infill can support skin in the layer above.
|
||||||
|
*/
|
||||||
|
coord_t m_supporting_radius;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* How far a wall can support the wall above it. If a wall completely
|
||||||
|
* supports the wall above it, no infill needs to support that.
|
||||||
|
*
|
||||||
|
* This is similar to the overhang distance calculated for support. It is
|
||||||
|
* determined by the lightning_infill_overhang_angle setting.
|
||||||
|
*/
|
||||||
|
coord_t m_wall_supporting_radius;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* How far each piece of infill can support other infill in the layer above.
|
||||||
|
*
|
||||||
|
* This may be different than \ref supporting_radius, because the infill is
|
||||||
|
* printed with one end floating in mid-air. This endpoint will sag more, so
|
||||||
|
* an infill line may need to be supported more than a skin line.
|
||||||
|
*/
|
||||||
|
coord_t m_prune_length;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* How far a line may be shifted in order to straighten the line out.
|
||||||
|
*
|
||||||
|
* Straightening the line reduces material and time usage and reduces
|
||||||
|
* accelerations needed to print the pattern. However it makes the infill
|
||||||
|
* weak if lines are partially suspended next to the line on the previous
|
||||||
|
* layer.
|
||||||
|
*/
|
||||||
|
coord_t m_straightening_max_distance;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* For each layer, the overhang that needs to be supported by the pattern.
|
||||||
|
*
|
||||||
|
* This is generated by \ref generateInitialInternalOverhangs.
|
||||||
|
*/
|
||||||
|
std::vector<Polygons> m_overhang_per_layer;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* For each layer, the generated lightning paths.
|
||||||
|
*
|
||||||
|
* This is generated by \ref generateTrees.
|
||||||
|
*/
|
||||||
|
std::vector<LightningLayer> m_lightning_layers;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // LIGHTNING_GENERATOR_H
|
410
src/libslic3r/Fill/Lightning/Layer.cpp
Normal file
410
src/libslic3r/Fill/Lightning/Layer.cpp
Normal file
@ -0,0 +1,410 @@
|
|||||||
|
//Copyright (c) 2021 Ultimaker B.V.
|
||||||
|
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
#include "Layer.hpp" //The class we're implementing.
|
||||||
|
|
||||||
|
#include <iterator> // advance
|
||||||
|
|
||||||
|
#include "DistanceField.hpp"
|
||||||
|
#include "TreeNode.hpp"
|
||||||
|
|
||||||
|
#include "../../Geometry.hpp"
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
coord_t LightningLayer::getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location)
|
||||||
|
{
|
||||||
|
return coord_t((boundary_loc - unsupported_location).cast<double>().norm());
|
||||||
|
}
|
||||||
|
|
||||||
|
Point GroundingLocation::p() const
|
||||||
|
{
|
||||||
|
assert(tree_node || boundary_location);
|
||||||
|
return tree_node ? tree_node->getLocation() : *boundary_location;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightningLayer::fillLocator(SparseLightningTreeNodeGrid &tree_node_locator)
|
||||||
|
{
|
||||||
|
std::function<void(LightningTreeNodeSPtr)> add_node_to_locator_func = [&tree_node_locator](LightningTreeNodeSPtr node) {
|
||||||
|
tree_node_locator.insert(std::make_pair(Point(node->getLocation().x() / locator_cell_size, node->getLocation().y() / locator_cell_size), node));
|
||||||
|
};
|
||||||
|
for (auto& tree : tree_roots)
|
||||||
|
tree->visitNodes(add_node_to_locator_func);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightningLayer::generateNewTrees
|
||||||
|
(
|
||||||
|
const Polygons& current_overhang,
|
||||||
|
const Polygons& current_outlines,
|
||||||
|
const EdgeGrid::Grid& outlines_locator,
|
||||||
|
const coord_t supporting_radius,
|
||||||
|
const coord_t wall_supporting_radius
|
||||||
|
)
|
||||||
|
{
|
||||||
|
LightningDistanceField distance_field(supporting_radius, current_outlines, current_overhang);
|
||||||
|
|
||||||
|
SparseLightningTreeNodeGrid tree_node_locator;
|
||||||
|
fillLocator(tree_node_locator);
|
||||||
|
|
||||||
|
// Until no more points need to be added to support all:
|
||||||
|
// Determine next point from tree/outline areas via distance-field
|
||||||
|
Point unsupported_location;
|
||||||
|
while (distance_field.tryGetNextPoint(&unsupported_location)) {
|
||||||
|
GroundingLocation grounding_loc = getBestGroundingLocation(
|
||||||
|
unsupported_location, current_outlines, outlines_locator, supporting_radius, wall_supporting_radius, tree_node_locator);
|
||||||
|
|
||||||
|
LightningTreeNodeSPtr new_parent;
|
||||||
|
LightningTreeNodeSPtr new_child;
|
||||||
|
this->attach(unsupported_location, grounding_loc, new_child, new_parent);
|
||||||
|
tree_node_locator.insert(std::make_pair(Point(new_child->getLocation().x() / locator_cell_size, new_child->getLocation().y() / locator_cell_size), new_child));
|
||||||
|
if (new_parent)
|
||||||
|
tree_node_locator.insert(std::make_pair(Point(new_parent->getLocation().x() / locator_cell_size, new_parent->getLocation().y() / locator_cell_size), new_parent));
|
||||||
|
// update distance field
|
||||||
|
distance_field.update(grounding_loc.p(), unsupported_location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool polygonCollidesWithLineSegment(const Point from, const Point to, const EdgeGrid::Grid &loc_to_line)
|
||||||
|
{
|
||||||
|
struct Visitor {
|
||||||
|
explicit Visitor(const EdgeGrid::Grid &grid) : grid(grid) {}
|
||||||
|
|
||||||
|
bool operator()(coord_t iy, coord_t ix) {
|
||||||
|
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||||
|
auto cell_data_range = grid.cell_data_range(iy, ix);
|
||||||
|
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
|
||||||
|
// End points of the line segment and their vector.
|
||||||
|
auto segment = grid.segment(*it_contour_and_segment);
|
||||||
|
if (Geometry::segments_intersect(segment.first, segment.second, line.a, line.b)) {
|
||||||
|
this->intersect = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Continue traversing the grid along the edge.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EdgeGrid::Grid& grid;
|
||||||
|
Line line;
|
||||||
|
bool intersect = false;
|
||||||
|
} visitor(loc_to_line);
|
||||||
|
|
||||||
|
loc_to_line.visit_cells_intersecting_line(from, to, visitor);
|
||||||
|
return visitor.intersect;
|
||||||
|
}
|
||||||
|
|
||||||
|
GroundingLocation LightningLayer::getBestGroundingLocation
|
||||||
|
(
|
||||||
|
const Point& unsupported_location,
|
||||||
|
const Polygons& current_outlines,
|
||||||
|
const EdgeGrid::Grid& outline_locator,
|
||||||
|
const coord_t supporting_radius,
|
||||||
|
const coord_t wall_supporting_radius,
|
||||||
|
const SparseLightningTreeNodeGrid& tree_node_locator,
|
||||||
|
const LightningTreeNodeSPtr& exclude_tree
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Closest point on current_outlines to unsupported_location:
|
||||||
|
Point node_location;
|
||||||
|
{
|
||||||
|
double d2 = std::numeric_limits<double>::max();
|
||||||
|
for (const Polygon &contour : current_outlines)
|
||||||
|
if (contour.size() > 2) {
|
||||||
|
Point prev = contour.points.back();
|
||||||
|
for (const Point &p2 : contour.points) {
|
||||||
|
if (double d = Line::distance_to_squared(unsupported_location, prev, p2); d < d2) {
|
||||||
|
d2 = d;
|
||||||
|
node_location = Geometry::foot_pt({ prev, p2 }, unsupported_location).cast<coord_t>();
|
||||||
|
}
|
||||||
|
prev = p2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto within_dist = coord_t((node_location - unsupported_location).cast<double>().norm());
|
||||||
|
|
||||||
|
LightningTreeNodeSPtr sub_tree{ nullptr };
|
||||||
|
coord_t current_dist = getWeightedDistance(node_location, unsupported_location);
|
||||||
|
if (current_dist >= wall_supporting_radius) { // Only reconnect tree roots to other trees if they are not already close to the outlines.
|
||||||
|
const coord_t search_radius = std::min(current_dist, within_dist);
|
||||||
|
BoundingBox region(unsupported_location - Point(search_radius, search_radius), unsupported_location + Point(search_radius + locator_cell_size, search_radius + locator_cell_size));
|
||||||
|
region.min /= locator_cell_size;
|
||||||
|
region.max /= locator_cell_size;
|
||||||
|
Point grid_addr;
|
||||||
|
for (grid_addr.y() = region.min.y(); grid_addr.y() < region.max.y(); ++ grid_addr.y())
|
||||||
|
for (grid_addr.x() = region.min.x(); grid_addr.x() < region.max.x(); ++ grid_addr.x()) {
|
||||||
|
auto it_range = tree_node_locator.equal_range(grid_addr);
|
||||||
|
for (auto it = it_range.first; it != it_range.second; ++ it) {
|
||||||
|
auto candidate_sub_tree = it->second.lock();
|
||||||
|
if ((candidate_sub_tree && candidate_sub_tree != exclude_tree) &&
|
||||||
|
!(exclude_tree && exclude_tree->hasOffspring(candidate_sub_tree)) &&
|
||||||
|
!polygonCollidesWithLineSegment(unsupported_location, candidate_sub_tree->getLocation(), outline_locator)) {
|
||||||
|
const coord_t candidate_dist = candidate_sub_tree->getWeightedDistance(unsupported_location, supporting_radius);
|
||||||
|
if (candidate_dist < current_dist) {
|
||||||
|
current_dist = candidate_dist;
|
||||||
|
sub_tree = candidate_sub_tree;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ! sub_tree ?
|
||||||
|
GroundingLocation{ nullptr, node_location } :
|
||||||
|
GroundingLocation{ sub_tree, std::optional<Point>() };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LightningLayer::attach(
|
||||||
|
const Point& unsupported_location,
|
||||||
|
const GroundingLocation& grounding_loc,
|
||||||
|
LightningTreeNodeSPtr& new_child,
|
||||||
|
LightningTreeNodeSPtr& new_root)
|
||||||
|
{
|
||||||
|
// Update trees & distance fields.
|
||||||
|
if (grounding_loc.boundary_location) {
|
||||||
|
new_root = LightningTreeNode::create(grounding_loc.p(), std::make_optional(grounding_loc.p()));
|
||||||
|
new_child = new_root->addChild(unsupported_location);
|
||||||
|
tree_roots.push_back(new_root);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
new_child = grounding_loc.tree_node->addChild(unsupported_location);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightningLayer::reconnectRoots
|
||||||
|
(
|
||||||
|
std::vector<LightningTreeNodeSPtr>& to_be_reconnected_tree_roots,
|
||||||
|
const Polygons& current_outlines,
|
||||||
|
const EdgeGrid::Grid& outline_locator,
|
||||||
|
const coord_t supporting_radius,
|
||||||
|
const coord_t wall_supporting_radius
|
||||||
|
)
|
||||||
|
{
|
||||||
|
constexpr coord_t tree_connecting_ignore_offset = 100;
|
||||||
|
|
||||||
|
SparseLightningTreeNodeGrid tree_node_locator;
|
||||||
|
fillLocator(tree_node_locator);
|
||||||
|
|
||||||
|
const coord_t within_max_dist = outline_locator.resolution() * 2;
|
||||||
|
for (auto root_ptr : to_be_reconnected_tree_roots)
|
||||||
|
{
|
||||||
|
auto old_root_it = std::find(tree_roots.begin(), tree_roots.end(), root_ptr);
|
||||||
|
|
||||||
|
if (root_ptr->getLastGroundingLocation())
|
||||||
|
{
|
||||||
|
const Point& ground_loc = root_ptr->getLastGroundingLocation().value();
|
||||||
|
if (ground_loc != root_ptr->getLocation())
|
||||||
|
{
|
||||||
|
Point new_root_pt;
|
||||||
|
// Find an intersection of the line segment from root_ptr->getLocation() to ground_loc, at within_max_dist from ground_loc.
|
||||||
|
if (lineSegmentPolygonsIntersection(root_ptr->getLocation(), ground_loc, current_outlines, outline_locator, new_root_pt, within_max_dist)) {
|
||||||
|
auto new_root = LightningTreeNode::create(new_root_pt, new_root_pt);
|
||||||
|
root_ptr->addChild(new_root);
|
||||||
|
new_root->reroot();
|
||||||
|
|
||||||
|
tree_node_locator.insert(std::make_pair(Point(new_root->getLocation().x() / locator_cell_size, new_root->getLocation().y() / locator_cell_size), new_root));
|
||||||
|
|
||||||
|
*old_root_it = std::move(new_root); // replace old root with new root
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const coord_t tree_connecting_ignore_width = wall_supporting_radius - tree_connecting_ignore_offset; // Ideally, the boundary size in which the valence rule is ignored would be configurable.
|
||||||
|
GroundingLocation ground =
|
||||||
|
getBestGroundingLocation
|
||||||
|
(
|
||||||
|
root_ptr->getLocation(),
|
||||||
|
current_outlines,
|
||||||
|
outline_locator,
|
||||||
|
supporting_radius,
|
||||||
|
tree_connecting_ignore_width,
|
||||||
|
tree_node_locator,
|
||||||
|
root_ptr
|
||||||
|
);
|
||||||
|
if (ground.boundary_location)
|
||||||
|
{
|
||||||
|
if (ground.boundary_location.value() == root_ptr->getLocation())
|
||||||
|
continue; // Already on the boundary.
|
||||||
|
|
||||||
|
auto new_root = LightningTreeNode::create(ground.p(), ground.p());
|
||||||
|
auto attach_ptr = root_ptr->closestNode(new_root->getLocation());
|
||||||
|
attach_ptr->reroot();
|
||||||
|
|
||||||
|
new_root->addChild(attach_ptr);
|
||||||
|
tree_node_locator.insert(std::make_pair(new_root->getLocation(), new_root));
|
||||||
|
|
||||||
|
*old_root_it = std::move(new_root); // replace old root with new root
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(ground.tree_node);
|
||||||
|
assert(ground.tree_node != root_ptr);
|
||||||
|
assert(!root_ptr->hasOffspring(ground.tree_node));
|
||||||
|
assert(!ground.tree_node->hasOffspring(root_ptr));
|
||||||
|
|
||||||
|
auto attach_ptr = root_ptr->closestNode(ground.tree_node->getLocation());
|
||||||
|
attach_ptr->reroot();
|
||||||
|
|
||||||
|
ground.tree_node->addChild(attach_ptr);
|
||||||
|
|
||||||
|
// remove old root
|
||||||
|
*old_root_it = std::move(tree_roots.back());
|
||||||
|
tree_roots.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implementation assumes moving inside, but moving outside should just as well be possible.
|
||||||
|
*/
|
||||||
|
static unsigned int moveInside(const Polygons& polygons, Point& from, int distance, int64_t maxDist2)
|
||||||
|
{
|
||||||
|
Point ret = from;
|
||||||
|
int64_t bestDist2 = std::numeric_limits<int64_t>::max();
|
||||||
|
unsigned int bestPoly = static_cast<unsigned int>(-1);
|
||||||
|
bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
|
||||||
|
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
||||||
|
{
|
||||||
|
const Polygon &poly = polygons[poly_idx];
|
||||||
|
if (poly.size() < 2)
|
||||||
|
continue;
|
||||||
|
Point p0 = poly[poly.size() - 2];
|
||||||
|
Point p1 = poly.back();
|
||||||
|
// because we compare with vSize2 here (no division by zero), we also need to compare by vSize2 inside the loop
|
||||||
|
// to avoid integer rounding edge cases
|
||||||
|
bool projected_p_beyond_prev_segment = (p1 - p0).cast<int64_t>().dot((from - p0).cast<int64_t>()) >= (p1 - p0).cast<int64_t>().squaredNorm();
|
||||||
|
for (const Point& p2 : poly)
|
||||||
|
{
|
||||||
|
// X = A + Normal(B-A) * (((B-A) dot (P-A)) / VSize(B-A));
|
||||||
|
// = A + (B-A) * ((B-A) dot (P-A)) / VSize2(B-A);
|
||||||
|
// X = P projected on AB
|
||||||
|
const Point& a = p1;
|
||||||
|
const Point& b = p2;
|
||||||
|
const Point& p = from;
|
||||||
|
Point ab = b - a;
|
||||||
|
Point ap = p - a;
|
||||||
|
int64_t ab_length2 = ab.cast<int64_t>().squaredNorm();
|
||||||
|
if (ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other.
|
||||||
|
{
|
||||||
|
p1 = p2; //Skip only one of the points.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int64_t dot_prod = ab.cast<int64_t>().dot(ap.cast<int64_t>());
|
||||||
|
if (dot_prod <= 0) // x is projected to before ab
|
||||||
|
{
|
||||||
|
if (projected_p_beyond_prev_segment)
|
||||||
|
{ // case which looks like: > .
|
||||||
|
projected_p_beyond_prev_segment = false;
|
||||||
|
Point& x = p1;
|
||||||
|
|
||||||
|
int64_t dist2 = (x - p).cast<int64_t>().squaredNorm();
|
||||||
|
if (dist2 < bestDist2)
|
||||||
|
{
|
||||||
|
bestDist2 = dist2;
|
||||||
|
bestPoly = poly_idx;
|
||||||
|
if (distance == 0) {
|
||||||
|
ret = x;
|
||||||
|
} else {
|
||||||
|
// inward direction irrespective of sign of [distance]
|
||||||
|
Point inward_dir = perp((ab.cast<double>().normalized() * scaled<double>(10.0) + (p1 - p0).cast<double>().normalized() * scaled<double>(10.0)).eval()).cast<coord_t>();
|
||||||
|
// MM2INT(10.0) to retain precision for the eventual normalization
|
||||||
|
ret = x + (inward_dir.cast<double>().normalized() * distance).cast<coord_t>();
|
||||||
|
is_already_on_correct_side_of_boundary = inward_dir.cast<int64_t>().dot((p - x).cast<int64_t>()) * distance >= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
projected_p_beyond_prev_segment = false;
|
||||||
|
p0 = p1;
|
||||||
|
p1 = p2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (dot_prod >= ab_length2) // x is projected to beyond ab
|
||||||
|
{
|
||||||
|
projected_p_beyond_prev_segment = true;
|
||||||
|
p0 = p1;
|
||||||
|
p1 = p2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
|
||||||
|
projected_p_beyond_prev_segment = false;
|
||||||
|
Point x = a + ab * dot_prod / ab_length2;
|
||||||
|
|
||||||
|
int64_t dist2 = (p - x).cast<int64_t>().squaredNorm();
|
||||||
|
if (dist2 < bestDist2)
|
||||||
|
{
|
||||||
|
bestDist2 = dist2;
|
||||||
|
bestPoly = poly_idx;
|
||||||
|
if (distance == 0) { ret = x; }
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// inward or outward depending on the sign of [distance]
|
||||||
|
Vec2d inward_dir = perp((ab.cast<double>().normalized() * distance).eval());
|
||||||
|
ret = x + inward_dir.cast<coord_t>();
|
||||||
|
is_already_on_correct_side_of_boundary = inward_dir.dot((p - x).cast<double>()) >= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p0 = p1;
|
||||||
|
p1 = p2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_already_on_correct_side_of_boundary) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside
|
||||||
|
{
|
||||||
|
if (bestDist2 < distance * distance)
|
||||||
|
{
|
||||||
|
from = ret;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// from = from; // original point stays unaltered. It is already inside by enough distance
|
||||||
|
}
|
||||||
|
return bestPoly;
|
||||||
|
}
|
||||||
|
else if (bestDist2 < maxDist2)
|
||||||
|
{
|
||||||
|
from = ret;
|
||||||
|
return bestPoly;
|
||||||
|
}
|
||||||
|
return static_cast<unsigned int>(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns 'added someting'.
|
||||||
|
Polygons LightningLayer::convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const
|
||||||
|
{
|
||||||
|
Polygons result_lines;
|
||||||
|
if (tree_roots.empty())
|
||||||
|
return result_lines;
|
||||||
|
|
||||||
|
for (const auto& tree : tree_roots)
|
||||||
|
{
|
||||||
|
// If even the furthest location in the tree is inside the polygon, the entire tree must be inside of the polygon.
|
||||||
|
// (Don't take the root as that may be on the edge and cause rounding errors to register as 'outside'.)
|
||||||
|
constexpr coord_t epsilon = 5;
|
||||||
|
Point should_be_inside = tree->getLocation();
|
||||||
|
moveInside(limit_to_outline, should_be_inside, epsilon, epsilon * epsilon);
|
||||||
|
if (inside(limit_to_outline, should_be_inside))
|
||||||
|
tree->convertToPolylines(result_lines, line_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: allow for polylines!
|
||||||
|
Polygons 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;
|
||||||
|
}
|
87
src/libslic3r/Fill/Lightning/Layer.hpp
Normal file
87
src/libslic3r/Fill/Lightning/Layer.hpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
//Copyright (c) 2021 Ultimaker B.V.
|
||||||
|
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
#ifndef LIGHTNING_LAYER_H
|
||||||
|
#define LIGHTNING_LAYER_H
|
||||||
|
|
||||||
|
#include "../../EdgeGrid.hpp"
|
||||||
|
#include "../../Polygon.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <list>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace Slic3r
|
||||||
|
{
|
||||||
|
class LightningTreeNode;
|
||||||
|
|
||||||
|
using LightningTreeNodeSPtr = std::shared_ptr<LightningTreeNode>;
|
||||||
|
using SparseLightningTreeNodeGrid = std::unordered_multimap<Point, std::weak_ptr<LightningTreeNode>, PointHash>;
|
||||||
|
|
||||||
|
struct GroundingLocation
|
||||||
|
{
|
||||||
|
LightningTreeNodeSPtr tree_node; //!< not null if the gounding location is on a tree
|
||||||
|
std::optional<Point> boundary_location; //!< in case the gounding location is on the boundary
|
||||||
|
Point p() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* A layer of the lightning fill.
|
||||||
|
*
|
||||||
|
* Contains the trees to be printed and propagated to the next layer below.
|
||||||
|
*/
|
||||||
|
class LightningLayer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<LightningTreeNodeSPtr> tree_roots;
|
||||||
|
|
||||||
|
void generateNewTrees
|
||||||
|
(
|
||||||
|
const Polygons& current_overhang,
|
||||||
|
const Polygons& current_outlines,
|
||||||
|
const EdgeGrid::Grid& outline_locator,
|
||||||
|
const coord_t supporting_radius,
|
||||||
|
const coord_t wall_supporting_radius
|
||||||
|
);
|
||||||
|
|
||||||
|
/*! Determine & connect to connection point in tree/outline.
|
||||||
|
* \param min_dist_from_boundary_for_tree If the unsupported point is closer to the boundary than this then don't consider connecting it to a tree
|
||||||
|
*/
|
||||||
|
GroundingLocation getBestGroundingLocation
|
||||||
|
(
|
||||||
|
const Point& unsupported_location,
|
||||||
|
const Polygons& current_outlines,
|
||||||
|
const EdgeGrid::Grid& outline_locator,
|
||||||
|
const coord_t supporting_radius,
|
||||||
|
const coord_t wall_supporting_radius,
|
||||||
|
const SparseLightningTreeNodeGrid& tree_node_locator,
|
||||||
|
const LightningTreeNodeSPtr& exclude_tree = nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \param[out] new_child The new child node introduced
|
||||||
|
* \param[out] new_root The new root node if one had been made
|
||||||
|
* \return Whether a new root was added
|
||||||
|
*/
|
||||||
|
bool attach(const Point& unsupported_location, const GroundingLocation& ground, LightningTreeNodeSPtr& new_child, LightningTreeNodeSPtr& new_root);
|
||||||
|
|
||||||
|
void reconnectRoots
|
||||||
|
(
|
||||||
|
std::vector<LightningTreeNodeSPtr>& to_be_reconnected_tree_roots,
|
||||||
|
const Polygons& current_outlines,
|
||||||
|
const EdgeGrid::Grid& outline_locator,
|
||||||
|
const coord_t supporting_radius,
|
||||||
|
const coord_t wall_supporting_radius
|
||||||
|
);
|
||||||
|
|
||||||
|
Polygons convertToLines(const Polygons& limit_to_outline, const coord_t line_width) const;
|
||||||
|
|
||||||
|
coord_t getWeightedDistance(const Point& boundary_loc, const Point& unsupported_location);
|
||||||
|
|
||||||
|
void fillLocator(SparseLightningTreeNodeGrid& tree_node_locator);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // LIGHTNING_LAYER_H
|
408
src/libslic3r/Fill/Lightning/TreeNode.cpp
Normal file
408
src/libslic3r/Fill/Lightning/TreeNode.cpp
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
//Copyright (c) 2021 Ultimaker B.V.
|
||||||
|
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
#include "TreeNode.hpp"
|
||||||
|
|
||||||
|
#include "../../Geometry.hpp"
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
coord_t LightningTreeNode::getWeightedDistance(const Point& unsupported_location, const coord_t& supporting_radius) const
|
||||||
|
{
|
||||||
|
constexpr coord_t min_valence_for_boost = 0;
|
||||||
|
constexpr coord_t max_valence_for_boost = 4;
|
||||||
|
constexpr coord_t valence_boost_multiplier = 4;
|
||||||
|
|
||||||
|
const size_t valence = (!m_is_root) + m_children.size();
|
||||||
|
const coord_t valence_boost = (min_valence_for_boost < valence && valence < max_valence_for_boost) ? valence_boost_multiplier * supporting_radius : 0;
|
||||||
|
const auto dist_here = coord_t((getLocation() - unsupported_location).cast<double>().norm());
|
||||||
|
return dist_here - valence_boost;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LightningTreeNode::hasOffspring(const LightningTreeNodeSPtr& to_be_checked) const
|
||||||
|
{
|
||||||
|
if (to_be_checked == shared_from_this())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto& child_ptr : m_children)
|
||||||
|
if (child_ptr->hasOffspring(to_be_checked))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LightningTreeNodeSPtr LightningTreeNode::addChild(const Point& child_loc)
|
||||||
|
{
|
||||||
|
assert(m_p != child_loc);
|
||||||
|
LightningTreeNodeSPtr child = LightningTreeNode::create(child_loc);
|
||||||
|
return addChild(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
LightningTreeNodeSPtr LightningTreeNode::addChild(LightningTreeNodeSPtr& new_child)
|
||||||
|
{
|
||||||
|
assert(new_child != shared_from_this());
|
||||||
|
//assert(p != new_child->p); // NOTE: No problem for now. Issue to solve later. Maybe even afetr final. Low prio.
|
||||||
|
m_children.push_back(new_child);
|
||||||
|
new_child->m_parent = shared_from_this();
|
||||||
|
new_child->m_is_root = false;
|
||||||
|
return new_child;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightningTreeNode::propagateToNextLayer(
|
||||||
|
std::vector<LightningTreeNodeSPtr>& next_trees,
|
||||||
|
const Polygons& next_outlines,
|
||||||
|
const EdgeGrid::Grid& outline_locator,
|
||||||
|
const coord_t prune_distance,
|
||||||
|
const coord_t smooth_magnitude,
|
||||||
|
const coord_t max_remove_colinear_dist) const
|
||||||
|
{
|
||||||
|
auto tree_below = deepCopy();
|
||||||
|
tree_below->prune(prune_distance);
|
||||||
|
tree_below->straighten(smooth_magnitude, max_remove_colinear_dist);
|
||||||
|
if (tree_below->realign(next_outlines, outline_locator, next_trees))
|
||||||
|
next_trees.push_back(tree_below);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Depth-first, as currently implemented.
|
||||||
|
// Skips the root (because that has no root itself), but all initial nodes will have the root point anyway.
|
||||||
|
void LightningTreeNode::visitBranches(const std::function<void(const Point&, const Point&)>& visitor) const
|
||||||
|
{
|
||||||
|
for (const auto& node : m_children) {
|
||||||
|
assert(node->m_parent.lock() == shared_from_this());
|
||||||
|
visitor(m_p, node->m_p);
|
||||||
|
node->visitBranches(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Depth-first, as currently implemented.
|
||||||
|
void LightningTreeNode::visitNodes(const std::function<void(LightningTreeNodeSPtr)>& visitor)
|
||||||
|
{
|
||||||
|
visitor(shared_from_this());
|
||||||
|
for (const auto& node : m_children) {
|
||||||
|
assert(node->m_parent.lock() == shared_from_this());
|
||||||
|
node->visitNodes(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LightningTreeNode::LightningTreeNode(const Point& p, const std::optional<Point>& last_grounding_location /*= std::nullopt*/) :
|
||||||
|
m_is_root(true), m_p(p), m_last_grounding_location(last_grounding_location)
|
||||||
|
{}
|
||||||
|
|
||||||
|
LightningTreeNodeSPtr LightningTreeNode::deepCopy() const
|
||||||
|
{
|
||||||
|
LightningTreeNodeSPtr local_root = LightningTreeNode::create(m_p);
|
||||||
|
local_root->m_is_root = m_is_root;
|
||||||
|
if (m_is_root)
|
||||||
|
{
|
||||||
|
local_root->m_last_grounding_location = m_last_grounding_location.value_or(m_p);
|
||||||
|
}
|
||||||
|
local_root->m_children.reserve(m_children.size());
|
||||||
|
for (const auto& node : m_children)
|
||||||
|
{
|
||||||
|
LightningTreeNodeSPtr child = node->deepCopy();
|
||||||
|
child->m_parent = local_root;
|
||||||
|
local_root->m_children.push_back(child);
|
||||||
|
}
|
||||||
|
return local_root;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightningTreeNode::reroot(LightningTreeNodeSPtr new_parent /*= nullptr*/)
|
||||||
|
{
|
||||||
|
if (! m_is_root) {
|
||||||
|
auto old_parent = m_parent.lock();
|
||||||
|
old_parent->reroot(shared_from_this());
|
||||||
|
m_children.push_back(old_parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_parent) {
|
||||||
|
m_children.erase(std::remove(m_children.begin(), m_children.end(), new_parent), m_children.end());
|
||||||
|
m_is_root = false;
|
||||||
|
m_parent = new_parent;
|
||||||
|
} else {
|
||||||
|
m_is_root = true;
|
||||||
|
m_parent.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LightningTreeNodeSPtr LightningTreeNode::closestNode(const Point& loc)
|
||||||
|
{
|
||||||
|
LightningTreeNodeSPtr result = shared_from_this();
|
||||||
|
auto closest_dist2 = coord_t((m_p - loc).cast<double>().norm());
|
||||||
|
|
||||||
|
for (const auto& child : m_children) {
|
||||||
|
LightningTreeNodeSPtr candidate_node = child->closestNode(loc);
|
||||||
|
const auto child_dist2 = coord_t((candidate_node->m_p - loc).cast<double>().norm());
|
||||||
|
if (child_dist2 < closest_dist2) {
|
||||||
|
closest_dist2 = child_dist2;
|
||||||
|
result = candidate_node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inside(const Polygons &polygons, const Point p)
|
||||||
|
{
|
||||||
|
int poly_count_inside = 0;
|
||||||
|
for (const Polygon &poly : polygons) {
|
||||||
|
const int is_inside_this_poly = ClipperLib::PointInPolygon(p, poly.points);
|
||||||
|
if (is_inside_this_poly == -1)
|
||||||
|
return true;
|
||||||
|
poly_count_inside += is_inside_this_poly;
|
||||||
|
}
|
||||||
|
return (poly_count_inside % 2) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist)
|
||||||
|
{
|
||||||
|
struct Visitor {
|
||||||
|
bool operator()(coord_t iy, coord_t ix) {
|
||||||
|
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||||
|
auto cell_data_range = grid.cell_data_range(iy, ix);
|
||||||
|
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
|
||||||
|
// End points of the line segment and their vector.
|
||||||
|
auto segment = grid.segment(*it_contour_and_segment);
|
||||||
|
if (Vec2d ip; Geometry::segment_segment_intersection(segment.first.cast<double>(), segment.second.cast<double>(), this->line_a, this->line_b, ip))
|
||||||
|
if (double d = (this->intersection_pt - this->line_b).squaredNorm(); d < d2min) {
|
||||||
|
this->d2min = d;
|
||||||
|
this->intersection_pt = ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Continue traversing the grid along the edge.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EdgeGrid::Grid& grid;
|
||||||
|
Vec2d line_a;
|
||||||
|
Vec2d line_b;
|
||||||
|
Vec2d intersection_pt;
|
||||||
|
double d2min { std::numeric_limits<double>::max() };
|
||||||
|
} visitor { outline_locator, a.cast<double>(), b.cast<double>() };
|
||||||
|
|
||||||
|
outline_locator.visit_cells_intersecting_line(a, b, visitor);
|
||||||
|
return visitor.d2min < within_max_dist * within_max_dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LightningTreeNode::realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector<LightningTreeNodeSPtr>& rerooted_parts)
|
||||||
|
{
|
||||||
|
if (outlines.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (inside(outlines, m_p)) {
|
||||||
|
// Only keep children that have an unbroken connection to here, realign will put the rest in rerooted parts due to recursion:
|
||||||
|
Point coll;
|
||||||
|
bool reground_me = false;
|
||||||
|
m_children.erase(std::remove_if(m_children.begin(), m_children.end(), [&](const LightningTreeNodeSPtr &child) {
|
||||||
|
bool connect_branch = child->realign(outlines, outline_locator, rerooted_parts);
|
||||||
|
// Find an intersection of the line segment from p to child->p, at maximum outline_locator.resolution() * 2 distance from p.
|
||||||
|
if (connect_branch && lineSegmentPolygonsIntersection(child->m_p, m_p, outlines, outline_locator, coll, outline_locator.resolution() * 2)) {
|
||||||
|
child->m_last_grounding_location.reset();
|
||||||
|
child->m_parent.reset();
|
||||||
|
child->m_is_root = true;
|
||||||
|
rerooted_parts.push_back(child);
|
||||||
|
reground_me = true;
|
||||||
|
connect_branch = false;
|
||||||
|
}
|
||||||
|
return ! connect_branch;
|
||||||
|
}), m_children.end());
|
||||||
|
if (reground_me)
|
||||||
|
m_last_grounding_location.reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'Lift' any decendants out of this tree:
|
||||||
|
for (auto& child : m_children)
|
||||||
|
if (child->realign(outlines, outline_locator, rerooted_parts)) {
|
||||||
|
child->m_last_grounding_location = m_p;
|
||||||
|
child->m_parent.reset();
|
||||||
|
child->m_is_root = true;
|
||||||
|
rerooted_parts.push_back(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_children.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightningTreeNode::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);
|
||||||
|
}
|
||||||
|
|
||||||
|
LightningTreeNode::RectilinearJunction LightningTreeNode::straighten(
|
||||||
|
const coord_t magnitude,
|
||||||
|
const Point& junction_above,
|
||||||
|
const coord_t accumulated_dist,
|
||||||
|
const coord_t max_remove_colinear_dist2)
|
||||||
|
{
|
||||||
|
constexpr coord_t junction_magnitude_factor_numerator = 3;
|
||||||
|
constexpr coord_t junction_magnitude_factor_denominator = 4;
|
||||||
|
|
||||||
|
const coord_t junction_magnitude = magnitude * junction_magnitude_factor_numerator / junction_magnitude_factor_denominator;
|
||||||
|
if (m_children.size() == 1)
|
||||||
|
{
|
||||||
|
auto child_p = m_children.front();
|
||||||
|
auto child_dist = coord_t((m_p - child_p->m_p).cast<double>().norm());
|
||||||
|
RectilinearJunction junction_below = child_p->straighten(magnitude, junction_above, accumulated_dist + child_dist, max_remove_colinear_dist2);
|
||||||
|
coord_t total_dist_to_junction_below = junction_below.total_recti_dist;
|
||||||
|
Point a = junction_above;
|
||||||
|
Point b = junction_below.junction_loc;
|
||||||
|
if (a != b) // should always be true!
|
||||||
|
{
|
||||||
|
Point ab = b - a;
|
||||||
|
Point destination = a + ab * accumulated_dist / std::max(coord_t(1), total_dist_to_junction_below);
|
||||||
|
if ((destination - m_p).cast<double>().squaredNorm() <= magnitude * magnitude)
|
||||||
|
m_p = destination;
|
||||||
|
else
|
||||||
|
m_p += ((destination - m_p).cast<double>().normalized() * magnitude).cast<coord_t>();
|
||||||
|
}
|
||||||
|
{ // remove nodes on linear segments
|
||||||
|
constexpr coord_t close_enough = 10;
|
||||||
|
|
||||||
|
child_p = m_children.front(); //recursive call to straighten might have removed the child
|
||||||
|
const LightningTreeNodeSPtr& parent_node = m_parent.lock();
|
||||||
|
if (parent_node &&
|
||||||
|
(child_p->m_p - parent_node->m_p).cast<double>().squaredNorm() < max_remove_colinear_dist2 &&
|
||||||
|
Line::distance_to_squared(m_p, parent_node->m_p, child_p->m_p) < close_enough * close_enough) {
|
||||||
|
child_p->m_parent = m_parent;
|
||||||
|
for (auto& sibling : parent_node->m_children)
|
||||||
|
{ // find this node among siblings
|
||||||
|
if (sibling == shared_from_this())
|
||||||
|
{
|
||||||
|
sibling = child_p; // replace this node by child
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return junction_below;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
constexpr coord_t weight = 1000;
|
||||||
|
Point junction_moving_dir = ((junction_above - m_p).cast<double>().normalized() * weight).cast<coord_t>();
|
||||||
|
bool prevent_junction_moving = false;
|
||||||
|
for (auto& child_p : m_children)
|
||||||
|
{
|
||||||
|
const auto child_dist = coord_t((m_p - child_p->m_p).cast<double>().norm());
|
||||||
|
RectilinearJunction below = child_p->straighten(magnitude, m_p, child_dist, max_remove_colinear_dist2);
|
||||||
|
|
||||||
|
junction_moving_dir += ((below.junction_loc - m_p).cast<double>().normalized() * weight).cast<coord_t>();
|
||||||
|
if (below.total_recti_dist < magnitude) // TODO: make configurable?
|
||||||
|
{
|
||||||
|
prevent_junction_moving = true; // prevent flipflopping in branches due to straightening and junctoin moving clashing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (junction_moving_dir != Point(0, 0) && ! m_children.empty() && ! m_is_root && ! prevent_junction_moving)
|
||||||
|
{
|
||||||
|
auto junction_moving_dir_len = coord_t(junction_moving_dir.norm());
|
||||||
|
if (junction_moving_dir_len > junction_magnitude)
|
||||||
|
{
|
||||||
|
junction_moving_dir = junction_moving_dir * junction_magnitude / junction_moving_dir_len;
|
||||||
|
}
|
||||||
|
m_p += junction_moving_dir;
|
||||||
|
}
|
||||||
|
return RectilinearJunction{ accumulated_dist, m_p };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune the tree from the extremeties (leaf-nodes) until the pruning distance is reached.
|
||||||
|
coord_t LightningTreeNode::prune(const coord_t& pruning_distance)
|
||||||
|
{
|
||||||
|
if (pruning_distance <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
coord_t max_distance_pruned = 0;
|
||||||
|
for (auto child_it = m_children.begin(); child_it != m_children.end(); ) {
|
||||||
|
auto& child = *child_it;
|
||||||
|
coord_t dist_pruned_child = child->prune(pruning_distance);
|
||||||
|
if (dist_pruned_child >= pruning_distance)
|
||||||
|
{ // pruning is finished for child; dont modify further
|
||||||
|
max_distance_pruned = std::max(max_distance_pruned, dist_pruned_child);
|
||||||
|
++child_it;
|
||||||
|
} else {
|
||||||
|
const Point a = getLocation();
|
||||||
|
const Point b = child->getLocation();
|
||||||
|
const Point ba = a - b;
|
||||||
|
const auto ab_len = coord_t(ba.cast<double>().norm());
|
||||||
|
if (dist_pruned_child + ab_len <= pruning_distance) {
|
||||||
|
// we're still in the process of pruning
|
||||||
|
assert(child->m_children.empty() && "when pruning away a node all it's children must already have been pruned away");
|
||||||
|
max_distance_pruned = std::max(max_distance_pruned, dist_pruned_child + ab_len);
|
||||||
|
child_it = m_children.erase(child_it);
|
||||||
|
} else {
|
||||||
|
// pruning stops in between this node and the child
|
||||||
|
const Point n = b + (ba.cast<double>().normalized() * (pruning_distance - dist_pruned_child)).cast<coord_t>();
|
||||||
|
assert(std::abs((n - b).cast<double>().norm() + dist_pruned_child - pruning_distance) < 10 && "total pruned distance must be equal to the pruning_distance");
|
||||||
|
max_distance_pruned = std::max(max_distance_pruned, pruning_distance);
|
||||||
|
child->setLocation(n);
|
||||||
|
++child_it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return max_distance_pruned;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightningTreeNode::convertToPolylines(Polygons& output, const coord_t line_width) const
|
||||||
|
{
|
||||||
|
Polygons result;
|
||||||
|
output.emplace_back();
|
||||||
|
convertToPolylines(0, result);
|
||||||
|
removeJunctionOverlap(result, line_width);
|
||||||
|
append(output, std::move(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightningTreeNode::convertToPolylines(size_t long_line_idx, Polygons& output) const
|
||||||
|
{
|
||||||
|
if (m_children.empty()) {
|
||||||
|
output[long_line_idx].points.push_back(m_p);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t first_child_idx = rand() % m_children.size();
|
||||||
|
m_children[first_child_idx]->convertToPolylines(long_line_idx, output);
|
||||||
|
output[long_line_idx].points.push_back(m_p);
|
||||||
|
|
||||||
|
for (size_t idx_offset = 1; idx_offset < m_children.size(); idx_offset++) {
|
||||||
|
size_t child_idx = (first_child_idx + idx_offset) % m_children.size();
|
||||||
|
const LightningTreeNode& child = *m_children[child_idx];
|
||||||
|
output.emplace_back();
|
||||||
|
size_t child_line_idx = output.size() - 1;
|
||||||
|
child.convertToPolylines(child_line_idx, output);
|
||||||
|
output[child_line_idx].points.emplace_back(m_p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LightningTreeNode::removeJunctionOverlap(Polygons& result_lines, const coord_t line_width) const
|
||||||
|
{
|
||||||
|
const coord_t reduction = line_width / 2; // TODO make configurable?
|
||||||
|
for (auto poly_it = result_lines.begin(); poly_it != result_lines.end(); ) {
|
||||||
|
Polygon &polyline = *poly_it;
|
||||||
|
if (polyline.size() <= 1) {
|
||||||
|
polyline = std::move(result_lines.back());
|
||||||
|
result_lines.pop_back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
coord_t to_be_reduced = reduction;
|
||||||
|
Point a = polyline.back();
|
||||||
|
for (int point_idx = polyline.size() - 2; point_idx >= 0; point_idx--) {
|
||||||
|
const Point b = polyline[point_idx];
|
||||||
|
const Point ab = b - a;
|
||||||
|
const auto ab_len = coord_t(ab.cast<double>().norm());
|
||||||
|
if (ab_len >= to_be_reduced) {
|
||||||
|
polyline.points.back() = a + (ab.cast<double>() * (double(to_be_reduced) / ab_len)).cast<coord_t>();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
to_be_reduced -= ab_len;
|
||||||
|
polyline.points.pop_back();
|
||||||
|
}
|
||||||
|
a = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (polyline.size() <= 1) {
|
||||||
|
polyline = std::move(result_lines.back());
|
||||||
|
result_lines.pop_back();
|
||||||
|
} else
|
||||||
|
++ poly_it;
|
||||||
|
}
|
||||||
|
}
|
275
src/libslic3r/Fill/Lightning/TreeNode.hpp
Normal file
275
src/libslic3r/Fill/Lightning/TreeNode.hpp
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
//Copyright (c) 2021 Ultimaker B.V.
|
||||||
|
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
#ifndef LIGHTNING_TREE_NODE_H
|
||||||
|
#define LIGHTNING_TREE_NODE_H
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../../EdgeGrid.hpp"
|
||||||
|
#include "../../Polygon.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r
|
||||||
|
{
|
||||||
|
|
||||||
|
constexpr auto locator_cell_size = scaled<coord_t>(4.);
|
||||||
|
|
||||||
|
class LightningTreeNode;
|
||||||
|
|
||||||
|
using LightningTreeNodeSPtr = std::shared_ptr<LightningTreeNode>;
|
||||||
|
|
||||||
|
// NOTE: As written, this struct will only be valid for a single layer, will have to be updated for the next.
|
||||||
|
// NOTE: Reasons for implementing this with some separate closures:
|
||||||
|
// - keep clear deliniation during development
|
||||||
|
// - possibility of multiple distance field strategies
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* A single vertex of a Lightning Tree, the structure that determines the paths
|
||||||
|
* to be printed to form Lightning Infill.
|
||||||
|
*
|
||||||
|
* In essence these vertices are just a position linked to other positions in
|
||||||
|
* 2D. The nodes have a hierarchical structure of parents and children, forming
|
||||||
|
* a tree. The class also has some helper functions specific to Lightning Infill
|
||||||
|
* e.g. to straighten the paths around this node.
|
||||||
|
*/
|
||||||
|
class LightningTreeNode : public std::enable_shared_from_this<LightningTreeNode>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Workaround for private/protected constructors and 'make_shared': https://stackoverflow.com/a/27832765
|
||||||
|
template<typename ...Arg> LightningTreeNodeSPtr static create(Arg&&...arg)
|
||||||
|
{
|
||||||
|
struct EnableMakeShared : public LightningTreeNode
|
||||||
|
{
|
||||||
|
EnableMakeShared(Arg&&...arg) : LightningTreeNode(std::forward<Arg>(arg)...) {}
|
||||||
|
};
|
||||||
|
return std::make_shared<EnableMakeShared>(std::forward<Arg>(arg)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Get the position on this layer that this node represents, a vertex of the
|
||||||
|
* path to print.
|
||||||
|
* \return The position that this node represents.
|
||||||
|
*/
|
||||||
|
const Point& getLocation() const { return m_p; }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Change the position on this layer that the node represents.
|
||||||
|
* \param p The position that the node needs to represent.
|
||||||
|
*/
|
||||||
|
void setLocation(const Point& p) { m_p = p; }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Construct a new ``LightningTreeNode`` instance and add it as a child of
|
||||||
|
* this node.
|
||||||
|
* \param p The location of the new node.
|
||||||
|
* \return A shared pointer to the new node.
|
||||||
|
*/
|
||||||
|
LightningTreeNodeSPtr addChild(const Point& p);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Add an existing ``LightningTreeNode`` as a child of this node.
|
||||||
|
* \param new_child The node that must be added as a child.
|
||||||
|
* \return Always returns \p new_child.
|
||||||
|
*/
|
||||||
|
LightningTreeNodeSPtr addChild(LightningTreeNodeSPtr& new_child);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Propagate this node's sub-tree to the next layer.
|
||||||
|
*
|
||||||
|
* Creates a copy of this tree, realign it to the new layer boundaries
|
||||||
|
* \p next_outlines and reduce (i.e. prune and straighten) it. A copy of
|
||||||
|
* this node and all of its descendant nodes will be added to the
|
||||||
|
* \p next_trees vector.
|
||||||
|
* \param next_trees A collection of tree nodes to use for the next layer.
|
||||||
|
* \param next_outlines The shape of the layer below, to make sure that the
|
||||||
|
* tree stays within the bounds of the infill area.
|
||||||
|
* \param prune_distance The maximum distance that a leaf node may be moved
|
||||||
|
* such that it still supports the current node.
|
||||||
|
* \param smooth_magnitude The maximum distance that a line may be shifted
|
||||||
|
* to straighten the tree's paths, such that it still supports the current
|
||||||
|
* paths.
|
||||||
|
* \param max_remove_colinear_dist The maximum distance of a line-segment
|
||||||
|
* from which straightening may remove a colinear point.
|
||||||
|
*/
|
||||||
|
void propagateToNextLayer
|
||||||
|
(
|
||||||
|
std::vector<LightningTreeNodeSPtr>& next_trees,
|
||||||
|
const Polygons& next_outlines,
|
||||||
|
const EdgeGrid::Grid& outline_locator,
|
||||||
|
const coord_t prune_distance,
|
||||||
|
const coord_t smooth_magnitude,
|
||||||
|
const coord_t max_remove_colinear_dist
|
||||||
|
) const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Executes a given function for every line segment in this node's sub-tree.
|
||||||
|
*
|
||||||
|
* The function takes two `Point` arguments. These arguments will be filled
|
||||||
|
* in with the higher-order node (closer to the root) first, and the
|
||||||
|
* downtree node (closer to the leaves) as the second argument. The segment
|
||||||
|
* from this node's parent to this node itself is not included.
|
||||||
|
* The order in which the segments are visited is depth-first.
|
||||||
|
* \param visitor A function to execute for every branch in the node's sub-
|
||||||
|
* tree.
|
||||||
|
*/
|
||||||
|
void visitBranches(const std::function<void(const Point&, const Point&)>& visitor) const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Execute a given function for every node in this node's sub-tree.
|
||||||
|
*
|
||||||
|
* The visitor function takes a node as input. This node is not const, so
|
||||||
|
* this can be used to change the tree.
|
||||||
|
* Nodes are visited in depth-first order. This node itself is visited as
|
||||||
|
* well (pre-order).
|
||||||
|
* \param visitor A function to execute for every node in this node's sub-
|
||||||
|
* tree.
|
||||||
|
*/
|
||||||
|
void visitNodes(const std::function<void(LightningTreeNodeSPtr)>& visitor);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Get a weighted distance from an unsupported point to this node (given the current supporting radius).
|
||||||
|
*
|
||||||
|
* When attaching a unsupported location to a node, not all nodes have the same priority.
|
||||||
|
* (Eucludian) closer nodes are prioritised, but that's not the whole story.
|
||||||
|
* For instance, we give some nodes a 'valence boost' depending on the nr. of branches.
|
||||||
|
* \param unsupported_location The (unsuppported) location of which the weighted distance needs to be calculated.
|
||||||
|
* \param supporting_radius The maximum distance which can be bridged without (infill) supporting it.
|
||||||
|
* \return The weighted distance.
|
||||||
|
*/
|
||||||
|
coord_t getWeightedDistance(const Point& unsupported_location, const coord_t& supporting_radius) const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Returns whether this node is the root of a lightning tree. It is the root
|
||||||
|
* if it has no parents.
|
||||||
|
* \return ``true`` if this node is the root (no parents) or ``false`` if it
|
||||||
|
* is a child node of some other node.
|
||||||
|
*/
|
||||||
|
bool isRoot() const { return m_is_root; }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Reverse the parent-child relationship all the way to the root, from this node onward.
|
||||||
|
* This has the effect of 're-rooting' the tree at the current node if no immediate parent is given as argument.
|
||||||
|
* That is, the current node will become the root, it's (former) parent if any, will become one of it's children.
|
||||||
|
* This is then recursively bubbled up until it reaches the (former) root, which then will become a leaf.
|
||||||
|
* \param new_parent The (new) parent-node of the root, useful for recursing or immediately attaching the node to another tree.
|
||||||
|
*/
|
||||||
|
void reroot(LightningTreeNodeSPtr new_parent = nullptr);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Retrieves the closest node to the specified location.
|
||||||
|
* \param loc The specified location.
|
||||||
|
* \result The branch that starts at the position closest to the location within this tree.
|
||||||
|
*/
|
||||||
|
LightningTreeNodeSPtr closestNode(const Point& loc);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Returns whether the given tree node is a descendant of this node.
|
||||||
|
*
|
||||||
|
* If this node itself is given, it is also considered to be a descendant.
|
||||||
|
* \param to_be_checked A node to find out whether it is a descendant of
|
||||||
|
* this node.
|
||||||
|
* \return ``true`` if the given node is a descendant or this node itself,
|
||||||
|
* or ``false`` if it is not in the sub-tree.
|
||||||
|
*/
|
||||||
|
bool hasOffspring(const LightningTreeNodeSPtr& to_be_checked) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
LightningTreeNode() = delete; // Don't allow empty contruction
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Construct a new node, either for insertion in a tree or as root.
|
||||||
|
* \param p The physical location in the 2D layer that this node represents.
|
||||||
|
* Connecting other nodes to this node indicates that a line segment should
|
||||||
|
* be drawn between those two physical positions.
|
||||||
|
*/
|
||||||
|
LightningTreeNode(const Point& p, const std::optional<Point>& last_grounding_location = std::nullopt);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Copy this node and its entire sub-tree.
|
||||||
|
* \return The equivalent of this node in the copy (the root of the new sub-
|
||||||
|
* tree).
|
||||||
|
*/
|
||||||
|
LightningTreeNodeSPtr deepCopy() const;
|
||||||
|
|
||||||
|
/*! Reconnect trees from the layer above to the new outlines of the lower layer.
|
||||||
|
* \return Wether or not the root is kept (false is no, true is yes).
|
||||||
|
*/
|
||||||
|
bool realign(const Polygons& outlines, const EdgeGrid::Grid& outline_locator, std::vector<LightningTreeNodeSPtr>& rerooted_parts);
|
||||||
|
|
||||||
|
struct RectilinearJunction
|
||||||
|
{
|
||||||
|
coord_t total_recti_dist; //!< rectilinear distance along the tree from the last junction above to the junction below
|
||||||
|
Point junction_loc; //!< junction location below
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Smoothen the tree to make it a bit more printable, while still supporting
|
||||||
|
* the trees above.
|
||||||
|
* \param magnitude The maximum allowed distance to move the node.
|
||||||
|
* \param max_remove_colinear_dist Maximum distance of the (compound) line-segment from which a co-linear point may be removed.
|
||||||
|
*/
|
||||||
|
void straighten(const coord_t magnitude, const coord_t max_remove_colinear_dist);
|
||||||
|
|
||||||
|
/*! Recursive part of \ref straighten(.)
|
||||||
|
* \param junction_above The last seen junction with multiple children above
|
||||||
|
* \param accumulated_dist The distance along the tree from the last seen junction to this node
|
||||||
|
* \param max_remove_colinear_dist2 Maximum distance _squared_ of the (compound) line-segment from which a co-linear point may be removed.
|
||||||
|
* \return the total distance along the tree from the last junction above to the first next junction below and the location of the next junction below
|
||||||
|
*/
|
||||||
|
RectilinearJunction straighten(const coord_t magnitude, const Point& junction_above, const coord_t accumulated_dist, const coord_t max_remove_colinear_dist2);
|
||||||
|
|
||||||
|
/*! Prune the tree from the extremeties (leaf-nodes) until the pruning distance is reached.
|
||||||
|
* \return The distance that has been pruned. If less than \p distance, then the whole tree was puned away.
|
||||||
|
*/
|
||||||
|
coord_t prune(const coord_t& distance);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
* Convert the tree into polylines
|
||||||
|
*
|
||||||
|
* At each junction one line is chosen at random to continue
|
||||||
|
*
|
||||||
|
* The lines start at a leaf and end in a junction
|
||||||
|
*
|
||||||
|
* \param output all branches in this tree connected into polylines
|
||||||
|
*/
|
||||||
|
void convertToPolylines(Polygons& output, const coord_t line_width) const;
|
||||||
|
|
||||||
|
/*! If this was ever a direct child of the root, it'll have a previous grounding location.
|
||||||
|
*
|
||||||
|
* This needs to be known when roots are reconnected, so that the last (higher) layer is supported by the next one.
|
||||||
|
*/
|
||||||
|
const std::optional<Point>& getLastGroundingLocation() const { return m_last_grounding_location; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/*!
|
||||||
|
* Convert the tree into polylines
|
||||||
|
*
|
||||||
|
* At each junction one line is chosen at random to continue
|
||||||
|
*
|
||||||
|
* The lines start at a leaf and end in a junction
|
||||||
|
*
|
||||||
|
* \param long_line a reference to a polyline in \p output which to continue building on in the recursion
|
||||||
|
* \param output all branches in this tree connected into polylines
|
||||||
|
*/
|
||||||
|
void convertToPolylines(size_t long_line_idx, Polygons& output) const;
|
||||||
|
|
||||||
|
void removeJunctionOverlap(Polygons& polylines, const coord_t line_width) const;
|
||||||
|
|
||||||
|
bool m_is_root;
|
||||||
|
Point m_p;
|
||||||
|
std::weak_ptr<LightningTreeNode> m_parent;
|
||||||
|
std::vector<LightningTreeNodeSPtr> m_children;
|
||||||
|
|
||||||
|
std::optional<Point> m_last_grounding_location; //<! The last known grounding location, see 'getLastGroundingLocation()'.
|
||||||
|
};
|
||||||
|
|
||||||
|
bool inside(const Polygons &polygons, const Point p);
|
||||||
|
bool lineSegmentPolygonsIntersection(const Point& a, const Point& b, const Polygons& current_outlines, const EdgeGrid::Grid& outline_locator, Point& result, const coord_t within_max_dist);
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // LIGHTNING_TREE_NODE_H
|
@ -255,7 +255,7 @@ namespace int128 {
|
|||||||
// To be used by std::unordered_map, std::unordered_multimap and friends.
|
// To be used by std::unordered_map, std::unordered_multimap and friends.
|
||||||
struct PointHash {
|
struct PointHash {
|
||||||
size_t operator()(const Vec2crd &pt) const {
|
size_t operator()(const Vec2crd &pt) const {
|
||||||
return std::hash<coord_t>()(pt.x()) ^ std::hash<coord_t>()(pt.y());
|
return coord_t((89 * 31 + int64_t(pt.x())) * 31 + pt.y());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user