Skip to content

Commit

Permalink
[FEATURE] Port CrossHatch infill pattern from BambuSlicer
Browse files Browse the repository at this point in the history
  • Loading branch information
mjonuschat committed Jun 27, 2024
1 parent 185281f commit 203d7af
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/libslic3r/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ set(SLIC3R_SOURCES
Fill/FillBase.hpp
Fill/FillConcentric.cpp
Fill/FillConcentric.hpp
Fill/FillCrossHatch.cpp
Fill/FillCrossHatch.hpp
Fill/FillEnsuring.cpp
Fill/FillEnsuring.hpp
Fill/FillHoneycomb.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/libslic3r/Fill/FillBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include "FillBase.hpp"
#include "FillConcentric.hpp"
#include "FillCrossHatch.hpp"
#include "FillHoneycomb.hpp"
#include "Fill3DHoneycomb.hpp"
#include "FillGyroid.hpp"
Expand All @@ -46,6 +47,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipGyroid: return new FillGyroid();
case ipRectilinear: return new FillRectilinear();
case ipAlignedRectilinear: return new FillAlignedRectilinear();
case ipCrossHatch: return new FillCrossHatch();
case ipMonotonic: return new FillMonotonic();
case ipMonotonicLines: return new FillMonotonicLines();
case ipLine: return new FillLine();
Expand Down
233 changes: 233 additions & 0 deletions src/libslic3r/Fill/FillCrossHatch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
#include "../ClipperUtils.hpp"
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include <cmath>

#include "FillCrossHatch.hpp"

namespace Slic3r {

// CrossHatch Infill: Enhances 3D Printing Speed & Reduces Noise
// CrossHatch, as its name hints, alternates line direction by 90 degrees every few layers to improve adhesion.
// It introduces transform layers between direction shifts for better line cohesion, which fixes the weakness of line infill.
// The transform technique is inspired by David Eccles, improved 3D honeycomb but we made a more flexible implementation.
// This method notably increases printing speed, meeting the demands of modern high-speed 3D printers, and reduces noise for most layers.
// By Bambu Lab

// graph credits: David Eccles (gringer).
// But we made a different definition for points.
/* o---o
* / \
* / \
* \ /
* \ /
* o---o
* p1 p2 p3 p4
*/

static Pointfs generate_one_cycle(double progress, coordf_t period)
{
Pointfs out;
double offset = progress * 1. / 8. * period;
out.reserve(4);
out.push_back(Vec2d(0.25 * period - offset, offset));
out.push_back(Vec2d(0.25 * period + offset, offset));
out.push_back(Vec2d(0.75 * period - offset, -offset));
out.push_back(Vec2d(0.75 * period + offset, -offset));
return out;
}

static Polylines generate_transform_pattern(double inprogress, int direction, coordf_t ingrid_size, coordf_t inwidth, coordf_t inheight)
{
coordf_t width = inwidth;
coordf_t height = inheight;
coordf_t grid_size = ingrid_size * 2; // we due with odd and even saparately.
double progress = inprogress;
Polylines out_polylines;

// generate template patterns;
Pointfs one_cycle_points = generate_one_cycle(progress, grid_size);

Polyline one_cycle;
one_cycle.points.reserve(one_cycle_points.size());
for (size_t i = 0; i < one_cycle_points.size(); i++) one_cycle.points.push_back(Point(one_cycle_points[i]));

// swap if vertical
if (direction < 0) {
width = height;
height = inwidth;
}

// replicate polylines;
Polylines odd_polylines;
Polyline odd_poly;
int num_of_cycle = width / grid_size + 2;
odd_poly.points.reserve(num_of_cycle * one_cycle.size());

// replicate to odd line
Point translate = Point(0, 0);
for (size_t i = 0; i < num_of_cycle; i++) {
Polyline odd_points;
odd_points = Polyline(one_cycle);
odd_points.translate(Point(i * grid_size, 0.0));
odd_poly.points.insert(odd_poly.points.end(), odd_points.begin(), odd_points.end());
}

// fill the height
int num_of_lines = height / grid_size + 2;
odd_polylines.reserve(num_of_lines * odd_poly.size());
for (size_t i = 0; i < num_of_lines; i++) {
Polyline poly = odd_poly;
poly.translate(Point(0.0, grid_size * i));
odd_polylines.push_back(poly);
}
// save to output
out_polylines.insert(out_polylines.end(), odd_polylines.begin(), odd_polylines.end());

// replicate to even lines
Polylines even_polylines;
even_polylines.reserve(odd_polylines.size());
for (size_t i = 0; i < odd_polylines.size(); i++) {
Polyline even = odd_poly;
even.translate(Point(-0.5 * grid_size, (i + 0.5) * grid_size));
even_polylines.push_back(even);
}

// save for output
out_polylines.insert(out_polylines.end(), even_polylines.begin(), even_polylines.end());

// change to vertical if need
if (direction < 0) {
// swap xy, see if we need better performance method
for (Polyline &poly : out_polylines) {
for (Point &p : poly) { std::swap(p.x(), p.y()); }
}
}

return out_polylines;
}

static Polylines generate_repeat_pattern(int direction, coordf_t grid_size, coordf_t inwidth, coordf_t inheight)
{
coordf_t width = inwidth;
coordf_t height = inheight;
Polylines out_polylines;

// swap if vertical
if (direction < 0) {
width = height;
height = inwidth;
}

int num_of_lines = height / grid_size + 1;
out_polylines.reserve(num_of_lines);

for (int i = 0; i < num_of_lines; i++) {
Polyline poly;
poly.points.reserve(2);
poly.append(Point(coordf_t(0), coordf_t(grid_size * i)));
poly.append(Point(width, coordf_t(grid_size * i)));
out_polylines.push_back(poly);
}

// change to vertical if needed
if (direction < 0) {
// swap xy
for (Polyline &poly : out_polylines) {
for (Point &p : poly) { std::swap(p.x(), p.y()); }
}
}

return out_polylines;
}

// it makes the real patterns that overlap the bounding box
// repeat_ratio define the ratio between the height of repeat pattern and grid
static Polylines generate_infill_layers(coordf_t z_height, double repeat_ratio, coordf_t grid_size, coordf_t width, coordf_t height)
{
Polylines result;
coordf_t trans_layer_size = grid_size * 0.4; // upper.
coordf_t repeat_layer_size = grid_size * repeat_ratio; // lower.
z_height += repeat_layer_size / 2; // offset to improve first few layer strength
coordf_t period = trans_layer_size + repeat_layer_size;
coordf_t remains = z_height - std::floor(z_height / period) * period;
coordf_t trans_z = remains - repeat_layer_size; // put repeat layer first.
coordf_t repeat_z = remains;

int phase = fmod(z_height, period * 2) - (period - 1); // add epsilon
int direction = phase <= 0 ? -1 : 1;

// this is a repeat layer
if (trans_z < 0) {
result = generate_repeat_pattern(direction, grid_size, width, height);
}
// this is a transform layer
else {
double progress = fmod(trans_z, trans_layer_size) / trans_layer_size;

// split the progress to forward and backward, with a opposite direction.
if (progress < 0.5)
result = generate_transform_pattern((progress + 0.1) * 2, direction, grid_size, width, height); // increase overlapping.
else
result = generate_transform_pattern((1.1 - progress) * 2, -1 * direction, grid_size, width, height);
}

return result;
}

void FillCrossHatch ::_fill_surface_single(
const FillParams &params, unsigned int thickness_layers, const std::pair<float, Point> &direction, ExPolygon expolygon, Polylines &polylines_out)
{
// rotate angle
auto infill_angle = float(this->angle);
if (std::abs(infill_angle) >= EPSILON) expolygon.rotate(-infill_angle);

// get the rotated bounding box
BoundingBox bb = expolygon.contour.bounding_box();

// linespace modifier
coord_t line_spacing = coord_t(scale_(this->spacing) / params.density);

// reduce density
if (params.density < 0.999) line_spacing *= 1.08;

bb.merge(align_to_grid(bb.min, Point(line_spacing * 4, line_spacing * 4)));

// generate pattern
//Orca: optimize the cross-hatch infill pattern to improve strength when low infill density is used.
double repeat_ratio = 1.0;
if (params.density < 0.3)
repeat_ratio = std::clamp(1.0 - std::exp(-5 * params.density), 0.2, 1.0);

Polylines polylines = generate_infill_layers(scale_(this->z), repeat_ratio, line_spacing, bb.size()(0), bb.size()(1));

// shift the pattern to the actual space
for (Polyline &pl : polylines) { pl.translate(bb.min); }

polylines = intersection_pl(polylines, to_polygons(expolygon));

// --- remove small remains from gyroid infill
if (!polylines.empty()) {
// Remove very small bits, but be careful to not remove infill lines connecting thin walls!
// The infill perimeter lines should be separated by around a single infill line width.
const double minlength = scale_(0.8 * this->spacing);
polylines.erase(std::remove_if(polylines.begin(), polylines.end(), [minlength](const Polyline &pl)
{ return pl.length() < minlength; }), polylines.end());
}

if (!polylines.empty()) {
int infill_start_idx = polylines_out.size(); // only rotate what belongs to us.
// connect lines
if (params.dont_connect() || polylines.size() <= 1)
append(polylines_out, chain_polylines(std::move(polylines)));
else
this->connect_infill(std::move(polylines), expolygon, polylines_out, this->spacing, params);

// rotate back
if (std::abs(infill_angle) >= EPSILON) {
for (auto it = polylines_out.begin() + infill_start_idx; it != polylines_out.end(); ++it) it->rotate(infill_angle);
}
}
}

} // namespace Slic3r
31 changes: 31 additions & 0 deletions src/libslic3r/Fill/FillCrossHatch.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef slic3r_FillCrossHatch_hpp_
#define slic3r_FillCrossHatch_hpp_

#include <map>

#include "../libslic3r.h"

#include "FillBase.hpp"

namespace Slic3r {

class FillCrossHatch : public Fill
{
public:
Fill *clone() const override { return new FillCrossHatch(*this); };
~FillCrossHatch() override {}

bool is_self_crossing() override { return false; }

protected:
void _fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out) override;
};

} // namespace Slic3r

