Files
OrcaSlicer/src/libslic3r/Fill/FillGyroid.cpp
T
Hanif Koh e374956d03 Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced
Generated with include-what-you-use and applied conservatively. Only OrcaSlicer's own headers, the ones under src/ and tests/, are removed or forward-declared; standard-library and third-party includes are left alone. An include is removed only when both the Release and the Debug configuration leave it unused, never from inside a conditional block, and never from a file with platform-specific blocks, which only gain includes. Files whose only use of a header sits behind a feature or debug macro (libvgcode's OpenGL ES and marker code, the ARACHNE/TESTS_EXPORT_SVGS debug output) keep their includes.

clonable_ptr.hpp gains #pragma once; it had no include guard and was only safe while Config.hpp was its sole includer.
2026-10-04 14:53:22 +08:00

383 lines
16 KiB
C++

#include "../ClipperUtils.hpp"
#include "../MarchingSquares.hpp"
#include <cmath>
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <limits>
#include "libslic3r/BoundingBox.hpp"
#include <vector>
#include "libslic3r/Execution/ExecutionTBB.hpp"
#include <math.h>
#include <utility>
#include "libslic3r/ExPolygon.hpp"
#include "FillBase.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/Polyline.hpp"
#include "FillGyroid.hpp"
#include "libslic3r/Polygon.hpp"
// ---------------------------------------------------------------------------
// Marching-squares scalar field for the optimized gyroid branch.
// Modeled after FillTpmsFK.cpp's ScalarField.
//
// The gyroid scalar field is the standard implicit equation
// F(x,y,z) = sin(fx*x)cos(fy*y) + sin(fy*y)cos(fz*z) + sin(fz*z)cos(fx*x)
// Marching squares extracts the iso-zero contour, which gives smoother
// transitions between vertical and horizontal regimes than the analytical
// asin-based wave generator. Setting fz = omega * baseline anisotropically
// tightens the wave along the layer-stacking axis, shortening the effective
// vertical strand length and improving column-buckling resistance under
// Z-axis compression.
// ---------------------------------------------------------------------------
namespace marchsq {
using namespace Slic3r;
using coordr_t = long;
using Pointf = Vec2d;
struct GyroidField
{
static constexpr float gsizef = 0.40f;
static constexpr float rsizef = 0.004f;
const coord_t rsize = scaled(rsizef);
const coordr_t gsize = std::round(gsizef / rsizef);
Point size;
Point offs;
coordf_t z;
float fx;
float fy;
float fz;
float isoval = 0.0f;
explicit GyroidField(const BoundingBox bb, const coordf_t z, const float period, const float omega = 1.0f)
: size{bb.size()}, offs{bb.min}, z{z}
{
const float baseline = float(2.0 * PI) / std::max(period, 1e-3f);
fx = baseline;
fy = baseline;
fz = omega * baseline;
}
float get_scalar(coordf_t x, coordf_t y, coordf_t z_arg) const
{
const float a = fx * float(x);
const float b = fy * float(y);
const float c = fz * float(z_arg);
return std::sin(a) * std::cos(b) + std::sin(b) * std::cos(c) + std::sin(c) * std::cos(a);
}
float get_scalar(Coord p) const
{
Pointf pf = to_Pointf(p);
return get_scalar(pf.x(), pf.y(), z);
}
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; }
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)); }
};
template<> struct _RasterTraits<GyroidField>
{
using ValueType = float;
static float get (const GyroidField& sf, size_t row, size_t col) { return sf.get_scalar(Coord(row, col)); }
static size_t rows(const GyroidField& sf) { return sf.to_coordr(sf.size.y()); }
static size_t cols(const GyroidField& sf) { return sf.to_coordr(sf.size.x()); }
};
inline Polylines get_gyroid_polylines(const GyroidField& 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());
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));
pts.push_back(pts.front());
if (tolerance >= 0.0)
poly.simplify(tolerance);
polys.emplace_back(poly);
}
return polys;
}
} // namespace marchsq
namespace Slic3r {
static inline double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
{
if (vertical) {
double phase_offset = (z_cos < 0 ? M_PI : 0) + M_PI;
double a = sin(x + phase_offset);
double b = - z_cos;
double res = z_sin * cos(x + phase_offset + (flip ? M_PI : 0.));
double r = sqrt(sqr(a) + sqr(b));
return asin(a/r) + asin(res/r) + M_PI;
}
else {
double phase_offset = z_sin < 0 ? M_PI : 0.;
double a = cos(x + phase_offset);
double b = - z_sin;
double res = z_cos * sin(x + phase_offset + (flip ? 0 : M_PI));
double r = sqrt(sqr(a) + sqr(b));
return (asin(a/r) + asin(res/r) + 0.5 * M_PI);
}
}
// Repeats one period of a wave from the last sample at or before x_min to the first one at or after x_max.
static inline Polyline make_wave(
const std::vector<Vec2d>& one_period, double x_min, double x_max, double offset, double scaleFactor, bool vertical)
{
const double period = one_period.back().x();
// The last sample of a period is the first one of the next.
const size_t n = one_period.size() - 1;
double x0 = std::floor(x_min / period) * period;
size_t i = 0;
while (i + 1 < n && x0 + one_period[i + 1].x() <= x_min)
++i;
Polyline polyline;
polyline.points.reserve(size_t((x_max - x0) / period + 1.) * n + 1);
for (;;) {
Vec2d point(x0 + one_period[i].x(), one_period[i].y() + offset);
const bool last = point.x() >= x_max;
if (vertical)
std::swap(point(0), point(1));
polyline.points.emplace_back((point * scaleFactor).cast<coord_t>());
if (last)
break;
if (++i == n) {
i = 0;
x0 += period;
}
}
return polyline;
}
static std::vector<Vec2d> make_one_period(double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance)
{
std::vector<Vec2d> points;
double dx = M_PI_2; // exact coordinates on main inflexion lobes
double limit = 2*M_PI;
points.reserve(coord_t(ceil(limit / tolerance / 3)));
for (double x = 0.; x < limit - EPSILON; x += dx) {
points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
}
points.emplace_back(Vec2d(limit, f(limit, z_sin, z_cos, vertical, flip)));
// piecewise increase in resolution up to requested tolerance
for(;;)
{
size_t size = points.size();
for (unsigned int i = 1;i < size; ++i) {
auto& lp = points[i-1]; // left point
auto& rp = points[i]; // right point
double x = lp(0) + (rp(0) - lp(0)) / 2;
double y = f(x, z_sin, z_cos, vertical, flip);
Vec2d ip = {x, y};
if (std::abs(cross2(Vec2d(ip - lp), Vec2d(ip - rp))) > sqr(tolerance)) {
points.emplace_back(std::move(ip));
}
}
if (size == points.size())
break;
else
{
// insert new points in order
std::sort(points.begin(), points.end(),
[](const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0); });
}
}
return points;
}
// ---------------------------------------------------------------------------
// "Optimized" gyroid wave: marching-squares variant gated on
// params.gyroid_optimized. The wave shape is extracted from the gyroid
// implicit scalar field (see marchsq::GyroidField above) at iso=0, with
// the Z dimension's spatial frequency multiplied by an Euler-Bernoulli
// buckling-derived factor so the vertical strands become shorter columns,
// raising the critical buckling load against Z-axis compression.
//
// The formula is INVERTED from a naive "scale with density" derivation:
// at LOW density the gyroid strands are long and slender (prime buckling
// targets), so they need the most shortening; at high density the strands
// are already short and need little extra help. omega is therefore the
// inverse-square-root of density_adjusted:
//
// omega = sqrt(1 / density_adj) / sqrt(1 + layer_h/spacing),
// clamped [1.0, 2.0]
//
// fx and fy are left at the baseline frequency, so the per-XY-slice line
// length per unit area is preserved -> mass at the same `sparse_infill_density`
// setting matches the standard gyroid path. Strength gain comes purely from
// the shorter vertical column length (P_cr proportional to 1/L^2).
//
// Empirical Python sim (sim_gyroid_compare.py) at layer_h=0.20, spacing=0.45:
//
// density omega line/std strength/std strength_per_mass
// 10% 2.00 1.00 2.84 2.84
// 15% 1.38 1.00 1.89 1.89
// 20% 1.19 1.00 1.42 1.42
// 30% 1.00 1.00 1.00 1.00
// 50%+ 1.00 1.00 1.00 1.00
//
// When gyroid_optimized is false, behavior is byte-identical to the
// standard parametric gyroid path below.
// ---------------------------------------------------------------------------
static inline double compute_omega_factor(double density_adjusted, double line_spacing, double layer_height)
{
double lh_ratio = (line_spacing > 0.) ? layer_height / line_spacing : 0.5;
double correction = 1.0 / std::sqrt(1.0 + lh_ratio);
double raw = std::sqrt(1.0 / std::max(density_adjusted, 0.1)) * correction;
return std::clamp(raw, 1.0, 2.0);
}
// Waves covering bbox, with the pattern anchored at origin.
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, const BoundingBox &bbox, const Point &origin)
{
const double scaleFactor = scale_(line_spacing) / density_adjusted;
// tolerance in scaled units. clamp the maximum tolerance as there's
// no processing-speed benefit to do so beyond a certain point
const double tolerance = std::min(line_spacing / 2, FillGyroid::PatternTolerance) / unscale<double>(scaleFactor);
//scale factor for 5% : 8 712 388
// 1z = 10^-6 mm ?
const double z = gridZ / scaleFactor;
const double z_sin = sin(z);
const double z_cos = cos(z);
bool vertical = (std::abs(z_sin) <= std::abs(z_cos));
// Range to cover in pattern units, with the waves running along x.
Vec2d lo = (bbox.min - origin).cast<double>() / scaleFactor;
Vec2d hi = (bbox.max - origin).cast<double>() / scaleFactor;
double lower_bound = 0.;
bool flip = true;
if (vertical) {
flip = false;
lower_bound = -M_PI;
std::swap(lo(0), lo(1));
std::swap(hi(0), hi(1));
}
std::vector<Vec2d> one_period_odd = make_one_period(scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time
flip = !flip; // even polylines are a bit shifted
std::vector<Vec2d> one_period_even = make_one_period(scaleFactor, z_cos, z_sin, vertical, flip, tolerance);
// Every wave spans [offset + f_min, offset + f_max] across.
double f_min = std::numeric_limits<double>::max();
double f_max = std::numeric_limits<double>::lowest();
for (const std::vector<Vec2d> *one_period : { &one_period_odd, &one_period_even })
for (const Vec2d &point : *one_period) {
f_min = std::min(f_min, point.y());
f_max = std::max(f_max, point.y());
}
Polylines result;
for (int i = int(std::ceil((lo.y() - f_max - lower_bound) / M_PI)); lower_bound + i * M_PI + f_min <= hi.y(); ++i) {
Polyline &wave = result.emplace_back(make_wave(i % 2 == 0 ? one_period_odd : one_period_even, lo.x(), hi.x(),
lower_bound + i * M_PI, scaleFactor, vertical));
wave.translate(origin);
}
return result;
}
// FIXME: needed to fix build on Mac on buildserver
constexpr double FillGyroid::PatternTolerance;
void FillGyroid::_fill_surface_single(
const FillParams &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
Polylines &polylines_out)
{
auto infill_angle = float(this->angle + (CorrectionAngle * 2*M_PI) / 360.);
if(std::abs(infill_angle) >= EPSILON)
expolygon.rotate(-infill_angle);
BoundingBox bb = expolygon.contour.bounding_box();
// Density adjusted to have a good %of weight.
double density_adjusted = std::max(0., params.density * DensityAdjust / params.multiline);
// Distance between the gyroid waves in scaled coordinates.
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
// Anchor the pattern to our grid module; the 10-line shift keeps its established phase.
const coord_t shift = coord_t(10 * scale_(this->spacing));
const Point origin = align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)) - Point(shift, shift);
// Keep the pattern ends and the multiline copies outside the contour.
bb.offset(scale_(this->spacing * params.multiline));
// generate pattern
Polylines polylines;
if (params.gyroid_optimized) {
// Marching-squares path on the gyroid implicit field. Base period matches
// the standard parametric path's wavelength: 2*pi * spacing / density_adj.
// omega >= 1 always, so fz >= baseline -> shorter vertical wavelength ->
// shorter effective column length -> higher buckling resistance.
//
// Mass: fx and fy are left at baseline (same as standard), so the
// per-XY-slice line length per unit area is approximately preserved.
// Empirically (sim_gyroid_compare.py) the optimized line/std ratio is
// ~1.000 across densities, so no period compensation is needed.
const double lh = (params.layer_height > 0.) ? double(params.layer_height) : double(this->spacing);
const double omega = compute_omega_factor(density_adjusted, this->spacing * params.multiline, lh);
const float density_factor = std::max(0.001f, float(params.density * DensityAdjust / params.multiline));
const float period = float(2.0 * M_PI) * float(this->spacing) / density_factor;
// A cell of margin for the rings closed along the raster border, and a fixed sampling grid for every region.
const coord_t cell = scaled(marchsq::GyroidField::gsizef);
bb.offset(cell);
bb.merge(align_to_grid(bb.min, Point(cell, cell)));
marchsq::GyroidField sf(bb, this->z, period, float(omega));
polylines = marchsq::get_gyroid_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION);
} else {
polylines = make_gyroid_waves(scale_(this->z), density_adjusted, this->spacing, bb, origin);
}
// Apply multiline offset if needed
multiline_fill(polylines, params, spacing);
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);
// 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