diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index ec9b14a7a..17d918aeb 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -692,6 +692,40 @@ inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set( detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits::infinity(), hit_idx_out, hit_point_out); } +// Decides if exists some triangle in defined radius on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree. +// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. +// Returns true if exists some triangle in defined radius, false otherwise. +template +inline bool is_any_triangle_in_radius( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. + const TreeType &tree, + // Point to which the closest point on the indexed triangle set is searched for. + const VectorType &point, + // Maximum distance in which triangle is search for + typename VectorType::Scalar &max_distance) +{ + using Scalar = typename VectorType::Scalar; + auto distancer = detail::IndexedTriangleSetDistancer + { vertices, faces, tree, point }; + + size_t hit_idx; + VectorType hit_point = VectorType::Ones() * (std::nan("")); + + if(tree.empty()) + { + return false; + } + + detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), max_distance, hit_idx, hit_point); + + return hit_point.allFinite(); +} + } // namespace AABBTreeIndirect } // namespace Slic3r diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 3d241dd37..09f75c747 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -46,6 +46,8 @@ add_library(libslic3r STATIC Fill/Fill.hpp Fill/Fill3DHoneycomb.cpp Fill/Fill3DHoneycomb.hpp + Fill/FillAdaptive.cpp + Fill/FillAdaptive.hpp Fill/FillBase.cpp Fill/FillBase.hpp Fill/FillConcentric.cpp diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 3c16527f0..9d468a6aa 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -318,7 +318,7 @@ void export_group_fills_to_svg(const char *path, const std::vector #endif // friend to Layer -void Layer::make_fills() +void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree) { for (LayerRegion *layerm : m_regions) layerm->fills.clear(); @@ -345,6 +345,7 @@ void Layer::make_fills() f->layer_id = this->id(); f->z = this->print_z; f->angle = surface_fill.params.angle; + f->adapt_fill_octree = adaptive_fill_octree; // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge; diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp new file mode 100644 index 000000000..bf9cd7f9d --- /dev/null +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -0,0 +1,270 @@ +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../Surface.hpp" +#include "../Geometry.hpp" +#include "../AABBTreeIndirect.hpp" +#include "../ShortestPath.hpp" + +#include "FillAdaptive.hpp" + +namespace Slic3r { + +void FillAdaptive::_fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines &polylines_out) +{ + // Store grouped lines by its direction (multiple of 120°) + std::vector infill_lines_dir(3); + this->generate_infill_lines(this->adapt_fill_octree->root_cube.get(), + this->z, this->adapt_fill_octree->origin,infill_lines_dir, + this->adapt_fill_octree->cubes_properties, + this->adapt_fill_octree->cubes_properties.size() - 1); + + Polylines all_polylines; + all_polylines.reserve(infill_lines_dir[0].size() * 3); + for (Lines &infill_lines : infill_lines_dir) + { + for (const Line &line : infill_lines) + { + all_polylines.emplace_back(line.a, line.b); + } + } + + if (params.dont_connect) + { + // Crop all polylines + polylines_out = intersection_pl(all_polylines, to_polygons(expolygon)); + } + else + { + // Crop all polylines + all_polylines = intersection_pl(all_polylines, to_polygons(expolygon)); + + Polylines boundary_polylines; + Polylines non_boundary_polylines; + for (const Polyline &polyline : all_polylines) + { + // connect_infill required all polylines to touch the boundary. + if(polyline.lines().size() == 1 && expolygon.has_boundary_point(polyline.lines().front().a) && expolygon.has_boundary_point(polyline.lines().front().b)) + { + boundary_polylines.push_back(polyline); + } + else + { + non_boundary_polylines.push_back(polyline); + } + } + + if(!boundary_polylines.empty()) + { + boundary_polylines = chain_polylines(boundary_polylines); + FillAdaptive::connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params); + } + + polylines_out.insert(polylines_out.end(), non_boundary_polylines.begin(), non_boundary_polylines.end()); + } + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRuna = 0; + BoundingBox bbox_svg = this->bounding_box; + { + ::Slic3r::SVG svg(debug_out_path("FillAdaptive-%d.svg", iRuna), bbox_svg); + for (const Polyline &polyline : polylines_out) + { + for (const Line &line : polyline.lines()) + { + Point from = line.a; + Point to = line.b; + Point diff = to - from; + + float shrink_length = scale_(0.4); + float line_slope = (float)diff.y() / diff.x(); + float shrink_x = shrink_length / (float)std::sqrt(1.0 + (line_slope * line_slope)); + float shrink_y = line_slope * shrink_x; + + to.x() -= shrink_x; + to.y() -= shrink_y; + from.x() += shrink_x; + from.y() += shrink_y; + + svg.draw(Line(from, to)); + } + } + } + + iRuna++; + } +#endif /* SLIC3R_DEBUG */ +} + +void FillAdaptive::generate_infill_lines( + FillAdaptive_Internal::Cube *cube, + double z_position, + const Vec3d &origin, + std::vector &dir_lines_out, + const std::vector &cubes_properties, + int depth) +{ + using namespace FillAdaptive_Internal; + + if(cube == nullptr) + { + return; + } + + double z_diff = std::abs(z_position - cube->center.z()); + + if (z_diff > cubes_properties[depth].height / 2) + { + return; + } + + if (z_diff < cubes_properties[depth].line_z_distance) + { + Point from( + scale_((cubes_properties[depth].diagonal_length / 2) * (cubes_properties[depth].line_z_distance - z_diff) / cubes_properties[depth].line_z_distance), + scale_(cubes_properties[depth].line_xy_distance - ((z_position - (cube->center.z() - cubes_properties[depth].line_z_distance)) / sqrt(2)))); + Point to(-from.x(), from.y()); + // Relative to cube center + + float rotation_angle = (2.0 * M_PI) / 3.0; + for (Lines &lines : dir_lines_out) + { + Vec3d offset = cube->center - origin; + Point from_abs(from), to_abs(to); + + from_abs.x() += scale_(offset.x()); + from_abs.y() += scale_(offset.y()); + to_abs.x() += scale_(offset.x()); + to_abs.y() += scale_(offset.y()); + +// lines.emplace_back(from_abs, to_abs); + this->connect_lines(lines, Line(from_abs, to_abs)); + + from.rotate(rotation_angle); + to.rotate(rotation_angle); + } + } + + for(const std::unique_ptr &child : cube->children) + { + if(child != nullptr) + { + generate_infill_lines(child.get(), z_position, origin, dir_lines_out, cubes_properties, depth - 1); + } + } +} + +void FillAdaptive::connect_lines(Lines &lines, Line new_line) +{ + int eps = scale_(0.10); + for (size_t i = 0; i < lines.size(); ++i) + { + if (std::abs(new_line.a.x() - lines[i].b.x()) < eps && std::abs(new_line.a.y() - lines[i].b.y()) < eps) + { + new_line.a = lines[i].a; + lines.erase(lines.begin() + i); + --i; + continue; + } + + if (std::abs(new_line.b.x() - lines[i].a.x()) < eps && std::abs(new_line.b.y() - lines[i].a.y()) < eps) + { + new_line.b = lines[i].b; + lines.erase(lines.begin() + i); + --i; + continue; + } + } + + lines.emplace_back(new_line.a, new_line.b); +} + +std::unique_ptr FillAdaptive::build_octree( + TriangleMesh &triangle_mesh, + coordf_t line_spacing, + const Vec3d &cube_center) +{ + using namespace FillAdaptive_Internal; + + if(line_spacing <= 0 || std::isnan(line_spacing)) + { + return nullptr; + } + + Vec3d bb_size = triangle_mesh.bounding_box().size(); + // The furthest point from the center of the bottom of the mesh bounding box. + double furthest_point = std::sqrt(((bb_size.x() * bb_size.x()) / 4.0) + + ((bb_size.y() * bb_size.y()) / 4.0) + + (bb_size.z() * bb_size.z())); + double max_cube_edge_length = furthest_point * 2; + + std::vector cubes_properties; + for (double edge_length = (line_spacing * 2); edge_length < (max_cube_edge_length * 2); edge_length *= 2) + { + CubeProperties props{}; + props.edge_length = edge_length; + props.height = edge_length * sqrt(3); + props.diagonal_length = edge_length * sqrt(2); + props.line_z_distance = edge_length / sqrt(3); + props.line_xy_distance = edge_length / sqrt(6); + cubes_properties.push_back(props); + } + + if (triangle_mesh.its.vertices.empty()) + { + triangle_mesh.require_shared_vertices(); + } + + Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); + Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); + + AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + triangle_mesh.its.vertices, triangle_mesh.its.indices); + auto octree = std::make_unique(std::make_unique(cube_center), cube_center, cubes_properties); + + FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, rotation_matrix, aabbTree, triangle_mesh, cubes_properties.size() - 1); + + return octree; +} + +void FillAdaptive::expand_cube( + FillAdaptive_Internal::Cube *cube, + const std::vector &cubes_properties, + const Transform3d &rotation_matrix, + const AABBTreeIndirect::Tree3f &distance_tree, + const TriangleMesh &triangle_mesh, int depth) +{ + using namespace FillAdaptive_Internal; + + if (cube == nullptr || depth == 0) + { + return; + } + + std::vector child_centers = { + Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d(-1, -1, 1), + Vec3d( 1, 1, 1), Vec3d(-1, 1, 1), Vec3d( 1, -1, 1), Vec3d( 1, 1, -1) + }; + + double cube_radius_squared = (cubes_properties[depth].height * cubes_properties[depth].height) / 16; + + for (size_t i = 0; i < 8; ++i) + { + const Vec3d &child_center = child_centers[i]; + Vec3d child_center_transformed = cube->center + rotation_matrix * (child_center * (cubes_properties[depth].edge_length / 4)); + + if(AABBTreeIndirect::is_any_triangle_in_radius(triangle_mesh.its.vertices, triangle_mesh.its.indices, + distance_tree, child_center_transformed, cube_radius_squared)) + { + cube->children[i] = std::make_unique(child_center_transformed); + FillAdaptive::expand_cube(cube->children[i].get(), cubes_properties, rotation_matrix, distance_tree, triangle_mesh, depth - 1); + } + } +} + +} // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp new file mode 100644 index 000000000..14694b766 --- /dev/null +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -0,0 +1,87 @@ +#ifndef slic3r_FillAdaptive_hpp_ +#define slic3r_FillAdaptive_hpp_ + +#include "../AABBTreeIndirect.hpp" + +#include "FillBase.hpp" + +namespace Slic3r { + +namespace FillAdaptive_Internal +{ + struct CubeProperties + { + double edge_length; // Lenght of edge of a cube + double height; // Height of rotated cube (standing on the corner) + double diagonal_length; // Length of diagonal of a cube a face + double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created + double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created + }; + + struct Cube + { + Vec3d center; + std::unique_ptr children[8] = {}; + Cube(const Vec3d ¢er) : center(center) {} + }; + + struct Octree + { + std::unique_ptr root_cube; + Vec3d origin; + std::vector cubes_properties; + + Octree(std::unique_ptr rootCube, const Vec3d &origin, const std::vector &cubes_properties) + : root_cube(std::move(rootCube)), origin(origin), cubes_properties(cubes_properties) {} + }; +}; // namespace FillAdaptive_Internal + +// +// Some of the algorithms used by class FillAdaptive were inspired by +// Cura Engine's class SubDivCube +// https://github.com/Ultimaker/CuraEngine/blob/master/src/infill/SubDivCube.h +// +class FillAdaptive : public Fill +{ +public: + virtual ~FillAdaptive() {} + +protected: + virtual Fill* clone() const { return new FillAdaptive(*this); }; + virtual void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines &polylines_out); + + virtual bool no_sort() const { return true; } + + void generate_infill_lines( + FillAdaptive_Internal::Cube *cube, + double z_position, + const Vec3d & origin, + std::vector & dir_lines_out, + const std::vector &cubes_properties, + int depth); + + static void connect_lines(Lines &lines, Line new_line); + +public: + static std::unique_ptr build_octree( + TriangleMesh &triangle_mesh, + coordf_t line_spacing, + const Vec3d & cube_center); + + static void expand_cube( + FillAdaptive_Internal::Cube *cube, + const std::vector &cubes_properties, + const Transform3d & rotation_matrix, + const AABBTreeIndirect::Tree3f &distance_tree, + const TriangleMesh & triangle_mesh, + int depth); +}; + +} // namespace Slic3r + +#endif // slic3r_FillAdaptive_hpp_ diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index c760218c0..c1f38dad5 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -16,6 +16,7 @@ #include "FillRectilinear.hpp" #include "FillRectilinear2.hpp" #include "FillRectilinear3.hpp" +#include "FillAdaptive.hpp" namespace Slic3r { @@ -37,6 +38,7 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipArchimedeanChords: return new FillArchimedeanChords(); case ipHilbertCurve: return new FillHilbertCurve(); case ipOctagramSpiral: return new FillOctagramSpiral(); + case ipAdaptiveCubic: return new FillAdaptive(); default: throw std::invalid_argument("unknown type"); } } diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 2e9b64735..9f70b69e0 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -19,6 +19,10 @@ class ExPolygon; class Surface; enum InfillPattern : int; +namespace FillAdaptive_Internal { + struct Octree; +}; + class InfillFailedException : public std::runtime_error { public: InfillFailedException() : std::runtime_error("Infill failed") {} @@ -69,6 +73,8 @@ public: // In scaled coordinates. Bounding box of the 2D projection of the object. BoundingBox bounding_box; + FillAdaptive_Internal::Octree* adapt_fill_octree = nullptr; + public: virtual ~Fill() {} diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index c104d46da..014d2623a 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -13,6 +13,10 @@ class Layer; class PrintRegion; class PrintObject; +namespace FillAdaptive_Internal { + struct Octree; +}; + class LayerRegion { public: @@ -134,7 +138,8 @@ public: return false; } void make_perimeters(); - void make_fills(); + void make_fills() { this->make_fills(nullptr); }; + void make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree); void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index d436f90bf..effb6bde9 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -30,6 +30,10 @@ enum class SlicingMode : uint32_t; class Layer; class SupportLayer; +namespace FillAdaptive_Internal { + struct Octree; +}; + // Print step IDs for keeping track of the print state. enum PrintStep { psSkirt, @@ -235,6 +239,7 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); + std::unique_ptr prepare_adaptive_infill_data(); // XYZ in scaled coordinates Vec3crd m_size; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 239969a1d..718fae365 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -881,6 +881,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("hilbertcurve"); def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); + def->enum_values.push_back("adaptivecubic"); def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Grid")); def->enum_labels.push_back(L("Triangles")); @@ -894,6 +895,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Hilbert Curve")); def->enum_labels.push_back(L("Archimedean Chords")); def->enum_labels.push_back(L("Octagram Spiral")); + def->enum_labels.push_back(L("Adaptive Cubic")); def->set_default_value(new ConfigOptionEnum(ipStars)); def = this->add("first_layer_acceleration", coFloat); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index b133a2e4e..3726444fa 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -39,7 +39,7 @@ enum AuthorizationType { enum InfillPattern : int { ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, - ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount, + ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipCount, }; enum class IroningType { @@ -139,6 +139,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::g keys_map["hilbertcurve"] = ipHilbertCurve; keys_map["archimedeanchords"] = ipArchimedeanChords; keys_map["octagramspiral"] = ipOctagramSpiral; + keys_map["adaptivecubic"] = ipAdaptiveCubic; } return keys_map; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index aecf90771..167be8a36 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -9,6 +9,8 @@ #include "Surface.hpp" #include "Slicing.hpp" #include "Utils.hpp" +#include "AABBTreeIndirect.hpp" +#include "Fill/FillAdaptive.hpp" #include #include @@ -369,13 +371,15 @@ void PrintObject::infill() this->prepare_infill(); if (this->set_started(posInfill)) { + std::unique_ptr octree = this->prepare_adaptive_infill_data(); + BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this](const tbb::blocked_range& range) { + [this, &octree](const tbb::blocked_range& 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(); + m_layers[layer_idx]->make_fills(octree.get()); } } ); @@ -428,6 +432,48 @@ void PrintObject::generate_support_material() } } +std::unique_ptr PrintObject::prepare_adaptive_infill_data() +{ + float fill_density = 0; + float infill_extrusion_width = 0; + + // Compute the average of above parameters over all layers + for (size_t layer_idx = 0; layer_idx < this->m_layers.size(); ++layer_idx) + { + for (size_t region_id = 0; region_id < this->m_layers[layer_idx]->m_regions.size(); ++region_id) + { + LayerRegion *layerm = this->m_layers[layer_idx]->m_regions[region_id]; + + // Check if region_id is used for this layer + if(!layerm->fill_surfaces.surfaces.empty()) { + const PrintRegionConfig ®ion_config = layerm->region()->config(); + + fill_density += region_config.fill_density; + infill_extrusion_width += region_config.infill_extrusion_width; + } + } + } + + fill_density /= this->m_layers.size(); + infill_extrusion_width /= this->m_layers.size(); + + if(fill_density <= 0 || infill_extrusion_width <= 0) + { + return std::unique_ptr{}; + } + + coordf_t line_spacing = infill_extrusion_width / ((fill_density / 100.0f) * 0.333333333f); + + TriangleMesh mesh = this->model_object()->raw_mesh(); + mesh.transform(m_trafo, true); + // Apply XY shift + mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); + + // Center of the first cube in octree + Vec3d mesh_origin = mesh.bounding_box().center(); + return FillAdaptive::build_octree(mesh, line_spacing, mesh_origin); +} + void PrintObject::clear_layers() { for (Layer *l : m_layers)