#endif // slic3r_FillCrossHatch_hpp_
6 changes: 4 additions & 2 deletions src/libslic3r/PrintConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ static const t_config_enum_values s_keys_map_InfillPattern {
{ "octagramspiral", ipOctagramSpiral },
{ "adaptivecubic", ipAdaptiveCubic },
{ "supportcubic", ipSupportCubic },
{ "lightning", ipLightning }
{ "lightning", ipLightning },
{ "crosshatch", ipCrossHatch }
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern)

Expand Down Expand Up @@ -1560,7 +1561,8 @@ void PrintConfigDef::init_fff_params()
{ "octagramspiral", L("Octagram Spiral")},
{ "adaptivecubic", L("Adaptive Cubic")},
{ "supportcubic", L("Support Cubic")},
{ "lightning", L("Lightning")}
{ "lightning", L("Lightning")},
{ "crosshatch", L("Cross Hatch")}
});
def->set_default_value(new ConfigOptionEnum<InfillPattern>(ipStars));

Expand Down
1 change: 1 addition & 0 deletions src/libslic3r/PrintConfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ enum InfillPattern : int {
ipRectilinear, ipMonotonic, ipMonotonicLines, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase,
ipLightning,
ipCrossHatch,
ipEnsuring,
ipCount,
};
Expand Down
14 changes: 11 additions & 3 deletions src/libslic3r/PrintObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2065,9 +2065,16 @@ void PrintObject::bridge_over_infill()
};

