Skip to content

Commit 6e62bc6

Browse files
Add Z-buckling bias optimization (experimental) for Gyroid infill
Mirrors the design accepted in OrcaSlicer #13379. Adds a boolean config option `gyroid_optimized` (default false) that, when enabled on a region using the Gyroid infill pattern, replaces the analytical wave generator with a marching-squares iso-extraction on the gyroid implicit field with anisotropic Z-axis frequency scaling. Behavior: - omega = sqrt(1 / density_adj), clamped [1.0, 2.0]. Maximum boost at low density (long, slender vertical strands); clamps to no-op at ~30%+ density. - fz = omega * baseline; fx = fy = baseline. Tightens vertical wave to shorten effective column length under Z-axis compression. - Filament use is preserved at the same `sparse_infill_density` setting (verified via slicer + Python sim, ratio ~ 1.000). - Existing profiles using `gyroid` are byte-identical when the checkbox is off. User-facing: "Z-buckling bias optimization (experimental)" under Print Settings -> Strength -> Sparse infill, visible only when the sparse infill pattern is Gyroid.
1 parent dacd788 commit 6e62bc6

8 files changed

Lines changed: 167 additions & 13 deletions

File tree

src/libslic3r/Fill/Fill.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ struct SurfaceFillParams
7878
float lattice_angle_1 = -45.0f;
7979
float lattice_angle_2 = 45.0f;
8080

81+
// For Gyroid: when true, use the parameterized "optimized" variant.
82+
bool gyroid_optimized = false;
83+
8184
bool operator<(const SurfaceFillParams &rhs) const {
8285
#define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false;
8386
#define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false;
@@ -112,6 +115,7 @@ struct SurfaceFillParams
112115
RETURN_COMPARE_NON_EQUAL(lattice_angle_2);
113116
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, skin_pattern);
114117
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, skeleton_pattern);
118+
RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
115119
return false;
116120
}
117121

@@ -140,7 +144,8 @@ struct SurfaceFillParams
140144
this->lattice_angle_1 == rhs.lattice_angle_1 &&
141145
this->lattice_angle_2 == rhs.lattice_angle_2&&
142146
this-> skin_pattern == rhs.skin_pattern &&
143-
this-> skeleton_pattern == rhs.skeleton_pattern;
147+
this-> skeleton_pattern == rhs.skeleton_pattern &&
148+
this-> gyroid_optimized == rhs.gyroid_optimized;
144149
}
145150
};
146151

@@ -253,6 +258,10 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
253258
params.pattern == ipSupportCubic;
254259
params.multiline = (params.extrusion_role == erInternalInfill && support_multiline_infill) ? int(region_config.fill_multiline) : 1;
255260

261+
// Pass gyroid_optimized through only when the effective pattern is Gyroid,
262+
// so non-Gyroid fills aren't differentiated by an irrelevant flag.
263+
params.gyroid_optimized = (params.pattern == ipGyroid) && region_config.gyroid_optimized;
264+
256265
// Calculate the actual flow we'll be using for this infill.
257266
params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
258267
params.flow = params.bridge ?
@@ -710,6 +719,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
710719
params.resolution = resolution;
711720
params.use_arachne = surface_fill.params.pattern == ipConcentric || surface_fill.params.pattern == ipFloatingConcentric;
712721
params.layer_height = m_regions[surface_fill.region_id]->layer()->height;
722+
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
713723

714724
// BBS
715725
params.flow = surface_fill.params.flow;
@@ -857,6 +867,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
857867
params.resolution = resolution;
858868
params.use_arachne = false;
859869
params.layer_height = layerm.layer()->height;
870+
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
860871

