diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 76b3626c13..6db14ec2fd 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -78,6 +78,9 @@ struct SurfaceFillParams float lattice_angle_1 = -45.0f; float lattice_angle_2 = 45.0f; + // For Gyroid: when true, use the parameterized "optimized" variant. + bool gyroid_optimized = false; + bool operator<(const SurfaceFillParams &rhs) const { #define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false; #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 RETURN_COMPARE_NON_EQUAL(lattice_angle_2); RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, skin_pattern); RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, skeleton_pattern); + RETURN_COMPARE_NON_EQUAL(gyroid_optimized); return false; } @@ -140,7 +144,8 @@ struct SurfaceFillParams this->lattice_angle_1 == rhs.lattice_angle_1 && this->lattice_angle_2 == rhs.lattice_angle_2&& this-> skin_pattern == rhs.skin_pattern && - this-> skeleton_pattern == rhs.skeleton_pattern; + this-> skeleton_pattern == rhs.skeleton_pattern && + this-> gyroid_optimized == rhs.gyroid_optimized; } }; @@ -253,6 +258,10 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p params.pattern == ipSupportCubic; params.multiline = (params.extrusion_role == erInternalInfill && support_multiline_infill) ? int(region_config.fill_multiline) : 1; + // Pass gyroid_optimized through only when the effective pattern is Gyroid, + // so non-Gyroid fills aren't differentiated by an irrelevant flag. + params.gyroid_optimized = (params.pattern == ipGyroid) && region_config.gyroid_optimized; + // Calculate the actual flow we'll be using for this infill. params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern); params.flow = params.bridge ? @@ -710,6 +719,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.resolution = resolution; params.use_arachne = surface_fill.params.pattern == ipConcentric || surface_fill.params.pattern == ipFloatingConcentric; params.layer_height = m_regions[surface_fill.region_id]->layer()->height; + params.gyroid_optimized = surface_fill.params.gyroid_optimized; // BBS params.flow = surface_fill.params.flow; @@ -857,6 +867,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc params.resolution = resolution; params.use_arachne = false; params.layer_height = layerm.layer()->height; + params.gyroid_optimized = surface_fill.params.gyroid_optimized; // Pass pattern-specific parameters so that anchoring lines match the actual infill. if (surface_fill.params.pattern == ip2DLattice) { diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index ff23a14d82..d65798e000 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -82,6 +82,9 @@ struct FillParams // Layer height for Concentric infill with Arachne. coordf_t layer_height { 0.f }; + // For Gyroid: when true, use the parameterized "optimized" variant. + bool gyroid_optimized { false }; + InfillPattern pattern{ ipRectilinear }; // BBS diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp index 01056e9dec..972a2a6de6 100644 --- a/src/libslic3r/Fill/FillGyroid.cpp +++ b/src/libslic3r/Fill/FillGyroid.cpp @@ -1,14 +1,121 @@ #include "../ClipperUtils.hpp" +#include "../MarchingSquares.hpp" #include "../ShortestPath.hpp" #include "../Surface.hpp" #include #include #include +#include "FillBase.hpp" #include "FillGyroid.hpp" +// --------------------------------------------------------------------------- +// Marching-squares scalar field for the optimized gyroid branch. +// +// 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, giving 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 +{ + 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 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 { +// --------------------------------------------------------------------------- +// Z-buckling bias optimization: omega = sqrt(1 / density_adj) clamped [1, 2]. +// At low density (long, slender vertical strands) omega is highest; at ~30%+ +// density it clamps to 1.0 (no-op). When 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); +} + static inline double f(double x, double z_sin, double z_cos, bool vertical, bool flip) { if (vertical) { @@ -169,19 +276,33 @@ void FillGyroid::_fill_surface_single( bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance))); // generate pattern - Polylines polylines = make_gyroid_waves( - scale_(this->z), - density_adjusted, - this->spacing, - ceil(bb.size()(0) / distance) + 1., - ceil(bb.size()(1) / distance) + 1.); - - // shift the polyline to the grid origin - for (Polyline &pl : polylines) - pl.translate(bb.min); + Polylines polylines; + if (params.gyroid_optimized) { + // Marching-squares path on the gyroid implicit field with anisotropic Z-axis. + 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; + + 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, + ceil(bb.size()(0) / distance) + 1., + ceil(bb.size()(1) / distance) + 1.); + + // shift the parametric output to the grid origin; marching squares already + // emits absolute coords via GyroidField::to_Point so it skips this. + for (Polyline &pl : polylines) + pl.translate(bb.min); + } + // Apply multiline offset if needed multiline_fill(polylines, params, spacing); - + polylines = intersection_pl(polylines, expolygon); if (! polylines.empty()) { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index fa6d5962c6..776372ecb9 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -956,7 +956,7 @@ static std::vector s_Preset_print_options { "top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "top_color_penetration_layers", "bottom_color_penetration_layers", "infill_instead_top_bottom_surfaces", - "smooth_speed_discontinuity_area","smooth_coefficient", "seam_position", "seam_placement_away_from_overhangs", "wall_sequence", "is_infill_first", "sparse_infill_density", "fill_multiline", + "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", "sparse_infill_pattern", "sparse_infill_anchor", "sparse_infill_anchor_max", "top_surface_pattern", "monotonic_travel_into_wall", "locked_skin_infill_pattern", "locked_skeleton_infill_pattern", "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", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 6c5d2bce37..207957618a 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2759,6 +2759,19 @@ void PrintConfigDef::init_fff_params() def->max = 5; def->set_default_value(new ConfigOptionInt(1)); + // Z-buckling bias optimization (experimental). Tightens the gyroid wave along the Z + // (vertical) axis at low infill density to shorten the effective column length under + // Z-axis compression. Filament use at the same `sparse_infill_density` setting is + // preserved. No effect above ~30% density (formula clamps to no-op). + def = this->add("gyroid_optimized", coBool); + def->label = L("Z-buckling bias optimization (experimental)"); + def->category = L("Strength"); + def->tooltip = L("Tightens the gyroid wave along the Z (vertical) axis at low infill density " + "to shorten the effective vertical column length and improve Z-axis compression " + "buckling resistance. Filament use is preserved. No effect at ~30% sparse infill " + "density and above. Only applies when Sparse infill pattern is set to Gyroid."); + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("sparse_infill_pattern", coEnum); def->label = L("Sparse infill pattern"); def->category = L("Strength"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 6793118a2e..7df408fa28 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1044,6 +1044,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionPercent, skin_infill_density)) ((ConfigOptionPercent, sparse_infill_density)) ((ConfigOptionInt, fill_multiline)) + ((ConfigOptionBool, gyroid_optimized)) ((ConfigOptionFloat, infill_lock_depth)) ((ConfigOptionFloat, skin_infill_depth)) ((ConfigOptionEnum, sparse_infill_pattern)) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index f991c30df9..7a29cd98e5 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -810,6 +810,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in pattern == ipAdaptiveCubic || pattern == ipSupportCubic; toggle_line("fill_multiline", have_infill && support_multiline_infill); + + // gyroid_optimized only applies when the sparse infill pattern is gyroid; hide otherwise. + toggle_line("gyroid_optimized", have_infill && pattern == ipGyroid); + // Only allow configuration of open anchors if the anchoring is enabled. bool has_infill_anchors = have_infill && config->option("sparse_infill_anchor_max")->value > 0; toggle_line("sparse_infill_anchor", has_infill_anchors); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 3e1f3ae506..ec0ce6707a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3016,6 +3016,7 @@ void TabPrint::build() optgroup->append_single_option_line("sparse_infill_density"); optgroup->append_single_option_line("fill_multiline"); optgroup->append_single_option_line("sparse_infill_pattern", "fill-patterns#infill types and their properties of sparse"); + optgroup->append_single_option_line("gyroid_optimized"); optgroup->append_single_option_line("locked_skin_infill_pattern", "fill-patterns#infill types and their properties of sparse", -1, true); optgroup->append_single_option_line("skin_infill_density", "", -1, true); optgroup->append_single_option_line("locked_skeleton_infill_pattern", "fill-patterns#infill types and their properties of sparse", -1, true);