mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-04 14:20:58 +00:00
* Add Missing Includes Across src/libslic3r Every libslic3r source and header now directly includes the headers declaring what it uses, rather than relying on the precompiled header or transitive includes. Generated with clang-tidy misc-include-cleaner, with libslic3r headers spelled libslic3r/... so they resolve outside the library's private include paths. MultiMaterialSegmentation.hpp, Support/SupportParameters.hpp and Format/STEP.hpp are made self-contained by hand. * Make the libslic3r Headers Compile on Their Own Each now includes, or forward-declares, what it uses instead of relying on what its includers happened to include first. Left out: I18N.hpp, which errors on purpose when included from GUI code, and VoxelizeCSGMesh.hpp and SLA/bicubic.h, which nothing includes and which no longer compile at all. * Add the Includes Missing From the Hand-Fixed libslic3r Headers clang-tidy would not edit these headers while they failed to compile on their own, so the first pass skipped them. With the headers now self-contained, a second pass adds the rest. * Keep Windows Setup Ahead of the Added libslic3r Includes Print.cpp and Thread.cpp open with a _WIN32 block that has to come first; without the precompiled header, Print.cpp otherwise reaches windows.h through OCCT with NONLS defined and boost/regex fails. OpenVDBUtils.cpp and SLA/SupportTreeBuilder.cpp had includes inside #ifndef NOMINMAX, which libslic3r defines on Windows, so those were skipped there. .clang-tidy also ignores the MSVC STL and UCRT internals, Boost.Multiprecision's fwd.hpp and CPython's Windows include directory. * Re-Add libslic3r Includes After the Clipper2 2.0.1 Migration Rebasing onto main took main's version of the files the Clipper2 migration rewrote, so their added includes are restored here, along with includes for main's new code. Clipper2's individual headers are now ignored by clang-tidy: they only build the Z variant through clipper2_z.hpp, which defines USINGZ first, so including clipper.core.h and the like directly broke ClipperZUtils.cpp.
384 lines
16 KiB
C++
384 lines
16 KiB
C++
#include "../ClipperUtils.hpp"
|
|
#include "../MarchingSquares.hpp"
|
|
#include "../ShortestPath.hpp"
|
|
#include "../Surface.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"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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 ¶ms,
|
|
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
|