// LAMBDA do determine optimal bridging angle
auto determine_bridging_angle = [](const Polygons &bridged_area, const Lines &anchors, InfillPattern dominant_pattern) {
auto determine_bridging_angle = [](const Polygons &bridged_area, const Lines &anchors, InfillPattern dominant_pattern, double fill_angle) {
AABBTreeLines::LinesDistancer<Line> lines_tree(anchors);

// Check it the infill that require a fixed infill angle.
switch (dominant_pattern) {
case ip3DHoneycomb:
case ipCrossHatch: return (fill_angle + 45.0) * 2.0 * M_PI / 360.;
default: break;
}

std::map<double, int> counted_directions;
for (const Polygon &p : bridged_area) {
double acc_distance = 0;
Expand Down Expand Up @@ -2442,11 +2449,12 @@ void PrintObject::bridge_over_infill()
double bridging_angle = 0;
if (!anchors.empty()) {
bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(anchors),
candidate.region->region().config().fill_pattern.value);
candidate.region->region().config().fill_pattern.value,
candidate.region->region().config().fill_angle.value);
} else {
// use expansion boundaries as anchors.
// Also, use Infill pattern that is neutral for angle determination, since there are no infill lines.
bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(boundary_plines), InfillPattern::ipLine);
bridging_angle = determine_bridging_angle(area_to_be_bridge, to_lines(boundary_plines), InfillPattern::ipLine, 0);
}

boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end());
Expand Down

0 comments on commit 203d7af

Please sign in to comment.