Files
OrcaSlicer/src/libslic3r/Fill/FillTpmsFK.cpp
T
2026-10-09 12:36:09 -03:00

196 lines
8.8 KiB
C++

#include "../ClipperUtils.hpp"
#include "../MarchingSquares.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Polyline.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
#include "libslic3r/Fill/FillBase.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "FillTpmsFK.hpp"
#include "FillTpmsAdaptive.hpp"
#include <cmath>
#include <algorithm>
#include <cstddef>
#include <math.h>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "libslic3r/Polygon.hpp"
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
// Fischer - Koch S equation:
// cos(2x)sin(y)cos(z) + cos(2y)sin(z)cos(x) + cos(2z)sin(x)cos(y) = 0
static float fischer_koch(float x, float y, float z)
{
return cosf(2 * x) * sinf(y) * cosf(z) + cosf(2 * y) * sinf(z) * cosf(x) + cosf(2 * z) * sinf(x) * cosf(y);
}
} // namespace Slic3r
namespace marchsq {
using namespace Slic3r;
using coordr_t = long; // length type for (r, c) raster coordinates.
// Note that coordf_t, Pointfs, Point3f, etc all use double not float.
using Pointf = Vec2d; // (x, y) field point in coordf_t.
struct ScalarField
{
static constexpr float gsizef = 0.40; // grid cell size in mm (roughly line segment length).
static constexpr float rsizef = 0.004; // raster pixel size in mm (roughly point accuracy).
const coord_t rsize = scaled(rsizef); // raster pixel size in coord_t.
const coordr_t gsize = std::round(gsizef / rsizef); // grid cell size in coordr_t.
Point size; // field size in coord_t.
Point offs; // field offset in coord_t.
coordf_t z; // z offset as a float.
float freq; // field frequency in cycles per mm.
float isoval = 0.0; // iso value threshold to use.
explicit ScalarField(const BoundingBox bb, const coordf_t z = 0.0, const float period = 10.0)
: size{bb.size()}, offs{bb.min}, z{z}, freq{float(2 * PI) / period}
{}
// Get the scalar field value at x,y,z in coordf_t coordinates.
float get_scalar(coordf_t x, coordf_t y, coordf_t z) const { return fischer_koch(freq * x, freq * y, freq * z); }
// Get the scalar field value at a Coord for the current z value.
float get_scalar(Coord p) const
{
Pointf pf = to_Pointf(p);
return get_scalar(pf.x(), pf.y(), z);
}
// Convert between dimension scales.
inline coord_t to_coord(const coordr_t& x) const { return x * rsize; }
inline coordr_t to_coordr(const coord_t& x) const { return x / rsize; }
// Convert between point/coordinate systems, including translation.
inline Point to_Point(const Coord& p) const { return Point(to_coord(p.c) + offs.x(), to_coord(p.r) + offs.y()); }
inline Coord to_Coord(const Point& p) const { return Coord(to_coordr(p.y() - offs.y()), to_coordr(p.x() - offs.x())); }
inline Pointf to_Pointf(const Point& p) const { return Pointf(unscaled(p.x()), unscaled(p.y())); }
inline Pointf to_Pointf(const Coord& p) const { return to_Pointf(to_Point(p)); }
};
// Register ScalarField as a RasterType for MarchingSquares.
template<> struct _RasterTraits<ScalarField>
{
// The type of pixel cell in the raster
using ValueType = float;
// Value at a given position
static float get(const ScalarField& sf, size_t row, size_t col) { return sf.get_scalar(Coord(row, col)); }
// Number of rows and cols of the raster
static size_t rows(const ScalarField& sf) { return sf.to_coordr(sf.size.y()); }
static size_t cols(const ScalarField& sf) { return sf.to_coordr(sf.size.x()); }
};
// Get the polylines for the scalar field. The tolerance is used for
// simplifying the polylines to remove redundant points. The default will
// only remove points on (almost) perfectly straight lines. Set to -1 to turn
// off simplifying entirely. Note tolerance is the max line deviation from
// simplifying and should be scaled.
Polylines get_polylines(const ScalarField& sf, const double tolerance = SCALED_EPSILON)
{
std::vector<Ring> rings = execute_with_policy(ex_tbb, sf, sf.isoval, {sf.gsize, sf.gsize});
Polylines polys;
polys.reserve(rings.size());
// size_t old_pts = 0, new_pts = 0;
for (const Ring& ring : rings) {
Polyline poly;
Points& pts = poly.points;
pts.reserve(ring.size() + 1);
for (const Coord& crd : ring)
pts.emplace_back(sf.to_Point(crd));
// MarchingSquare's rings are polygons, so add the first point to the end to make it a PolyLine.
pts.push_back(pts.front());
// old_pts += poly.points.size();
// Simplify within specified tolerance to reduce points.
if (tolerance >= 0.0)
poly.simplify(tolerance);
// new_pts += poly.points.size();
polys.emplace_back(poly);
}
// std::cerr << "MarchingSquares: poly.simplify(" << tolerance << ") reduced points from" <<
// old_pts << " to " << new_pts << " (" << 100*new_pts/old_pts << "%)\n";
return polys;
}
} // namespace marchsq
namespace Slic3r {
void FillTpmsFK::_fill_surface_single(const FillParams& params,
unsigned int thickness_layers,
const std::pair<float, Point>& direction,
ExPolygon expolygon,
Polylines& polylines_out)
{
if (params.tpms_adaptive == TpmsAdaptiveMode::SteppedShells && this->tpms_radial_field != nullptr) {
fill_tpms_shells(*this->tpms_radial_field, expolygon, this->z - 0.5 * params.layer_height, params, this->spacing,
[&](const FillParams &shell_params, const ExPolygon &shell) {
this->_fill_surface_single(shell_params, thickness_layers, direction, shell, polylines_out);
});
return;
}
auto infill_angle = float(this->angle + (CorrectionAngle * 2 * M_PI) / 360.);
if (std::abs(infill_angle) >= EPSILON)
expolygon.rotate(-infill_angle);
// Density (field period) adjusted to have a good %of weight.
auto period = [&params, this](float density) { return 4.18f * spacing * params.multiline / std::min(0.9f, density); };
BoundingBox bbox = expolygon.contour.bounding_box();
// Enlarge the bounding box by the multi-line width to avoid artifacts at the edges.
bbox.offset(scale_((params.multiline + 1) * spacing));
Polylines polylines;
if (params.tpms_adaptive != TpmsAdaptiveMode::Disabled && this->tpms_radial_field != nullptr) {
polylines = make_adaptive_tpms({fischer_koch, 2. * PI / period(params.density), 2. * PI / period(params.tpms_interior_density),
params.tpms_adaptive_gradient},
*this->tpms_radial_field, bbox, this->z, params.layer_height, spacing, infill_angle);
} else {
marchsq::ScalarField sf = marchsq::ScalarField(bbox, this->z, period(params.density));
// Get simplified lines using coarse tolerance of 0.1mm (this is infill).
polylines = marchsq::get_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION);
}
// Apply multiline offset if needed
multiline_fill(polylines, params, spacing);
// Prune the lines within the expolygon.
polylines = intersection_pl(std::move(polylines), expolygon);
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()) {
// connect lines
size_t polylines_out_first_idx = polylines_out.size();
// chain_or_connect_infill(std::move(polylines), expolygon, polylines_out, this->spacing, params);
// chain_infill not situable for this pattern due to internal "islands", this also affect performance a lot.
connect_infill(std::move(polylines), expolygon, polylines_out, this->spacing, params);
// new paths must be rotated back
if (std::abs(infill_angle) >= EPSILON) {
for (auto it = polylines_out.begin() + polylines_out_first_idx; it != polylines_out.end(); ++it)
it->rotate(infill_angle);
}
}
}
} // namespace Slic3r