861872
// Pass pattern-specific parameters so that anchoring lines match the actual infill.
862873
if (surface_fill.params.pattern == ip2DLattice) {

src/libslic3r/Fill/FillBase.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ struct FillParams
8282
// Layer height for Concentric infill with Arachne.
8383
coordf_t layer_height { 0.f };
8484

85+
// For Gyroid: when true, use the parameterized "optimized" variant.
86+
bool gyroid_optimized { false };
87+
8588
InfillPattern pattern{ ipRectilinear };
8689

8790
// BBS

src/libslic3r/Fill/FillGyroid.cpp

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,121 @@
11
#include "../ClipperUtils.hpp"
2+
#include "../MarchingSquares.hpp"
23
#include "../ShortestPath.hpp"
34
#include "../Surface.hpp"
45
#include <cmath>
56
#include <algorithm>
67
#include <iostream>
78

9+
#include "FillBase.hpp"
810
#include "FillGyroid.hpp"
911

12+
// ---------------------------------------------------------------------------
13+
// Marching-squares scalar field for the optimized gyroid branch.
14+
//
15+
// The gyroid scalar field is the standard implicit equation
16+
// F(x,y,z) = sin(fx*x)cos(fy*y) + sin(fy*y)cos(fz*z) + sin(fz*z)cos(fx*x)
17+
// Marching squares extracts the iso-zero contour, giving smoother transitions
18+
// between vertical and horizontal regimes than the analytical asin-based wave
19+
// generator. Setting fz = omega * baseline anisotropically tightens the wave
20+
// along the layer-stacking axis, shortening the effective vertical strand
21+
// length and improving column-buckling resistance under Z-axis compression.
22+
// ---------------------------------------------------------------------------
23+
namespace marchsq {
24+
using namespace Slic3r;
25+
26+
using coordr_t = long;
27+
using Pointf = Vec2d;
28+
29+
struct GyroidField
30+
{
31+
static constexpr float gsizef = 0.40f;
32+
static constexpr float rsizef = 0.004f;
33+
const coord_t rsize = scaled(rsizef);
34+
const coordr_t gsize = std::round(gsizef / rsizef);
35+
Point size;
36+
Point offs;
37+
coordf_t z;
38+
float fx;
39+
float fy;
40+
float fz;
41+
float isoval = 0.0f;
42+
43+
explicit GyroidField(const BoundingBox bb, const coordf_t z, const float period, const float omega = 1.0f)
44+
: size{bb.size()}, offs{bb.min}, z{z}
45+
{
46+
const float baseline = float(2.0 * PI) / std::max(period, 1e-3f);
47+
fx = baseline;
48+
fy = baseline;
49+
fz = omega * baseline;
50+
}
51+
52+
float get_scalar(coordf_t x, coordf_t y, coordf_t z_arg) const
53+
{
54+
const float a = fx * float(x);
55+
const float b = fy * float(y);
56+
const float c = fz * float(z_arg);
57+
return std::sin(a) * std::cos(b) + std::sin(b) * std::cos(c) + std::sin(c) * std::cos(a);
58+
}
59+
60+
float get_scalar(Coord p) const
61+
{
62+
Pointf pf = to_Pointf(p);
63+
return get_scalar(pf.x(), pf.y(), z);
64+
}
65+
66+
inline coord_t to_coord (const coordr_t& x) const { return x * rsize; }
67+
inline coordr_t to_coordr(const coord_t& x) const { return x / rsize; }
68+
inline Point to_Point (const Coord& p) const { return Point(to_coord(p.c) + offs.x(), to_coord(p.r) + offs.y()); }
69+
inline Coord to_Coord (const Point& p) const { return Coord(to_coordr(p.y() - offs.y()), to_coordr(p.x() - offs.x())); }
70+
inline Pointf to_Pointf(const Point& p) const { return Pointf(unscaled(p.x()), unscaled(p.y())); }
71+
inline Pointf to_Pointf(const Coord& p) const { return to_Pointf(to_Point(p)); }
72+
};
73+
74+
template<> struct _RasterTraits<GyroidField>
75+
{
76+
using ValueType = float;
77+
static float get (const GyroidField& sf, size_t row, size_t col) { return sf.get_scalar(Coord(row, col)); }
78+
static size_t rows(const GyroidField& sf) { return sf.to_coordr(sf.size.y()); }
79+
static size_t cols(const GyroidField& sf) { return sf.to_coordr(sf.size.x()); }
80+
};
81+
82+
inline Polylines get_gyroid_polylines(const GyroidField& sf, const double tolerance = SCALED_EPSILON)
83+
{
84+
std::vector<Ring> rings = execute_with_policy(ex_tbb, sf, sf.isoval, {sf.gsize, sf.gsize});
85+
Polylines polys;
86+
polys.reserve(rings.size());
87+
for (const Ring& ring : rings) {
88+
Polyline poly;
89+
Points& pts = poly.points;
90+
pts.reserve(ring.size() + 1);
91+
for (const Coord& crd : ring)
92+
pts.emplace_back(sf.to_Point(crd));
93+
pts.push_back(pts.front());
94+
if (tolerance >= 0.0)
95+
poly.simplify(tolerance);
96+
polys.emplace_back(poly);
97+
}
98+
return polys;
99+
}
100+
101+
} // namespace marchsq
102+
10103
namespace Slic3r {
11104

105+
// ---------------------------------------------------------------------------
106+
// Z-buckling bias optimization: omega = sqrt(1 / density_adj) clamped [1, 2].
107+
// At low density (long, slender vertical strands) omega is highest; at ~30%+
108+
// density it clamps to 1.0 (no-op). When false, behavior is byte-identical
109+
// to the standard parametric gyroid path below.
110+
// ---------------------------------------------------------------------------
111+
static inline double compute_omega_factor(double density_adjusted, double line_spacing, double layer_height)
112+
{
113+
double lh_ratio = (line_spacing > 0.) ? layer_height / line_spacing : 0.5;
114+
double correction = 1.0 / std::sqrt(1.0 + lh_ratio);
115+
double raw = std::sqrt(1.0 / std::max(density_adjusted, 0.1)) * correction;
116+
return std::clamp(raw, 1.0, 2.0);
117+
}
118+
12119
static inline double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
13120
{
14121
if (vertical) {
@@ -169,19 +276,33 @@ void FillGyroid::_fill_surface_single(
169276
bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
170277

171278
// generate pattern
172-
Polylines polylines = make_gyroid_waves(
173-
scale_(this->z),
174-
density_adjusted,
175-
this->spacing,
176-
ceil(bb.size()(0) / distance) + 1.,
177-
ceil(bb.size()(1) / distance) + 1.);
178-
179-
// shift the polyline to the grid origin
180-
for (Polyline &pl : polylines)
181-
pl.translate(bb.min);
279+
Polylines polylines;
280+
if (params.gyroid_optimized) {
281+
// Marching-squares path on the gyroid implicit field with anisotropic Z-axis.
282+
const double lh = (params.layer_height > 0.) ? double(params.layer_height) : double(this->spacing);
283+
const double omega = compute_omega_factor(density_adjusted, this->spacing * params.multiline, lh);
284+
const float density_factor = std::max(0.001f, float(params.density * DensityAdjust / params.multiline));
285+
const float period = float(2.0 * M_PI) * float(this->spacing) / density_factor;
286+
287+
marchsq::GyroidField sf(bb, this->z, period, float(omega));
288+
polylines = marchsq::get_gyroid_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION);
289+
} else {
290+
polylines = make_gyroid_waves(
291+
scale_(this->z),
292+
density_adjusted,
293+
this->spacing,
294+
ceil(bb.size()(0) / distance) + 1.,
295+
ceil(bb.size()(1) / distance) + 1.);
296+
297+
// shift the parametric output to the grid origin; marching squares already
298+
// emits absolute coords via GyroidField::to_Point so it skips this.
299+
for (Polyline &pl : polylines)
300+
pl.translate(bb.min);
301+
}
302+
182303
// Apply multiline offset if needed
183304
multiline_fill(polylines, params, spacing);
184-
305+
185306
polylines = intersection_pl(polylines, expolygon);
186307

187308
if (! polylines.empty()) {

src/libslic3r/Preset.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -956,7 +956,7 @@ static std::vector<std::string> s_Preset_print_options {
956956
"top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall",
957957
"detect_overhang_wall", "top_color_penetration_layers", "bottom_color_penetration_layers",
958958
"infill_instead_top_bottom_surfaces",
959-
"smooth_speed_discontinuity_area","smooth_coefficient", "seam_position", "seam_placement_away_from_overhangs", "wall_sequence", "is_infill_first", "sparse_infill_density", "fill_multiline",
959+
"smooth_speed_discontinuity_area","smooth_coefficient", "seam_position", "seam_placement_away_from_overhangs", "wall_sequence", "is_infill_first", "sparse_infill_density", "fill_multiline", "gyroid_optimized",
960960
"sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max", "top_surface_pattern", "monotonic_travel_into_wall",
961961
"locked_skin_infill_pattern", "locked_skeleton_infill_pattern",
962962
"bottom_surface_pattern", "internal_solid_infill_pattern", "infill_direction", "bridge_angle", "infill_shift_step", "skeleton_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_density",

src/libslic3r/PrintConfig.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2759,6 +2759,19 @@ void PrintConfigDef::init_fff_params()
27592759
def->max = 5;
27602760
def->set_default_value(new ConfigOptionInt(1));
27612761

2762+
// Z-buckling bias optimization (experimental). Tightens the gyroid wave along the Z
2763+
// (vertical) axis at low infill density to shorten the effective column length under
2764+
// Z-axis compression. Filament use at the same `sparse_infill_density` setting is
2765+
// preserved. No effect above ~30% density (formula clamps to no-op).
2766+
def = this->add("gyroid_optimized", coBool);
2767+
def->label = L("Z-buckling bias optimization (experimental)");
2768+
def->category = L("Strength");
2769+
def->tooltip = L("Tightens the gyroid wave along the Z (vertical) axis at low infill density "
2770+
"to shorten the effective vertical column length and improve Z-axis compression "
2771+
"buckling resistance. Filament use is preserved. No effect at ~30% sparse infill "
2772+
"density and above. Only applies when Sparse infill pattern is set to Gyroid.");
2773+
def->set_default_value(new ConfigOptionBool(false));
2774+
27622775
def = this->add("sparse_infill_pattern", coEnum);
27632776
def->label = L("Sparse infill pattern");
27642777
def->category = L("Strength");

src/libslic3r/PrintConfig.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,7 @@ PRINT_CONFIG_CLASS_DEFINE(
10441044
((ConfigOptionPercent, skin_infill_density))
10451045
((ConfigOptionPercent, sparse_infill_density))
10461046
((ConfigOptionInt, fill_multiline))
1047+
((ConfigOptionBool, gyroid_optimized))
10471048
((ConfigOptionFloat, infill_lock_depth))
10481049
((ConfigOptionFloat, skin_infill_depth))
10491050
((ConfigOptionEnum<InfillPattern>, sparse_infill_pattern))

src/slic3r/GUI/ConfigManipulation.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
810810
pattern == ipAdaptiveCubic || pattern == ipSupportCubic;
811811

812812
toggle_line("fill_multiline", have_infill && support_multiline_infill);
813+
814+
// gyroid_optimized only applies when the sparse infill pattern is gyroid; hide otherwise.
815+
toggle_line("gyroid_optimized", have_infill && pattern == ipGyroid);
816+
813817
// Only allow configuration of open anchors if the anchoring is enabled.
814818
bool has_infill_anchors = have_infill && config->option<ConfigOptionFloatOrPercent>("sparse_infill_anchor_max")->value > 0;
815819
toggle_line("sparse_infill_anchor", has_infill_anchors);

src/slic3r/GUI/Tab.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3016,6 +3016,7 @@ void TabPrint::build()
30163016
optgroup->append_single_option_line("sparse_infill_density");
30173017
optgroup->append_single_option_line("fill_multiline");
30183018
optgroup->append_single_option_line("sparse_infill_pattern", "fill-patterns#infill types and their properties of sparse");
3019+
optgroup->append_single_option_line("gyroid_optimized");
30193020
optgroup->append_single_option_line("locked_skin_infill_pattern", "fill-patterns#infill types and their properties of sparse", -1, true);
30203021
optgroup->append_single_option_line("skin_infill_density", "", -1, true);
30213022
optgroup->append_single_option_line("locked_skeleton_infill_pattern", "fill-patterns#infill types and their properties of sparse", -1, true);

0 commit comments

Comments
 (0)