Merge branch 'main' into perf/slicing-optimizations

This commit is contained in:
SoftFever committed 2026-10-10 01:09:46 +08:00
commit f6e8821b13
47 files changed
+2419 -59

No files matched your search

+2
View File
@@ -180,6 +180,8 @@ set(lisbslic3r_sources
Fill/FillPlanePath.hpp
Fill/FillRectilinear.cpp
Fill/FillRectilinear.hpp
Fill/FillTpmsAdaptive.cpp
Fill/FillTpmsAdaptive.hpp
Fill/FillTpmsD.cpp
Fill/FillTpmsD.hpp
Fill/FillTpmsFK.cpp
+33 -6
View File
@@ -313,6 +313,11 @@ struct SurfaceFillParams
// For Gyroid: when true, use the parameterized "optimized" wave.
bool gyroid_optimized = false;
// For TPMS: grade the density from the surface to the interior of the object.
TpmsAdaptiveMode tpms_adaptive = TpmsAdaptiveMode::Disabled;
float tpms_interior_density = 0.f;
TpmsAdaptiveGradient tpms_adaptive_gradient = TpmsAdaptiveGradient::Linear;
// Orca: corner smoothing factor in the range [0, 1].
double smooth_factor { 0. };
@@ -355,6 +360,9 @@ struct SurfaceFillParams
RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
RETURN_COMPARE_NON_EQUAL(gyroid_optimized);
RETURN_COMPARE_NON_EQUAL(tpms_adaptive);
RETURN_COMPARE_NON_EQUAL(tpms_interior_density);
RETURN_COMPARE_NON_EQUAL(tpms_adaptive_gradient);
RETURN_COMPARE_NON_EQUAL(smooth_factor);
RETURN_COMPARE_NON_EQUAL(center_of_surface_pattern);
RETURN_COMPARE_NON_EQUAL(separated_infills);
@@ -388,6 +396,9 @@ struct SurfaceFillParams
this->center_of_surface_pattern == rhs.center_of_surface_pattern &&
this->separated_infills == rhs.separated_infills &&
this->gyroid_optimized == rhs.gyroid_optimized &&
this->tpms_adaptive == rhs.tpms_adaptive &&
this->tpms_interior_density == rhs.tpms_interior_density &&
this->tpms_adaptive_gradient == rhs.tpms_adaptive_gradient &&
this->smooth_factor == rhs.smooth_factor &&
this->fill_order == rhs.fill_order;
}
@@ -1005,15 +1016,23 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
// Orca: apply fill multiline only for sparse infill
params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1;
// Pass through gyroid_optimized only when the effective pattern is Gyroid,
// so non-Gyroid fills do not differ in SurfaceFillParams by an irrelevant flag
// (which would unnecessarily split fill batching).
// Stored on SurfaceFillParams; copied to FillParams during conversion.
params.gyroid_optimized = (params.pattern == ipGyroid) && region_config.gyroid_optimized;
// Orca: Likewise separated_infills only where it can move the pattern.
// Orca: Pass through separated_infills only where it can move the pattern.
params.separated_infills = region_config.separated_infills && is_separable_infill_pattern(params.pattern) &&
params.extrusion_role != erTopSolidInfill && params.extrusion_role != erBottomSurface;
// Orca: only the TPMS sparse infill is graded; reset otherwise, as params is reused.
params.tpms_adaptive = is_tpms_adaptive_pattern(params.pattern) && params.extrusion_role == erInternalInfill ?
region_config.tpms_adaptive.value : TpmsAdaptiveMode::Disabled;
const bool tpms_adaptive = params.tpms_adaptive != TpmsAdaptiveMode::Disabled;
params.tpms_interior_density = tpms_adaptive ? std::max(1.f, float(region_config.tpms_interior_density)) : 0.f;
params.tpms_adaptive_gradient = tpms_adaptive ? region_config.tpms_adaptive_gradient.value : TpmsAdaptiveGradient::Linear;
// Pass through gyroid_optimized only when the effective pattern is Gyroid,
// so non-Gyroid fills do not differ in SurfaceFillParams by an irrelevant flag
// (which would unnecessarily split fill batching). Adaptive density replaces it.
// Stored on SurfaceFillParams; copied to FillParams during conversion.
params.gyroid_optimized = (params.pattern == ipGyroid) && region_config.gyroid_optimized && !tpms_adaptive;
if (params.extrusion_role == erInternalInfill) {
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value,
region_config.sparse_infill_rotate_template.value);
@@ -1365,6 +1384,7 @@ void Layer::make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLigh
f->angle = surface_fill.params.angle;
f->fixed_angle = surface_fill.params.fixed_angle;
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
f->tpms_radial_field = this->object()->tpms_radial_field(surface_fill.params.tpms_adaptive);
f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config();
if (surface_fill.params.pattern == ipConcentricInternal) {
@@ -1414,6 +1434,9 @@ void Layer::make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLigh
params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2;
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
params.tpms_adaptive = surface_fill.params.tpms_adaptive;
params.tpms_interior_density = float(0.01 * surface_fill.params.tpms_interior_density);
params.tpms_adaptive_gradient = surface_fill.params.tpms_adaptive_gradient;
params.smooth_factor = surface_fill.params.smooth_factor;
// BBS
@@ -1594,6 +1617,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdapti
f->angle = surface_fill.params.angle;
f->fixed_angle = surface_fill.params.fixed_angle;
const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr;
f->tpms_radial_field = this->object()->tpms_radial_field(surface_fill.params.tpms_adaptive);
f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config();
@@ -1633,6 +1657,9 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdapti
params.infill_overhang_angle = surface_fill.params.infill_overhang_angle;
params.multiline = surface_fill.params.multiline;
params.gyroid_optimized = surface_fill.params.gyroid_optimized;
params.tpms_adaptive = surface_fill.params.tpms_adaptive;
params.tpms_interior_density = float(0.01 * surface_fill.params.tpms_interior_density);
params.tpms_adaptive_gradient = surface_fill.params.tpms_adaptive_gradient;
params.smooth_factor = surface_fill.params.smooth_factor;
// Orca: Match make_fills() when choosing the origin of plane-path patterns.
// Without the sparse extrusion role, the filler uses each surface's bounds
+9
View File
@@ -33,6 +33,7 @@ namespace Slic3r { class ExtrusionEntityCollection; }
namespace Slic3r {
class Surface;
class TpmsRadialField;
enum InfillPattern : int;
namespace FillAdaptive {
@@ -91,6 +92,11 @@ struct FillParams
// For Gyroid: when true, use the parameterized "optimized" variant.
bool gyroid_optimized { false };
// For TPMS: grade the density from the surface to the interior of the object. Density fraction.
TpmsAdaptiveMode tpms_adaptive { TpmsAdaptiveMode::Disabled };
float tpms_interior_density { 0.f };
TpmsAdaptiveGradient tpms_adaptive_gradient { TpmsAdaptiveGradient::Linear };
// Orca: corner smoothing factor in the range [0, 1].
double smooth_factor { 0. };
@@ -155,6 +161,9 @@ public:
// Octree builds on mesh for usage in the adaptive cubic infill
FillAdaptive::Octree* adapt_fill_octree = nullptr;
// Radial coordinate inside the object for the adaptive TPMS infill
const TpmsRadialField* tpms_radial_field = nullptr;
// PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring).
// Orca: also used by gap fill function.
const PrintConfig *print_config = nullptr;
+26 -5
View File
@@ -17,6 +17,17 @@
#include "libslic3r/Polyline.hpp"
#include "FillGyroid.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "FillTpmsAdaptive.hpp"
namespace Slic3r {
static float gyroid(float x, float y, float z)
{
return std::sin(x) * std::cos(y) + std::sin(y) * std::cos(z) + std::sin(z) * std::cos(x);
}
} // namespace Slic3r
// ---------------------------------------------------------------------------
// Marching-squares scalar field for the optimized gyroid branch.
@@ -62,10 +73,7 @@ struct GyroidField
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);
return gyroid(fx * float(x), fy * float(y), fz * float(z_arg));
}
float get_scalar(Coord p) const
@@ -307,6 +315,14 @@ void FillGyroid::_fill_surface_single(
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);
@@ -326,7 +342,12 @@ void FillGyroid::_fill_surface_single(
// generate pattern
Polylines polylines;
if (params.gyroid_optimized) {
if (params.tpms_adaptive != TpmsAdaptiveMode::Disabled && this->tpms_radial_field != nullptr) {
// Radians per mm of the regular pattern at a density.
auto frequency = [&params, this](double density) { return density * DensityAdjust / (params.multiline * this->spacing); };
polylines = make_adaptive_tpms({gyroid, frequency(params.density), frequency(params.tpms_interior_density), params.tpms_adaptive_gradient},
*this->tpms_radial_field, bb, this->z, params.layer_height, this->spacing, infill_angle);
} else 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 ->
+823
View File
@@ -0,0 +1,823 @@
#include "FillTpmsAdaptive.hpp"
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <deque>
#include <functional>
#include <limits>
#include <utility>
#include <vector>
#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
#include "../BoundingBox.hpp"
#include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp"
#include "FillBase.hpp"
#include "../Execution/ExecutionTBB.hpp"
#include "../MarchingSquares.hpp"
#include "../Point.hpp"
#include "../Polygon.hpp"
#include "../Polyline.hpp"
#include "../PrintConfig.hpp"
#include "../libslic3r.h"
namespace Slic3r {
namespace {
// At most 4 MB of body indices; finer cells would not change the grading.
constexpr double MaxNodes = double(1 << 20);
constexpr double MinCellSize = 0.5;
// Two deepest points are in separate lobes when the depth between them drops below this ratio of the shallower one.
constexpr double NeckRatio = 0.8;
// Lobes shallower than this ratio of the deepest one of their body are graded as part of it.
constexpr double MinLobeRatio = 0.3;
// A lobe reaches twice as far as the side towards its neighbour, so that the side is half way to the surface.
constexpr double LobeReach = 2.;
// Width of the morph between the patterns of two lobes, in their distance to the center over its depth.
constexpr double LobeMorph = 0.1;
// Densities of the levels of Stepped shells, and of Smooth blend, are at most these ratios apart. Fewer levels blend
// with fewer lines running along the blends.
constexpr double ShellRatio = 1.5;
constexpr double BlendRatio = 2.5;
// Distance warp: samples of the mean depth along a ray.
constexpr int Samples = 32;
constexpr float InfF = std::numeric_limits<float>::infinity();
constexpr double InfD = std::numeric_limits<double>::infinity();
// Squared distance transform of a line (Felzenszwalb & Huttenlocher); infinite samples are no sites.
void distance_transform_line(const float *f, float *d, int n, int *v, double *s)
{
int k = -1;
for (int q = 0; q < n; ++q) {
if (f[q] == InfF)
continue;
double x = -InfD;
while (k >= 0) {
x = (f[q] + double(q) * q - f[v[k]] - double(v[k]) * v[k]) / (2. * (q - v[k]));
if (x > s[k])
break;
--k;
}
if (k < 0)
x = -InfD;
v[++k] = q;
s[k] = x;
s[k + 1] = InfD;
}
if (k < 0) {
std::fill(d, d + n, InfF);
return;
}
for (int q = 0, j = 0; q < n; ++q) {
while (s[j + 1] < q)
++j;
d[q] = float(sqr(double(q - v[j])) + f[v[j]]);
}
}
void distance_transform_axis(std::vector<float> &grid, const Vec3i32 &size, int axis, const std::function<void()> &throw_if_canceled)
{
const int n = size[axis];
const int a1 = (axis + 1) % 3;
const int a2 = (axis + 2) % 3;
const size_t stride = axis == 0 ? 1 : axis == 1 ? size_t(size.x()) : size_t(size.x()) * size.y();
tbb::parallel_for(tbb::blocked_range<size_t>(0, size_t(size[a1]) * size[a2]), [&](const tbb::blocked_range<size_t> &range) {
std::vector<float> f(n), d(n);
std::vector<int> v(n);
std::vector<double> s(n + 1);
for (size_t line = range.begin(); line < range.end(); ++line) {
Vec3i32 idx;
idx[axis] = 0;
idx[a1] = int(line % size[a1]);
idx[a2] = int(line / size[a1]);
const size_t first = (size_t(idx.z()) * size.y() + idx.y()) * size.x() + idx.x();
for (int i = 0; i < n; ++i)
f[i] = grid[first + i * stride];
distance_transform_line(f.data(), d.data(), n, v.data(), s.data());
for (int i = 0; i < n; ++i)
grid[first + i * stride] = d[i];
}
throw_if_canceled();
});
}
// Marks the nodes inside the expolygons with infinity, by even-odd scanlines.
void rasterize(const ExPolygons &expolygons, const Vec2d &origin, double cell, int nx, int ny, float *nodes)
{
std::vector<std::vector<double>> crossings(ny);
auto add_crossings = [&](const Polygon &polygon) {
const Points &pts = polygon.points;
for (size_t i = 0; i < pts.size(); ++i) {
const Vec2d a = unscaled(pts[i]);
const Vec2d b = unscaled(pts[i + 1 == pts.size() ? 0 : i + 1]);
if (a.y() == b.y())
continue;
const auto [lo, hi] = std::minmax(a.y(), b.y());
const int j0 = std::max(0, int(std::ceil((lo - origin.y()) / cell)));
const int j1 = std::min(ny, int(std::ceil((hi - origin.y()) / cell)));
for (int j = j0; j < j1; ++j) {
const double y = origin.y() + j * cell;
crossings[j].push_back(a.x() + (b.x() - a.x()) * (y - a.y()) / (b.y() - a.y()));
}
}
};
for (const ExPolygon &expolygon : expolygons) {
add_crossings(expolygon.contour);
for (const Polygon &hole : expolygon.holes)
add_crossings(hole);
}
for (int j = 0; j < ny; ++j) {
std::vector<double> &xs = crossings[j];
std::sort(xs.begin(), xs.end());
for (size_t k = 0; k + 1 < xs.size(); k += 2) {
const int i0 = std::max(0, int(std::ceil((xs[k] - origin.x()) / cell)));
const int i1 = std::min(nx, int(std::ceil((xs[k + 1] - origin.x()) / cell)));
std::fill(nodes + size_t(j) * nx + std::min(i0, i1), nodes + size_t(j) * nx + i1, InfF);
}
}
}
// Scale of the pattern relative to the surface at a depth from 0 at the surface to 1 at the deepest point.
double target_scale(double ratio, TpmsAdaptiveGradient gradient, double depth)
{
switch (gradient) {
case TpmsAdaptiveGradient::Quadratic: return 1. + (ratio - 1.) * depth * depth;
case TpmsAdaptiveGradient::Exponential: return std::pow(ratio, depth);
default: return 1. + (ratio - 1.) * depth;
}
}
// Levels of Stepped shells and Smooth blend, geometric from the surface at 0 to the interior at count, and the
// continuous level of the target of a depth.
struct DensityLevels
{
DensityLevels(double ratio, double step, TpmsAdaptiveGradient gradient)
: ratio(ratio), gradient(gradient), count(int(std::ceil(std::abs(std::log(ratio)) / std::log(step) - EPSILON)))
{}
double scale(int level) const { return count == 0 ? 1. : std::pow(ratio, double(level) / count); }
double level(double depth) const
{
return count == 0 ? 0. : count * std::log(target_scale(ratio, gradient, depth)) / std::log(ratio);
}
double ratio;
TpmsAdaptiveGradient gradient;
int count;
};
// Scale of the pattern around the center at a radial coordinate t. The mean cell scale over the ball of radius t,
// t^-3 * integral of 3 t'^2 * target(t'), or over the disc in 2D, follows the gradient; beyond the surface the target
// is the surface scale.
class RadialScale
{
public:
RadialScale(const AdaptiveTpms &tpms, int dimensions) : m_dimensions(dimensions)
{
const double ratio = std::max(tpms.interior_frequency / tpms.surface_frequency, 1e-3);
auto target = [&tpms, ratio](double depth) { return target_scale(ratio, tpms.gradient, depth); };
m_scale[0] = target(1.);
double volume = 0.;
for (size_t i = 1; i < m_scale.size(); ++i) {
const double t0 = double(i - 1) / double(m_scale.size() - 1);
const double t1 = double(i) / double(m_scale.size() - 1);
volume += (std::pow(t1, m_dimensions) - std::pow(t0, m_dimensions)) * target(1. - 0.5 * (t0 + t1));
m_scale[i] = volume / std::pow(t1, m_dimensions);
}
}
double operator()(double t) const
{
if (t >= 1.) {
const double volume = std::pow(t, m_dimensions);
return (m_scale.back() + volume - 1.) / volume;
}
const double x = t * double(m_scale.size() - 1);
const size_t i = std::min(size_t(x), m_scale.size() - 2);
return m_scale[i] + (m_scale[i + 1] - m_scale[i]) * (x - double(i));
}
private:
int m_dimensions;
std::array<double, 257> m_scale;
};
} // namespace
TpmsRadialField::TpmsRadialField(const std::vector<Slice> &slices, const BoundingBox &bbox, TpmsAdaptiveMode mode,
const std::function<void()> &throw_if_canceled)
: m_mode(mode)
, m_axis(mode == TpmsAdaptiveMode::NormalX ? 0 : mode == TpmsAdaptiveMode::NormalY ? 1 : mode == TpmsAdaptiveMode::NormalZ ? 2 : -1)
{
assert(!slices.empty() && mode != TpmsAdaptiveMode::Disabled);
const Vec3d min(unscaled(bbox.min.x()), unscaled(bbox.min.y()), slices.front().bottom_z);
const Vec3d extent = Vec3d(unscaled(bbox.max.x()), unscaled(bbox.max.y()), slices.back().top_z) - min;
// Padded by a node on each side, so that the border of the grid is outside.
m_cell = std::max(MinCellSize, std::cbrt(extent.prod() / MaxNodes));
auto nodes = [this](double length) { return int(std::ceil(length / m_cell)) + 3; };
while (double(nodes(extent.x())) * nodes(extent.y()) * nodes(extent.z()) > MaxNodes)
m_cell *= 1.1;
m_size = Vec3i32(nodes(extent.x()), nodes(extent.y()), nodes(extent.z()));
m_origin = min - Vec3d::Constant(m_cell);
const size_t sy = size_t(m_size.x());
const size_t sz = sy * m_size.y();
std::vector<float> depth(sz * m_size.z(), 0.f);
tbb::parallel_for(tbb::blocked_range<int>(0, m_size.z()), [&](const tbb::blocked_range<int> &range) {
for (int k = range.begin(); k < range.end(); ++k) {
const double z = m_origin.z() + k * m_cell;
auto it = std::lower_bound(slices.begin(), slices.end(), z, [](const Slice &s, double z) { return s.top_z < z; });
if (it != slices.end() && z > it->bottom_z)
rasterize(*it->expolygons, m_origin.head<2>(), m_cell, m_size.x(), m_size.y(), depth.data() + k * sz);
}
throw_if_canceled();
});
for (int axis = 0; axis < 3; ++axis)
if (axis != m_axis)
distance_transform_axis(depth, m_size, axis, throw_if_canceled);
auto position = [this, sy, sz](size_t i) {
return Vec3d(m_origin + m_cell * Vec3d(double(i % sy), double(i / sy % m_size.y()), double(i / sz)));
};
const std::array<std::ptrdiff_t, 6> steps{1, -1, std::ptrdiff_t(sy), -std::ptrdiff_t(sy), std::ptrdiff_t(sz), -std::ptrdiff_t(sz)};
auto node_of = [this, sy, sz](const Vec3d &pt) -> std::ptrdiff_t {
const Vec3d f = (pt - m_origin) / m_cell;
const long x = std::lround(f.x()), y = std::lround(f.y()), z = std::lround(f.z());
if (x < 0 || y < 0 || z < 0 || x >= m_size.x() || y >= m_size.y() || z >= m_size.z())
return -1;
return std::ptrdiff_t(size_t(z) * sz + size_t(y) * sy + size_t(x));
};
// In the 2D modes, the steps within a section.
auto in_section = [this](size_t step) { return int(step / 2) != m_axis; };
std::vector<std::ptrdiff_t> neighbours;
for (int dz = -1; dz <= 1; ++dz)
for (int dy = -1; dy <= 1; ++dy)
for (int dx = -1; dx <= 1; ++dx)
if ((dx != 0 || dy != 0 || dz != 0) && (m_axis < 0 || Vec3i32(dx, dy, dz)[m_axis] == 0))
neighbours.push_back(std::ptrdiff_t(dz) * std::ptrdiff_t(sz) + std::ptrdiff_t(dy) * std::ptrdiff_t(sy) + dx);
// Bodies are the connected inside nodes, none of which is on the border. The deepest nodes of a body are the
// centers of its lobes, unless the depth between them stays above NeckRatio; where the depth ties, the center
// is the node nearest to the middle of the tied nodes.
const bool lobes = m_mode != TpmsAdaptiveMode::SteppedShells && m_mode != TpmsAdaptiveMode::SmoothBlend;
m_body.assign(depth.size(), -1);
std::vector<size_t> body_nodes;
for (size_t seed = 0; seed < depth.size(); ++seed) {
if (depth[seed] == 0.f || m_body[seed] >= 0)
continue;
const int id = int(m_bodies.size());
body_nodes.assign(1, seed);
m_body[seed] = id;
float max_depth = 0.f;
for (size_t k = 0; k < body_nodes.size(); ++k) {
max_depth = std::max(max_depth, depth[body_nodes[k]]);
for (size_t s = 0; s < steps.size(); ++s)
if (const size_t j = body_nodes[k] + steps[s]; in_section(s) && depth[j] > 0.f && m_body[j] < 0) {
m_body[j] = id;
body_nodes.push_back(j);
}
}
m_bodies.push_back({m_lobes.size(), 0, (std::sqrt(double(max_depth)) - 0.5) * m_cell});
if (!lobes)
continue;
std::vector<size_t> peaks;
for (size_t i : body_nodes)
if (depth[i] >= sqr(MinLobeRatio) * max_depth &&
std::all_of(neighbours.begin(), neighbours.end(), [&](std::ptrdiff_t n) { return depth[i + n] <= depth[i]; }))
peaks.push_back(i);
std::sort(peaks.begin(), peaks.end(), [&depth](size_t a, size_t b) { return depth[a] > depth[b] || (depth[a] == depth[b] && a < b); });
auto necked = [&](size_t a, size_t b) {
const Vec3d pa = position(a), pb = position(b);
const double limit = sqr(NeckRatio) * std::min(depth[a], depth[b]);
const int samples = int(std::ceil((pb - pa).norm() / (0.5 * m_cell)));
for (int s = 1; s < samples; ++s)
if (depth[node_of(pa + (pb - pa) * (double(s) / samples))] < limit)
return true;
return false;
};
// A peak joins the first lobe it sees without a neck, if it is as deep. The lobes are made one at a time,
// from the first remaining peak, testing the others in parallel.
std::vector<std::vector<size_t>> ties;
while (!peaks.empty()) {
const size_t front = peaks.front();
std::vector<char> joins(peaks.size(), 0);
tbb::parallel_for(tbb::blocked_range<size_t>(1, peaks.size()), [&](const tbb::blocked_range<size_t> &range) {
for (size_t k = range.begin(); k < range.end(); ++k)
if (!necked(front, peaks[k]))
joins[k] = std::sqrt(depth[peaks[k]]) >= std::sqrt(depth[front]) - 1.f ? 1 : 2;
});
std::vector<size_t> &tied = ties.emplace_back(1, front);
std::vector<size_t> remaining;
for (size_t k = 1; k < peaks.size(); ++k)
if (joins[k] == 0)
remaining.push_back(peaks[k]);
else if (joins[k] == 1)
tied.push_back(peaks[k]);
peaks = std::move(remaining);
}
m_bodies.back().lobes = ties.size();
for (const std::vector<size_t> &tied : ties) {
Vec3d middle(0., 0., 0.);
for (size_t i : tied)
middle += position(i);
middle /= double(tied.size());
Vec3d center = position(tied.front());
for (size_t i : tied)
if ((position(i) - middle).squaredNorm() < (center - middle).squaredNorm())
center = position(i);
m_lobes.push_back({center, (std::sqrt(double(depth[tied.front()])) - 0.5) * m_cell, {}});
}
}
throw_if_canceled();
if (m_mode == TpmsAdaptiveMode::SteppedShells || m_mode == TpmsAdaptiveMode::SmoothBlend || m_mode == TpmsAdaptiveMode::DistanceWarp) {
m_depth.assign(depth.size(), 0.f);
for (size_t i = 0; i < depth.size(); ++i)
if (m_body[i] >= 0)
m_depth[i] = float(std::min(1., std::max(0., std::sqrt(double(depth[i])) - 0.5) * m_cell / m_bodies[m_body[i]].depth));
}
// The reach of a lobe is the first exit along each direction from its center, smoothed over the directions.
// It is shortened towards a neighbouring lobe, from where the point is nearer to the other lobe relative to their depths.
std::vector<int> lobe_body(m_lobes.size());
for (size_t id = 0; id < m_bodies.size(); ++id)
std::fill_n(lobe_body.begin() + m_bodies[id].first_lobe, m_bodies[id].lobes, int(id));
const int rows = this->directions() / Azimuth;
auto direction = [this](int i, int j) {
const double azimuth = j * 2. * PI / Azimuth;
Vec3d dir = Vec3d::Zero();
if (m_axis < 0) {
const double polar = (i + 0.5) * PI / Polar;
dir = Vec3d(std::sin(polar) * std::cos(azimuth), std::sin(polar) * std::sin(azimuth), std::cos(polar));
} else {
dir[(m_axis + 1) % 3] = std::cos(azimuth);
dir[(m_axis + 2) % 3] = std::sin(azimuth);
}
return dir;
};
// Two passes of a box filter over the neighbouring directions, for each of the values of a direction.
auto smooth = [rows](std::vector<double> &values, size_t count) {
for (int pass = 0; pass < 2; ++pass) {
std::vector<double> smoothed(values.size(), 0.);
for (int i = 0; i < rows; ++i)
for (int j = 0; j < Azimuth; ++j)
for (size_t k = 0; k < count; ++k) {
double &sum = smoothed[size_t(i * Azimuth + j) * count + k];
for (int di = -1; di <= 1; ++di)
for (int dj = -1; dj <= 1; ++dj)
sum += values[size_t(std::clamp(i + di, 0, rows - 1) * Azimuth + (j + dj + Azimuth) % Azimuth) * count + k];
sum /= 9.;
}
values = std::move(smoothed);
}
};
tbb::parallel_for(tbb::blocked_range<size_t>(0, m_lobes.size()), [&](const tbb::blocked_range<size_t> &range) {
for (size_t l = range.begin(); l < range.end(); ++l) {
const int id = lobe_body[l];
const Body &body = m_bodies[id];
Lobe &lobe = m_lobes[l];
// The other lobes of the body, by the distance from the center beyond which they may be nearer.
std::vector<std::pair<double, size_t>> others;
for (size_t k = body.first_lobe; k < body.first_lobe + body.lobes; ++k)
if (k != l)
others.emplace_back((m_lobes[k].center - lobe.center).norm() / (1. + m_lobes[k].depth / lobe.depth), k);
std::sort(others.begin(), others.end());
auto nearer_lobe = [&](const Vec3d &pt, double r) {
for (auto it = others.begin(); it != others.end() && it->first < r; ++it)
if ((pt - m_lobes[it->second].center).norm() / m_lobes[it->second].depth < r / lobe.depth)
return true;
return false;
};
const double step = 0.5 * m_cell;
std::vector<double> log_reach(this->directions());
for (int i = 0; i < rows; ++i)
for (int j = 0; j < Azimuth; ++j) {
const Vec3d dir = direction(i, j);
double r = 0.;
double limit = InfD;
for (;;) {
const Vec3d pt = lobe.center + (r + step) * dir;
const std::ptrdiff_t n = node_of(pt);
if (n < 0 || depth[n] == 0.f || m_body[n] != id || r + step >= limit)
break;
if (limit == InfD && nearer_lobe(pt, r + step))
limit = LobeReach * (r + step);
r += step;
}
log_reach[i * Azimuth + j] = std::log(std::min(r + 0.5 * step, limit));
}
smooth(log_reach, 1);
lobe.reach.resize(log_reach.size());
std::transform(log_reach.begin(), log_reach.end(), lobe.reach.begin(), [](double v) { return float(std::exp(v)); });
if (m_mode == TpmsAdaptiveMode::DistanceWarp) {
std::vector<double> mean(log_reach.size() * (Samples + 1));
for (size_t d = 0; d < log_reach.size(); ++d) {
const Vec3d dir = direction(int(d) / Azimuth, int(d) % Azimuth);
double integral = 0.;
double previous = this->depth(lobe.center);
mean[d * (Samples + 1)] = previous;
for (int k = 1; k <= Samples; ++k) {
// Exact for a depth linear between the samples, a + b * tau, weighted by tau^2.
const double t0 = double(k - 1) / Samples;
const double t1 = double(k) / Samples;
const double d1 = this->depth(lobe.center + t1 * lobe.reach[d] * dir);
const double b = (d1 - previous) * Samples;
const double a = previous - b * t0;
integral += a * (std::pow(t1, 3) - std::pow(t0, 3)) / 3. + b * (std::pow(t1, 4) - std::pow(t0, 4)) / 4.;
previous = d1;
mean[d * (Samples + 1) + k] = 3. * integral / std::pow(t1, 3);
}
}
// Smoothed like the reach: sharper profiles shear the pattern across the layer, adding lines.
smooth(mean, Samples + 1);
lobe.mean_depth.assign(mean.begin(), mean.end());
}
}
throw_if_canceled();
});
// Every other node belongs to its nearest body, within its section in the 2D modes.
if (m_axis >= 0) {
std::vector<bool> has_body(m_size[m_axis], false);
for (size_t i = 0; i < m_body.size(); ++i)
if (m_body[i] >= 0)
has_body[m_axis == 0 ? i % sy : m_axis == 1 ? i / sy % m_size.y() : i / sz] = true;
m_section.assign(m_size[m_axis], -1);
for (int k = 0; k < m_size[m_axis]; ++k)
for (int d = 0; d < m_size[m_axis] && m_section[k] < 0; ++d)
if (k - d >= 0 && has_body[k - d])
m_section[k] = k - d;
else if (k + d < m_size[m_axis] && has_body[k + d])
m_section[k] = k + d;
}
// Stepped shells and Smooth blend only look up the depth.
if (!lobes) {
m_body = {};
return;
}
if (m_bodies.size() == 1) {
std::fill(m_body.begin(), m_body.end(), 0);
return;
}
std::deque<size_t> queue;
for (size_t i = 0; i < m_body.size(); ++i)
if (m_body[i] >= 0)
queue.push_back(i);
while (!queue.empty()) {
const size_t i = queue.front();
queue.pop_front();
const size_t x = i % sy, y = i / sy % m_size.y(), z = i / sz;
const std::array<bool, 6> valid{x + 1 < sy, x > 0, y + 1 < size_t(m_size.y()), y > 0, z + 1 < size_t(m_size.z()), z > 0};
for (size_t k = 0; k < steps.size(); ++k)
if (valid[k] && in_section(k) && m_body[i + steps[k]] < 0) {
m_body[i + steps[k]] = m_body[i];
queue.push_back(i + steps[k]);
}
}
}
double TpmsRadialField::depth(const Vec3d &pt) const
{
assert(!m_depth.empty());
const Vec3d f = (pt - m_origin) / m_cell;
std::array<int, 3> n0;
std::array<double, 3> w;
for (int a = 0; a < 3; ++a) {
n0[a] = std::clamp(int(std::floor(f[a])), 0, m_size[a] - 2);
w[a] = std::clamp(f[a] - n0[a], 0., 1.);
}
const size_t sy = size_t(m_size.x());
const size_t sz = sy * size_t(m_size.y());
double value = 0.;
for (int c = 0; c < 8; ++c) {
const size_t n = size_t(n0[2] + (c >> 2)) * sz + size_t(n0[1] + (c >> 1 & 1)) * sy + size_t(n0[0] + (c & 1));
value += (c & 1 ? w[0] : 1. - w[0]) * (c >> 1 & 1 ? w[1] : 1. - w[1]) * (c >> 2 ? w[2] : 1. - w[2]) * m_depth[n];
}
return value;
}
Vec3d TpmsRadialField::offset(const Vec3d &pt, const Vec3d &center) const
{
Vec3d d = pt - center;
if (m_axis >= 0)
d[m_axis] = 0.;
return d;
}
double TpmsRadialField::radial(const Lobe &lobe, const Vec3d &pt) const
{
const Vec3d d = this->offset(pt, lobe.center);
const double r = d.norm();
// Distance warp: the radial coordinate of the linear profile with the same mean depth, which is 1 - 3/4 of it.
auto warp = [](double mean_depth) { return std::max(0., 4. / 3. * (1. - mean_depth)); };
if (r < EPSILON)
return lobe.mean_depth.empty() ? 0. : warp(lobe.mean_depth.front());
int i = 0;
double fi = 0.;
double azimuth;
if (m_axis < 0) {
const double polar = std::clamp(std::acos(std::clamp(d.z() / r, -1., 1.)) / PI * Polar - 0.5, 0., double(Polar - 1));
i = std::min(int(polar), Polar - 2);
fi = polar - i;
azimuth = std::atan2(d.y(), d.x());
} else
azimuth = std::atan2(d[(m_axis + 2) % 3], d[(m_axis + 1) % 3]);
azimuth *= Azimuth / (2. * PI);
if (azimuth < 0.)
azimuth += Azimuth;
const int j0 = int(azimuth) % Azimuth;
const int j1 = (j0 + 1) % Azimuth;
const double fj = azimuth - std::floor(azimuth);
auto at = [&lobe](int i, int j) { return double(lobe.reach[i * Azimuth + j]); };
double reach = at(i, j0) * (1. - fj) + at(i, j1) * fj;
if (fi > 0.)
reach = reach * (1. - fi) + (at(i + 1, j0) * (1. - fj) + at(i + 1, j1) * fj) * fi;
const double tau = r / reach;
if (lobe.mean_depth.empty())
return tau;
// Beyond the surface, the depth is zero.
auto mean_at = [&lobe, tau](int i, int j) {
const float *mean = lobe.mean_depth.data() + size_t(i * Azimuth + j) * (Samples + 1);
if (tau >= 1.)
return double(mean[Samples]) / (tau * tau * tau);
const double x = tau * Samples;
const int k = std::min(int(x), Samples - 1);
return mean[k] + (mean[k + 1] - mean[k]) * (x - k);
};
double mean = mean_at(i, j0) * (1. - fj) + mean_at(i, j1) * fj;
if (fi > 0.)
mean = mean * (1. - fi) + (mean_at(i + 1, j0) * (1. - fj) + mean_at(i + 1, j1) * fj) * fi;
return warp(mean);
}
size_t TpmsRadialField::radial(const Vec3d &pt, Radials &out) const
{
assert(!this->empty());
Vec3i32 idx;
for (int axis = 0; axis < 3; ++axis)
idx[axis] = std::clamp<int>(int(std::lround((pt[axis] - m_origin[axis]) / m_cell)), 0, m_size[axis] - 1);
auto node = [this](const Vec3i32 &idx) { return (size_t(idx.z()) * m_size.y() + idx.y()) * m_size.x() + idx.x(); };
if (m_axis < 0)
return this->body_radial(node(idx), pt, 1.f, out.data());
// The sections around pt, or the nearest ones with a body.
const double f = std::clamp((pt[m_axis] - m_origin[m_axis]) / m_cell, 0., double(m_size[m_axis] - 1));
const int k = std::min(int(f), m_size[m_axis] - 2);
const float w = float(f - k);
size_t count = 0;
if (w < 1.f) {
idx[m_axis] = m_section[k];
count += this->body_radial(node(idx), pt, 1.f - w, out.data());
}
if (w > 0.f) {
idx[m_axis] = m_section[k + 1];
count += this->body_radial(node(idx), pt, w, out.data() + count);
}
return count;
}
size_t TpmsRadialField::body_radial(size_t node, const Vec3d &pt, float weight, Radial *out) const
{
const Body &body = m_bodies[m_body[node]];
if (body.lobes == 1) {
const Lobe &lobe = m_lobes[body.first_lobe];
out[0] = {lobe.center, radial(lobe, pt), weight};
return 1;
}
// The lobes nearest relative to their depth; they morph into each other near the sides where they are as near.
std::array<std::pair<double, size_t>, MaxMorph> nearest;
size_t count = 0;
for (size_t l = body.first_lobe; l < body.first_lobe + body.lobes; ++l) {
const double d = this->offset(pt, m_lobes[l].center).norm() / m_lobes[l].depth;
if (count < MaxMorph)
nearest[count++] = {d, l};
else if (d < nearest.back().first)
nearest.back() = {d, l};
else
continue;
for (size_t k = count - 1; k > 0 && nearest[k].first < nearest[k - 1].first; --k)
std::swap(nearest[k], nearest[k - 1]);
}
std::array<double, MaxMorph> blend;
double total = 0.;
size_t morphs = 0;
for (; morphs < count; ++morphs) {
const double u = 0.5 - (nearest[morphs].first - nearest[0].first) / LobeMorph;
if (u <= 0.)
break;
blend[morphs] = u * u * (3. - 2. * u);
total += blend[morphs];
}
for (size_t k = 0; k < morphs; ++k) {
const Lobe &lobe = m_lobes[nearest[k].second];
out[k] = {lobe.center, radial(lobe, pt), float(weight * blend[k] / total)};
}
return morphs;
}
} // namespace Slic3r
namespace marchsq {
using namespace Slic3r;
struct AdaptiveTpmsField
{
static constexpr float gsizef = 0.40f; // grid cell size in mm (roughly line segment length).
static constexpr float rsizef = 0.004f; // raster pixel size in mm (roughly point accuracy).
const coord_t rsize = scaled(rsizef);
const long gsize = std::lround(gsizef / rsizef);
const AdaptiveTpms &tpms;
const TpmsRadialField &radial_field;
RadialScale scale;
DensityLevels levels;
Point size;
Point offs;
double z;
double cos_angle;
double sin_angle;
AdaptiveTpmsField(const AdaptiveTpms &tpms, const TpmsRadialField &radial_field, const BoundingBox &bbox, coordf_t z, float angle)
: tpms(tpms), radial_field(radial_field), scale(tpms, radial_field.axis() < 0 ? 3 : 2)
, levels(std::max(tpms.interior_frequency / tpms.surface_frequency, 1e-3), BlendRatio, tpms.gradient)
, size(bbox.size()), offs(bbox.min), z(z)
, cos_angle(std::cos(angle)), sin_angle(std::sin(angle))
{}
// The pattern is scaled around the center of the lobe, morphing into the pattern of a neighbouring lobe near the
// side between them. In the 2D modes only within the section, with the interior frequency along the axis.
// The radial field is in the object frame, the fill is rotated by -angle.
float get_scalar(const Coord &p) const
{
const Point pt = to_Point(p);
const double x = unscaled(pt.x());
const double y = unscaled(pt.y());
const Vec3d obj(cos_angle * x - sin_angle * y, sin_angle * x + cos_angle * y, z);
if (radial_field.mode() == TpmsAdaptiveMode::SmoothBlend) {
// The regular patterns of the two levels around the target of the depth, blended by a smoothstep.
auto lattice = [this, x, y](int level) {
const double frequency = tpms.surface_frequency * levels.scale(level);
return tpms.equation(float(frequency * x), float(frequency * y), float(frequency * z));
};
if (levels.count == 0)
return lattice(0);
const double c = std::clamp(levels.level(radial_field.depth(obj)), 0., double(levels.count));
const int k = std::min(int(c), levels.count - 1);
const double u = c - k;
const double w = u * u * (3. - 2. * u);
return float((w < 1. ? (1. - w) * lattice(k) : 0.) + (w > 0. ? w * lattice(k + 1) : 0.));
}
const int axis = radial_field.axis();
TpmsRadialField::Radials radials;
const size_t count = radial_field.radial(obj, radials);
float value = 0.f;
for (size_t i = 0; i < count; ++i) {
const auto &[center, t, weight] = radials[i];
Vec3d q = tpms.surface_frequency * scale(t) * (obj - center);
if (axis >= 0)
q[axis] = tpms.interior_frequency * obj[axis];
value += weight * tpms.equation(float(cos_angle * q.x() + sin_angle * q.y()), float(cos_angle * q.y() - sin_angle * q.x()), float(q.z()));
}
return value;
}
inline coord_t to_coord(long x) const { return x * rsize; }
inline long to_coordr(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()); }
};
template<> struct _RasterTraits<AdaptiveTpmsField>
{
using ValueType = float;
static float get(const AdaptiveTpmsField &sf, size_t row, size_t col) { return sf.get_scalar(Coord(long(row), long(col))); }
static size_t rows(const AdaptiveTpmsField &sf) { return sf.to_coordr(sf.size.y()); }
static size_t cols(const AdaptiveTpmsField &sf) { return sf.to_coordr(sf.size.x()); }
};
// Continuous density level of the depth over a layer, in the object frame.
struct TpmsLevelField
{
static constexpr float gsizef = 0.5f;
static constexpr float rsizef = 0.05f;
const coord_t rsize = scaled(rsizef);
const long gsize = std::lround(gsizef / rsizef);
const TpmsRadialField &field;
const DensityLevels &levels;
Point size;
Point offs;
double z;
TpmsLevelField(const TpmsRadialField &field, const DensityLevels &levels, const BoundingBox &bbox, coordf_t z)
: field(field), levels(levels), size(bbox.size()), offs(bbox.min), z(z)
{}
float get_scalar(const Coord &p) const
{
const Point pt = to_Point(p);
return float(levels.level(field.depth(Vec3d(unscaled(pt.x()), unscaled(pt.y()), z))));
}
inline coord_t to_coord(long x) const { return x * rsize; }
inline long to_coordr(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()); }
};
template<> struct _RasterTraits<TpmsLevelField>
{
using ValueType = float;
static float get(const TpmsLevelField &sf, size_t row, size_t col) { return sf.get_scalar(Coord(long(row), long(col))); }
static size_t rows(const TpmsLevelField &sf) { return sf.to_coordr(sf.size.y()); }
static size_t cols(const TpmsLevelField &sf) { return sf.to_coordr(sf.size.x()); }
};
} // namespace marchsq
namespace Slic3r {
Polylines make_adaptive_tpms(const AdaptiveTpms &tpms, const TpmsRadialField &field, BoundingBox bbox,
coordf_t z, coordf_t layer_height, coordf_t spacing, float angle)
{
// 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::AdaptiveTpmsField::gsizef);
bbox.offset(cell);
bbox.merge(align_to_grid(bbox.min, Point(cell, cell)));
const marchsq::AdaptiveTpmsField raster(tpms, field, bbox, z - 0.5 * layer_height, angle);
const std::vector<marchsq::Ring> rings = marchsq::execute_with_policy(ex_tbb, raster, 0.f, {raster.gsize, raster.gsize});
// Loops narrower than two lines print as blobs.
const double min_loop_length = scaled(2. * PI * spacing);
Polylines polylines;
polylines.reserve(rings.size());
for (const marchsq::Ring &ring : rings) {
Polyline polyline;
polyline.points.reserve(ring.size() + 1);
for (const marchsq::Coord &crd : ring)
polyline.points.emplace_back(raster.to_Point(crd));
polyline.points.push_back(polyline.points.front());
polyline.simplify(SCALED_SPARSE_INFILL_RESOLUTION);
if (polyline.length() >= min_loop_length)
polylines.push_back(std::move(polyline));
}
return polylines;
}
std::vector<TpmsShell> make_tpms_shells(const TpmsRadialField &field, const ExPolygon &expolygon, coordf_t z,
float surface_density, float interior_density, TpmsAdaptiveGradient gradient)
{
const DensityLevels levels(interior_density / surface_density, ShellRatio, gradient);
if (levels.count == 0)
return {{surface_density, {expolygon}}};
// A fixed sampling grid, so that every region of a layer gets the same shells.
const coord_t cell = scaled(marchsq::TpmsLevelField::gsizef);
BoundingBox bbox = get_extents(expolygon);
bbox.offset(cell);
bbox.merge(align_to_grid(bbox.min, Point(cell, cell)));
const marchsq::TpmsLevelField raster(field, levels, bbox, z);
// Each level takes the part deeper than the middle between it and the previous one.
std::vector<TpmsShell> shells;
ExPolygons remaining{expolygon};
for (int level = 0; level < levels.count && !remaining.empty(); ++level) {
Polygons deeper;
for (const marchsq::Ring &ring : marchsq::execute_with_policy(ex_tbb, raster, float(level + 0.5), {raster.gsize, raster.gsize})) {
Polygon &polygon = deeper.emplace_back();
polygon.points.reserve(ring.size());
for (const marchsq::Coord &crd : ring)
polygon.points.emplace_back(raster.to_Point(crd));
}
ExPolygons inner = intersection_ex(union_ex(deeper), remaining);
shells.push_back({float(surface_density * levels.scale(level)), diff_ex(remaining, inner)});
remaining = std::move(inner);
}
if (!remaining.empty())
shells.push_back({interior_density, std::move(remaining)});
return shells;
}
void fill_tpms_shells(const TpmsRadialField &field, const ExPolygon &expolygon, coordf_t z, const FillParams &params, coordf_t spacing,
const std::function<void(const FillParams &, const ExPolygon &)> &fill_shell)
{
FillParams shell_params = params;
shell_params.tpms_adaptive = TpmsAdaptiveMode::Disabled;
for (const TpmsShell &shell : make_tpms_shells(field, expolygon, z, params.density, params.tpms_interior_density, params.tpms_adaptive_gradient)) {
shell_params.density = shell.density;
for (const ExPolygon &part : offset_ex(shell.expolygons, -float(scale_(0.5 * spacing))))
fill_shell(shell_params, part);
}
}
} // namespace Slic3r
+143
View File
@@ -0,0 +1,143 @@
#pragma once
#include <array>
#include <cstddef>
#include <functional>
#include <memory>
#include <utility>
#include <vector>
#include "../libslic3r.h"
#include "FillBase.hpp"
#include "../BoundingBox.hpp"
#include "../ExPolygon.hpp"
#include "../Point.hpp"
#include "../Polyline.hpp"
#include "../PrintConfig.hpp"
namespace Slic3r {
// Radial coordinate inside the lobes of the bodies of an object, sampled from its slices: 0 at the center of a
// lobe, 1 at its surface. Lobes are parts of a body separated by a neck, like two spheres united. In the 2D modes,
// every section normal to the axis has its own bodies and lobes, and distances are measured within the section. The
// modes following the distance to the surface use the depth of every point instead; Distance warp also the lobes.
class TpmsRadialField
{
public:
struct Slice
{
coordf_t bottom_z;
coordf_t top_z;
const ExPolygons *expolygons;
};
struct Radial
{
Vec3d center;
double t;
float weight;
};
// Slices sorted by z, in the XY coordinates of the fill and the print Z.
TpmsRadialField(const std::vector<Slice> &slices, const BoundingBox &bbox, TpmsAdaptiveMode mode,
const std::function<void()> &throw_if_canceled);
// Lobes blended near the sides between them, from a body or from each of the two sections around a point.
static constexpr size_t MaxMorph = 4;
using Radials = std::array<Radial, 2 * MaxMorph>;
// Without a body, as when the object is thinner than the grid cells.
bool empty() const { return m_bodies.empty(); }
// Radial coordinates of pt in unscaled coordinates towards the lobe it belongs to, and towards the neighbouring
// lobes near the sides between them, with weights summing to 1. In the 2D modes, those of the two sections around
// pt. Returns their count.
size_t radial(const Vec3d &pt, Radials &out) const;
// Axis normal to the sections in the 2D modes, -1 in the modes graded in 3D.
int axis() const { return m_axis; }
TpmsAdaptiveMode mode() const { return m_mode; }
// In the modes following the distance to the surface: depth relative to the deepest point of the body, from 0 at
// the surface to 1.
double depth(const Vec3d &pt) const;
private:
struct Lobe
{
Vec3d center;
double depth;
// Distance from the center to the surface on a latitude-longitude grid of directions.
std::vector<float> reach;
// Distance warp: mean depth over the ball along each direction, sampled up to the reach.
std::vector<float> mean_depth;
};
struct Body
{
size_t first_lobe;
size_t lobes;
// Distance from the deepest point to the surface.
double depth;
};
double radial(const Lobe &lobe, const Vec3d &pt) const;
size_t body_radial(size_t node, const Vec3d &pt, float weight, Radial *out) const;
// Offset of pt from a center, within the section in the 2D modes.
Vec3d offset(const Vec3d &pt, const Vec3d &center) const;
int directions() const { return m_axis < 0 ? Polar * Azimuth : Azimuth; }
// Directions of the reach of a lobe, on a latitude-longitude grid, or a circle in the 2D modes.
static constexpr int Polar = 24;
static constexpr int Azimuth = 48;
TpmsAdaptiveMode m_mode;
int m_axis;
Vec3d m_origin;
double m_cell;
Vec3i32 m_size;
// Nearest body of every grid node.
std::vector<int> m_body;
std::vector<Body> m_bodies;
std::vector<Lobe> m_lobes;
// In the 2D modes, the nearest section with a body to every section.
std::vector<int> m_section;
// In the modes following the distance to the surface, the depth of every grid node.
std::vector<float> m_depth;
};
using TpmsRadialFieldPtr = std::unique_ptr<TpmsRadialField>;
// A field for every adaptive mode in use, indexed by the mode.
using TpmsRadialFields = std::array<TpmsRadialFieldPtr, size_t(TpmsAdaptiveMode::Count)>;
struct AdaptiveTpms
{
// Implicit TPMS equation with a period of 2 PI.
float (*equation)(float x, float y, float z);
// Pattern frequencies at the surface and at the center, in radians per mm.
double surface_frequency;
double interior_frequency;
TpmsAdaptiveGradient gradient;
};
// Infill lines in the fill frame, the object frame rotated by -angle; z is the print_z of the layer.
Polylines make_adaptive_tpms(const AdaptiveTpms &tpms, const TpmsRadialField &field, BoundingBox bbox,
coordf_t z, coordf_t layer_height, coordf_t spacing, float angle);
struct TpmsShell
{
float density;
ExPolygons expolygons;
};
// Stepped shells: the parts of an expolygon in the object frame at each density, from the surface inwards; z is the
// middle of the layer.
std::vector<TpmsShell> make_tpms_shells(const TpmsRadialField &field, const ExPolygon &expolygon, coordf_t z,
float surface_density, float interior_density, TpmsAdaptiveGradient gradient);
// Stepped shells: fills every shell with fill_shell at its density, each shrunk by half a line like a filled region,
// so the lines connected along the boundaries of two shells don't overlap.
void fill_tpms_shells(const TpmsRadialField &field, const ExPolygon &expolygon, coordf_t z, const FillParams &params, coordf_t spacing,
const std::function<void(const FillParams &, const ExPolygon &)> &fill_shell);
} // namespace Slic3r
+43 -18
View File
@@ -14,8 +14,10 @@
#include "libslic3r/Fill/FillBase.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/libslic3r.h"
#include "FillTpmsD.hpp"
#include "FillTpmsAdaptive.hpp"
namespace Slic3r {
@@ -23,6 +25,11 @@ static double scaled_floor(double x,double scale){
return std::floor(x/scale)*scale;
}
static float schwarz_d(float x, float y, float z)
{
return std::sin(x) * std::sin(y) * std::sin(z) - std::cos(x) * std::cos(y) * std::cos(z);
}
static Polylines make_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
{
const double scaleFactor = scale_(line_spacing) / density_adjusted;
@@ -110,31 +117,49 @@ void FillTpmsD::_fill_surface_single(
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);
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);
Polylines polylines;
if (params.tpms_adaptive != TpmsAdaptiveMode::Disabled && this->tpms_radial_field != nullptr) {
// Radians per mm of the regular pattern at a density.
auto frequency = [&params, this](double density) { return density * DensityAdjust / (params.multiline * this->spacing); };
BoundingBox bbox = expolygon.contour.bounding_box();
bbox.offset(scale_((params.multiline + 1) * this->spacing));
polylines = make_adaptive_tpms({schwarz_d, frequency(params.density), frequency(params.tpms_interior_density), params.tpms_adaptive_gradient},
*this->tpms_radial_field, bbox, this->z, params.layer_height, this->spacing, infill_angle);
} else {
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);
// align bounding box to a multiple of our grid module
bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
// align bounding box to a multiple of our grid module
bb.merge(align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
// generate pattern
Polylines polylines = make_waves(
scale_(this->z),
density_adjusted,
this->spacing,
ceil(bb.size()(0) / distance) + 1.,
ceil(bb.size()(1) / distance) + 1.);
// generate pattern
polylines = make_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);
}
// shift the polyline to the grid origin
for (Polyline &pl : polylines)
pl.translate(bb.min);
// Apply multiline offset if needed
multiline_fill(polylines, params, spacing);
+33 -15
View File
@@ -8,6 +8,7 @@
#include "libslic3r/Fill/FillBase.hpp"
#include "libslic3r/ExPolygon.hpp"
#include "FillTpmsFK.hpp"
#include "FillTpmsAdaptive.hpp"
#include <cmath>
#include <algorithm>
#include <cstddef>
@@ -17,6 +18,18 @@
#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;
@@ -42,16 +55,7 @@ struct ScalarField
{}
// 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
{
const float fx = freq * x;
const float fy = freq * y;
const float fz = freq * z;
// Fischer - Koch S equation:
// cos(2x)sin(y)cos(z) + cos(2y)sin(z)cos(x) + cos(2z)sin(x)cos(y) = 0
return cosf(2 * fx) * sinf(fy) * cosf(fz) + cosf(2 * fy) * sinf(fz) * cosf(fx) + cosf(2 * fz) * sinf(fx) * cosf(fy);
}
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
@@ -128,20 +132,34 @@ void FillTpmsFK::_fill_surface_single(const FillParams& params,
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);
float density_factor = std::min(0.9f, params.density);
// Density (field period) adjusted to have a good %of weight.
const float vari_T = 4.18f * spacing * params.multiline / density_factor;
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));
marchsq::ScalarField sf = marchsq::ScalarField(bbox, this->z, vari_T);
// Get simplified lines using coarse tolerance of 0.1mm (this is infill).
Polylines polylines = marchsq::get_polylines(sf, SCALED_SPARSE_INFILL_RESOLUTION);
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);
+6 -2
View File
@@ -437,10 +437,14 @@ coordf_t Layer::get_sparse_infill_max_void_area()
double max_void_area = 0.;
for (auto layerm : m_regions) {
Flow flow = layerm->flow(frInfill);
float density = layerm->region().config().sparse_infill_density;
InfillPattern pattern = layerm->region().config().sparse_infill_pattern;
const PrintRegionConfig &config = layerm->region().config();
float density = config.sparse_infill_density;
InfillPattern pattern = config.sparse_infill_pattern;
if (density == 0.)
return -1;
// Orca: the adaptive TPMS infill is as sparse as its interior density.
if (density < 100.f && config.tpms_adaptive != TpmsAdaptiveMode::Disabled && is_tpms_adaptive_pattern(pattern))
density = std::min(density, std::max(1.f, float(config.tpms_interior_density)));
//BBS: rough estimation and need to be optimized
double spacing = flow.scaled_spacing() * (100 - density) / density;
+3
View File
@@ -1181,6 +1181,9 @@ static std::vector<std::string> s_Preset_print_options{
"is_infill_first",
"sparse_infill_density",
"fill_multiline",
"tpms_adaptive",
"tpms_interior_density",
"tpms_adaptive_gradient",
"gyroid_optimized",
"sparse_infill_pattern",
"sparse_infill_smooth_factor",
+25
View File
@@ -5340,6 +5340,31 @@ void Print::export_gcode_from_previous_file(const std::string& file, GCodeProces
}
}
void Print::reload_gcode_moves(GCodeProcessorResult* result) const
{
GCodeProcessor processor;
GCodeProcessor::s_IsBBLPrinter = is_BBL_printer();
const Vec3d origin = this->get_plate_origin();
processor.set_xy_offset(origin(0), origin(1));
// Estimate the per-move times with the same nozzle-grouping slot context as the export.
if (result->nozzle_group_result)
processor.initialize_from_context(result->nozzle_group_result);
try {
processor.process_file(result->filename);
} catch (const std::exception& ex) {
// The edited file is what gets printed, so failing to preview it must not fail the slice.
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": cannot re-read the G-code file " << result->filename << ": " << ex.what();
std::lock_guard<std::mutex> lock(result->result_mutex);
result->lines_ends.clear();
return;
}
GCodeProcessorResult& reloaded = processor.result();
std::lock_guard<std::mutex> lock(result->result_mutex);
result->moves = std::move(reloaded.moves);
result->lines_ends = std::move(reloaded.lines_ends);
}
std::tuple<float, float> Print::object_skirt_offset(double margin_height) const
{
if (config().skirt_loops == 0 || config().skirt_type != stPerObject || m_objects.empty())
+9
View File
@@ -12,6 +12,7 @@
#include "PrintBase.hpp"
#include "Fill/FillAdaptive.hpp"
#include "Fill/FillLightning.hpp"
#include "Fill/FillTpmsAdaptive.hpp"
#include "BoundingBox.hpp"
#include "ExtrusionEntityCollection.hpp"
@@ -406,6 +407,7 @@ public:
double max_z() const { return m_max_z; }
// Centering offset of the sliced mesh from the scaled and rotated mesh of the model.
const Point& center_offset() const { return m_center_offset; }
const TpmsRadialField* tpms_radial_field(TpmsAdaptiveMode mode) const { return m_tpms_radial_fields[size_t(mode)].get(); }
// BBS
void generate_support_preview();
@@ -650,6 +652,7 @@ private:
FillAdaptive::RegionOctrees prepare_adaptive_infill_data(
const std::vector<std::pair<const Surface*, const Layer*>>& surfaces_w_layer) const;
FillLightning::GeneratorPtr prepare_lightning_infill_data();
TpmsRadialFields prepare_tpms_radial_fields() const;
// BBS
SupportNecessaryType is_support_necessary();
@@ -700,6 +703,7 @@ private:
FillAdaptive::RegionOctrees m_adaptive_fill_octrees;
std::vector<BoundingBox> m_separated_body_bboxes;
FillLightning::GeneratorPtr m_lightning_generator;
TpmsRadialFields m_tpms_radial_fields;
std::vector < VolumeSlices > firstLayerObjSliceByVolume;
std::vector<groupedVolumeSlices> firstLayerObjSliceByGroups;
@@ -1287,6 +1291,11 @@ public:
void set_gcode_file_ready();
void set_gcode_file_invalidated();
void export_gcode_from_previous_file(const std::string& file, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
// Re-reads the moves and line offsets of `result` from its G-code file after the file was rewritten in
// place (post-processing scripts or plugins), so the preview and its G-code window follow the file on
// disk. Everything else in `result` was computed while slicing and is kept. If the file cannot be
// re-read, the moves are kept and the line offsets are cleared, which hides the G-code window.
void reload_gcode_moves(GCodeProcessorResult* result) const;
//BBS: add modify_count logic
int get_modified_count() const {return m_modified_count;}
//BBS: add status for whether support used
+87
View File
@@ -364,6 +364,26 @@ static t_config_enum_values s_keys_map_SurfaceFillOrder{
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SurfaceFillOrder)
//Orca
static t_config_enum_values s_keys_map_TpmsAdaptiveMode{
{ "disabled", int(TpmsAdaptiveMode::Disabled) },
{ "distance_warp", int(TpmsAdaptiveMode::DistanceWarp) },
{ "smooth_blend", int(TpmsAdaptiveMode::SmoothBlend) },
{ "stepped_shells", int(TpmsAdaptiveMode::SteppedShells) },
{ "lobes", int(TpmsAdaptiveMode::Lobes) },
{ "normal_z", int(TpmsAdaptiveMode::NormalZ) },
{ "normal_y", int(TpmsAdaptiveMode::NormalY) },
{ "normal_x", int(TpmsAdaptiveMode::NormalX) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TpmsAdaptiveMode)
static t_config_enum_values s_keys_map_TpmsAdaptiveGradient{
{ "linear", int(TpmsAdaptiveGradient::Linear) },
{ "quadratic", int(TpmsAdaptiveGradient::Quadratic) },
{ "exponential", int(TpmsAdaptiveGradient::Exponential) },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TpmsAdaptiveGradient)
//BBS
static t_config_enum_values s_keys_map_PrintSequence {
{ "by layer", int(PrintSequence::ByLayer) },
@@ -3620,6 +3640,73 @@ void PrintConfigDef::init_fff_params()
def->max = 10; // Maximum number of lines for infill pattern
def->set_default_value(new ConfigOptionInt(1));
def = this->add("tpms_adaptive", coEnum);
def->label = L("Adaptive density (experimental)");
def->category = L("Strength");
def->tooltip = L("Grades the Gyroid and TPMS infill inside the object: its cells grow from the surface of the "
"object towards its center. The sparse infill density is used at the surface and the interior "
"density at the center.\n"
"Distance warp, Smooth blend and Stepped shells follow the distance to the nearest surface, "
"including the top and bottom, with the interior density at the point farthest from it:\n"
" - Distance warp: one continuous pattern, stretched and sheared where the distance changes "
"across directions, as in plates and long parts.\n"
" - Smooth blend: the patterns of neighbouring densities blended into each other, with small "
"loops where they meet.\n"
" - Stepped shells: shells of the regular pattern at densities about 1.5 times apart, their "
"lines joined along the shell boundaries.\n"
" - Lobes: follows the 3D shape of the object, including its top and bottom. Every lobe, a part "
"joined to the rest by a narrower neck, is graded towards its own center.\n"
" - Normal Z, Y or X: follows the sections of the object normal to that axis, so the density "
"does not change along it.");
def->enum_keys_map = &ConfigOptionEnum<TpmsAdaptiveMode>::get_enum_values();
def->enum_values.push_back("disabled");
def->enum_values.push_back("distance_warp");
def->enum_values.push_back("smooth_blend");
def->enum_values.push_back("stepped_shells");
def->enum_values.push_back("lobes");
def->enum_values.push_back("normal_z");
def->enum_values.push_back("normal_y");
def->enum_values.push_back("normal_x");
def->enum_labels.push_back(L("Disabled"));
def->enum_labels.push_back(L("Distance warp"));
def->enum_labels.push_back(L("Smooth blend"));
def->enum_labels.push_back(L("Stepped shells"));
def->enum_labels.push_back(L("Lobes"));
def->enum_labels.push_back(L("Normal Z"));
def->enum_labels.push_back(L("Normal Y"));
def->enum_labels.push_back(L("Normal X"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<TpmsAdaptiveMode>(TpmsAdaptiveMode::Disabled));
def = this->add("tpms_interior_density", coPercent);
def->label = L("Interior density");
def->category = L("Strength");
def->tooltip = L("Density of the adaptive infill at the center of the object.");
def->sidetext = "%";
def->min = 1;
def->max = 100;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionPercent(5));
def = this->add("tpms_adaptive_gradient", coEnum);
def->label = L("Adaptive gradient");
def->category = L("Strength");
def->tooltip = L("How the density changes from the surface to the center of the object.\n"
"Linear: the density changes at a constant rate.\n"
"Quadratic: the density stays close to the sparse infill density near the surface and "
"changes faster towards the center.\n"
"Exponential: the density changes quickly just below the surface and levels off towards "
"the center.");
def->enum_keys_map = &ConfigOptionEnum<TpmsAdaptiveGradient>::get_enum_values();
def->enum_values.push_back("linear");
def->enum_values.push_back("quadratic");
def->enum_values.push_back("exponential");
def->enum_labels.push_back(L("Linear"));
def->enum_labels.push_back(L("Quadratic"));
def->enum_labels.push_back(L("Exponential"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<TpmsAdaptiveGradient>(TpmsAdaptiveGradient::Linear));
// 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
+27
View File
@@ -172,6 +172,8 @@ inline bool is_separable_infill_pattern(InfillPattern pattern)
// Orca: Infill patterns laid out by an octree, which each connected body always gets of its own.
inline bool is_octree_infill_pattern(InfillPattern pattern) { return pattern == ipAdaptiveCubic || pattern == ipSupportCubic; }
// Orca: Infill patterns graded by the "tpms_adaptive" option.
inline bool is_tpms_adaptive_pattern(InfillPattern pattern) { return pattern == ipGyroid || pattern == ipTpmsD || pattern == ipTpmsFK; }
// Orca: Infill patterns that round their corners by the "sparse_infill_smooth_factor" option.
// Grid, Triangles and Tri-hexagon only do so in their trapezoidal form, which is generated with more
@@ -257,6 +259,26 @@ enum class SurfaceFillOrder {
Count,
};
// Orca: what the adaptive TPMS density follows: the 3D shape of the object, or its 2D sections normal to an axis.
enum class TpmsAdaptiveMode {
Disabled,
DistanceWarp,
SmoothBlend,
SteppedShells,
Lobes,
NormalZ,
NormalY,
NormalX,
Count,
};
// Orca: how the adaptive TPMS density changes from the object surface to its deepest point.
enum class TpmsAdaptiveGradient {
Linear,
Quadratic,
Exponential,
};
//BBS
enum class PrintSequence {
ByLayer,
@@ -774,6 +796,8 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ToolChangeOrderingType)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PowerLossRecoveryMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SurfaceFillOrder)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TpmsAdaptiveMode)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TpmsAdaptiveGradient)
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
@@ -1450,6 +1474,9 @@ PRINT_CONFIG_CLASS_DEFINE(
// Orca:
((ConfigOptionFloatOrPercent, infill_combination_max_layer_height))
((ConfigOptionInt, fill_multiline))
((ConfigOptionEnum<TpmsAdaptiveMode>, tpms_adaptive))
((ConfigOptionPercent, tpms_interior_density))
((ConfigOptionEnum<TpmsAdaptiveGradient>, tpms_adaptive_gradient))
((ConfigOptionBool, gyroid_optimized))
// Ironing options
((ConfigOptionEnum<IroningType>, ironing_type))
+37
View File
@@ -31,6 +31,7 @@
#include "Fill/FillAdaptive.hpp"
#include "Fill/Fill.hpp"
#include "Fill/FillLightning.hpp"
#include "Fill/FillTpmsAdaptive.hpp"
#include "format.hpp"
#include "AABBTreeIndirect.hpp"
#include "AABBTreeLines.hpp"
@@ -1299,6 +1300,38 @@ FillLightning::GeneratorPtr PrintObject::prepare_lightning_infill_data()
return has_lightning_infill ? FillLightning::build_generator(std::as_const(*this), [this]() -> void { this->throw_if_canceled(); }) : FillLightning::GeneratorPtr();
}
TpmsRadialFields PrintObject::prepare_tpms_radial_fields() const
{
TpmsRadialFields fields;
std::array<bool, size_t(TpmsAdaptiveMode::Count)> modes{};
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id)
if (const PrintRegionConfig &config = this->printing_region(region_id).config();
config.sparse_infill_density > 0 && config.sparse_infill_density < 100 && is_tpms_adaptive_pattern(config.sparse_infill_pattern))
modes[size_t(config.tpms_adaptive.value)] = true;
modes[size_t(TpmsAdaptiveMode::Disabled)] = false;
if (std::find(modes.begin(), modes.end(), true) == modes.end() || m_layers.empty())
return fields;
std::vector<TpmsRadialField::Slice> slices;
slices.reserve(m_layers.size());
BoundingBox bbox;
for (const Layer *layer : m_layers) {
slices.push_back({layer->bottom_z(), layer->print_z, &layer->lslices});
bbox.merge(get_extents(layer->lslices));
}
if (!bbox.defined)
return fields;
for (size_t mode = 0; mode < modes.size(); ++mode) {
if (!modes[mode])
continue;
// Without a field, the infill falls back to the regular pattern.
auto field = std::make_unique<TpmsRadialField>(slices, bbox, TpmsAdaptiveMode(mode), [this]() { m_print->throw_if_canceled(); });
if (!field->empty())
fields[mode] = std::move(field);
}
return fields;
}
void PrintObject::clear_layers()
{
if (!m_shared_object) {
@@ -1702,6 +1735,9 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "infill_overhang_angle") {
steps.emplace_back(posInfill);
} else if (opt_key == "sparse_infill_pattern"
|| opt_key == "tpms_adaptive"
|| opt_key == "tpms_interior_density"
|| opt_key == "tpms_adaptive_gradient"
// Orca: Body centering now also determines bridge anchors during preparation.
// Invalidating preparation also invalidates infill, including top/bottom surfaces.
|| opt_key == "center_of_surface_pattern"
@@ -3320,6 +3356,7 @@ void PrintObject::bridge_over_infill()
}
this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_layer);
this->m_tpms_radial_fields = this->prepare_tpms_radial_fields();
std::vector<size_t> layers_to_generate_infill;
for (const auto &pair : surfaces_by_layer) {
+95 -5
View File
@@ -149,6 +149,56 @@ std::unique_ptr<AppAction> make_action(const std::string& plugin_key, const std:
return std::make_unique<PluginScriptAction>(plugin_key, capability, source_name);
}
// A plugin page capability exposed as a speed-dial action. source_key = plugin_key
// (identity), so a plugin display-name change does not re-key the action.
struct PluginPageAction : AppAction
{
static constexpr const char* kIdPrefix = "plugin_page_action";
std::string plugin_key;
std::string capability;
// The id an action for (plugin_key, capability) would have - lets refresh_page_capability
// remove a gone capability without materialising the action.
static std::string id_for(const std::string& plugin_key, const std::string& capability)
{ return AppAction::compose_id(kIdPrefix, capability.empty() ? plugin_key : capability, plugin_key); }
PluginPageAction(std::string plugin_key_in, std::string capability_in, std::string source_name)
: AppAction(kIdPrefix,
capability_in.empty() ? plugin_key_in : capability_in, // title
plugin_key_in, // source_key
std::move(source_name))
, plugin_key(std::move(plugin_key_in))
, capability(std::move(capability_in))
{
// Stay classified as a plugin: grouped under "Plugins" and gated by the same run-confirm.
this->kind = AppActionKind::Plugin;
// Icon is left empty on purpose: the webview builds resources/images/<icon>.svg, which a
// plugin filesystem icon path would not resolve to.
}
AppActionRunResult run(const std::string& /*param*/) const override
{
MainFrame* mf = wxGetApp().mainframe;
if (mf)
mf->plugin_pages().select_page({PluginCapabilityType::Pages, capability, plugin_key});
return {AppActionRunResult::Level::Success};
}
};
// Builds an action for a page capability, or nullptr if it is not a currently-loaded,
// enabled page capability.
std::unique_ptr<AppAction> make_page_action(const std::string& plugin_key, const std::string& capability, const std::string& source_name)
{
PluginManager& manager = PluginManager::instance();
if (!manager.is_plugin_loaded(plugin_key))
return nullptr;
// only_enabled defaults true, so a disabled capability resolves to nullptr here.
if (!manager.get_plugin_capability({PluginCapabilityType::Pages, capability, plugin_key}))
return nullptr;
return std::make_unique<PluginPageAction>(plugin_key, capability, source_name);
}
// ---- built-in command actions (the speed dial "commands" section) ------
constexpr const char* kSettingPrefix = "orca_setting";
@@ -311,13 +361,20 @@ void ActionRegistry::init()
});
};
auto on_capability = [this](const PluginCapabilityId& capability, ActionChange change) {
if (capability.type != PluginCapabilityType::Script || !wxTheApp || wxGetApp().is_closing())
if (capability.type != PluginCapabilityType::Script && capability.type != PluginCapabilityType::Pages)
return;
const std::string plugin_key = capability.plugin_key;
const std::string name = capability.name;
wxGetApp().CallAfter([this, plugin_key, name, change] {
if (!wxGetApp().is_closing())
if (!wxTheApp || wxGetApp().is_closing())
return;
const PluginCapabilityType type = capability.type;
const std::string plugin_key = capability.plugin_key;
const std::string name = capability.name;
wxGetApp().CallAfter([this, type, plugin_key, name, change] {
if (wxGetApp().is_closing())
return;
if (type == PluginCapabilityType::Script)
this->refresh_capability(plugin_key, name, change);
else
this->refresh_page_capability(plugin_key, name, change);
});
};
@@ -347,6 +404,16 @@ void ActionRegistry::init()
upsert(std::move(action));
}
for (const auto& capability : manager.get_plugin_capabilities("", PluginCapabilityType::Pages)) {
if (!capability)
continue;
const std::string& key = capability->audit_plugin_key();
auto it = source_names.find(key);
const std::string& source_name = it == source_names.end() ? key : it->second;
if (auto action = make_page_action(key, capability->name(), source_name))
upsert(std::move(action));
}
// Built-in palette commands (Save/Load, Preferences, Mode switch, Slice/Preview, Go to layer).
// Register after plugins so the plugin ids win on any (unlikely) id collision - ids are distinct
// by prefix, so this is order-independent. The catalog (and its thin AppAction adapter) lives in
@@ -401,6 +468,12 @@ void ActionRegistry::refresh_source(const std::string& plugin_key, ActionChange
if (auto action = make_action(plugin_key, capability->name(), source_name))
upsert(std::move(action));
}
for (const auto& capability : manager.get_plugin_capabilities(plugin_key, PluginCapabilityType::Pages)) {
if (!capability)
continue;
if (auto action = make_page_action(plugin_key, capability->name(), source_name))
upsert(std::move(action));
}
}
void ActionRegistry::refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change)
@@ -420,6 +493,23 @@ void ActionRegistry::refresh_capability(const std::string& plugin_key, const std
remove(id);
}
void ActionRegistry::refresh_page_capability(const std::string& plugin_key, const std::string& capability, ActionChange change)
{
assert(wxThread::IsMain());
const std::string id = PluginPageAction::id_for(plugin_key, capability);
if (change == ActionChange::Removed) {
remove(id);
return;
}
PluginManager& manager = PluginManager::instance();
if (auto action = make_page_action(plugin_key, capability, find_loaded_source_name(manager, plugin_key)))
upsert(std::move(action));
else
remove(id);
}
void ActionRegistry::upsert(std::unique_ptr<AppAction> action)
{
assert(wxThread::IsMain());
+1
View File
@@ -239,6 +239,7 @@ private:
// one plugin's whole action set; refresh_capability touches a single capability.
void refresh_source(const std::string& plugin_key, ActionChange change);
void refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change);
void refresh_page_capability(const std::string& plugin_key, const std::string& capability, ActionChange change);
bool m_started = false; // init() runs exactly once; guards double-subscription
std::unordered_map<std::string, std::shared_ptr<AppAction>> m_actions; // UI-thread confined; no lock
+5 -4
View File
@@ -283,11 +283,12 @@ void BackgroundSlicingProcess::process_fff()
m_temp_output_path = this->get_current_plate()->get_tmp_gcode_path();
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result,
[this](const ThumbnailsParams& params) { return this->render_thumbnails(params); });
// Orca: BBL printers post-process the g-code in place here and never re-parse it into a fresh
// GCodeProcessorResult, so m_gcode_result->nozzle_group_result (consumed by the H2C print-dispatch
// nozzle mapping) survives post-processing. No preservation guard is needed on this path.
// Orca: BBL printers post-process the g-code in place here, in the file the G-code viewer maps, so
// the preview re-reads its moves and line offsets from the edited file. The rest of m_gcode_result,
// including nozzle_group_result (consumed by the H2C print-dispatch nozzle mapping), is kept.
if (m_fff_print->is_BBL_printer()) {
run_post_process_scripts(m_temp_output_path, false, "File", m_temp_output_path, m_fff_print->full_print_config());
if (run_post_process_scripts(m_temp_output_path, false, "File", m_temp_output_path, m_fff_print->full_print_config()))
m_fff_print->reload_gcode_moves(m_gcode_result);
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": export gcode finished");
+10 -2
View File
@@ -843,9 +843,17 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
bool have_multiline_infill_pattern = pattern == ipGyroid || pattern == ipGrid || pattern == ipRectilinear || pattern == ipTpmsD || pattern == ipTpmsFK || pattern == ipCrossHatch || pattern == ipHoneycomb || pattern == ipLateralLattice || pattern == ipLateralHoneycomb || pattern == ipConcentric ||
pattern == ipCubic || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic|| pattern == ipTriangles || pattern == ipQuarterCubic|| pattern == ipArchimedeanChords || pattern == ipHilbertCurve || pattern == ipOctagramSpiral;
// gyroid_optimized only applies when the sparse infill pattern is gyroid;
// The sparse infill density is the surface density of the adaptive TPMS infill; at 100% the infill is solid.
bool have_tpms_infill = have_infill && config->option<ConfigOptionPercent>("sparse_infill_density")->value < 100 &&
is_tpms_adaptive_pattern(pattern);
toggle_line("tpms_adaptive", have_tpms_infill);
bool have_tpms_adaptive = have_tpms_infill && config->opt_enum<TpmsAdaptiveMode>("tpms_adaptive") != TpmsAdaptiveMode::Disabled;
toggle_line("tpms_interior_density", have_tpms_adaptive);
toggle_line("tpms_adaptive_gradient", have_tpms_adaptive);
// gyroid_optimized only applies when the sparse infill pattern is gyroid without adaptive density;
// hide the whole line otherwise.
toggle_line("gyroid_optimized", have_infill && pattern == ipGyroid);
toggle_line("gyroid_optimized", have_infill && pattern == ipGyroid && !have_tpms_adaptive);
// If there is infill, enable/disable fill_multiline according to whether the pattern supports multiline infill.
if (have_infill) {
+8 -2
View File
@@ -895,10 +895,16 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, f
auto update_lines = [this](uint64_t start_id, uint64_t end_id) {
std::vector<Line> ret;
ret.reserve(end_id - start_id + 1);
// Orca: m_lines_ends indexes into a memory mapping, so it must be clamped to the mapping. If the
// file was modified behind our back (an in-place post-processing script that shrank it), an
// unchecked read is an access violation, which the caller's try/catch cannot catch on Windows.
const size_t file_size = m_file.size();
for (uint64_t id = start_id; id <= end_id; ++id) {
// read line from file
const size_t start = id == 1 ? 0 : m_lines_ends[id - 2];
const size_t original_len = m_lines_ends[id - 1] - start;
// Keep one entry per id: render() indexes m_lines by (id - start_id).
const size_t start = id == 1 ? 0 : std::min(m_lines_ends[id - 2], file_size);
const size_t end = std::min(m_lines_ends[id - 1], file_size);
const size_t original_len = end > start ? end - start : 0;
// A character is four bytes at most, so 55 of them always fit in 220.
const size_t len = std::min(original_len, (size_t) 55 * 4);
std::string gline(m_file.data() + start, len);
+3
View File
@@ -148,6 +148,9 @@ std::map<std::string, std::vector<SimpleSettingData>> SettingsFactory::PART_CATE
{"sparse_infill_density", "", 1},
{"fill_multiline", "", 1},
{"sparse_infill_pattern", "", 1},
{"tpms_adaptive", "", 1},
{"tpms_interior_density", "", 1},
{"tpms_adaptive_gradient", "", 1},
{"sparse_infill_smooth_factor", "", 1},
{"lateral_lattice_angle_1", "", 1},
{"lateral_lattice_angle_2", "", 1},
+3
View File
@@ -2961,6 +2961,9 @@ void TabPrint::build()
optgroup->append_single_option_line("sparse_infill_density", "strength_settings_infill#sparse-infill-density");
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
optgroup->append_single_option_line("tpms_adaptive", "strength_settings_patterns#adaptive-density");
optgroup->append_single_option_line("tpms_interior_density", "strength_settings_patterns#interior-density");
optgroup->append_single_option_line("tpms_adaptive_gradient", "strength_settings_patterns#adaptive-gradient");
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized");
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_infill#sparse-infill-smooth-factor");
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
+11
View File
@@ -400,6 +400,17 @@ void PluginPages::relayout()
}
}
void PluginPages::select_page(const PluginCapabilityId& id)
{
if (m_parent == nullptr || m_pages.find(id) == m_pages.end())
return;
// Swap the page into the visible slot first when it lives behind the overflow menu.
m_swapped_in_id = id;
relayout();
m_parent->SelectPageByName(page_tab_id(id));
}
void PluginPages::show_overflow_menu()
{
const int visible_slots = std::max(1, m_visible_page_count);
+2
View File
@@ -66,6 +66,8 @@ public:
void relayout();
void select_page(const PluginCapabilityId& id);
private:
std::shared_ptr<PagesPluginCapability> get_pages_cap(const PluginCapabilityId& id, bool is_enabled) const;
bool create_page(const PluginCapabilityId& id);