Add contoning top surface coloring method

This commit is contained in:
sentientstardust
2026-05-25 00:59:56 +01:00
parent 3cfd8c656b
commit bf81236bc5
17 changed files with 2007 additions and 144 deletions

View File

@@ -480,6 +480,8 @@ set(lisbslic3r_sources
TriangleSetSampling.hpp
TextureMappingOffset.cpp
TextureMappingOffset.hpp
TextureMappingContoning.cpp
TextureMappingContoning.hpp
TextureMapping.cpp
TextureMapping.hpp
TriangulateWall.cpp

View File

@@ -5,6 +5,7 @@
#include <string>
#include <array>
#include <algorithm>
#include <cassert>
namespace Slic3r {
using RGB = std::array<float, 3>;

View File

@@ -170,7 +170,7 @@ public:
, m_can_reverse(rhs.m_can_reverse)
, m_role(rhs.m_role)
, m_no_extrusion(rhs.m_no_extrusion)
{}
{ this->inset_idx = rhs.inset_idx; }
ExtrusionPath(ExtrusionPath &&rhs)
: polyline(std::move(rhs.polyline))
, mm3_per_mm(rhs.mm3_per_mm)
@@ -179,7 +179,7 @@ public:
, m_can_reverse(rhs.m_can_reverse)
, m_role(rhs.m_role)
, m_no_extrusion(rhs.m_no_extrusion)
{}
{ this->inset_idx = rhs.inset_idx; }
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs)
: polyline(polyline)
, mm3_per_mm(rhs.mm3_per_mm)
@@ -188,7 +188,7 @@ public:
, m_can_reverse(rhs.m_can_reverse)
, m_role(rhs.m_role)
, m_no_extrusion(rhs.m_no_extrusion)
{}
{ this->inset_idx = rhs.inset_idx; }
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs)
: polyline(std::move(polyline))
, mm3_per_mm(rhs.mm3_per_mm)
@@ -197,12 +197,13 @@ public:
, m_can_reverse(rhs.m_can_reverse)
, m_role(rhs.m_role)
, m_no_extrusion(rhs.m_no_extrusion)
{}
{ this->inset_idx = rhs.inset_idx; }
ExtrusionPath& operator=(const ExtrusionPath& rhs) {
m_can_reverse = rhs.m_can_reverse;
m_role = rhs.m_role;
m_no_extrusion = rhs.m_no_extrusion;
inset_idx = rhs.inset_idx;
this->mm3_per_mm = rhs.mm3_per_mm;
this->width = rhs.width;
this->height = rhs.height;
@@ -213,6 +214,7 @@ public:
m_can_reverse = rhs.m_can_reverse;
m_role = rhs.m_role;
m_no_extrusion = rhs.m_no_extrusion;
inset_idx = rhs.inset_idx;
this->mm3_per_mm = rhs.mm3_per_mm;
this->width = rhs.width;
this->height = rhs.height;
@@ -329,21 +331,23 @@ public:
ExtrusionPaths paths;
ExtrusionMultiPath() {}
ExtrusionMultiPath(const ExtrusionMultiPath &rhs) : paths(rhs.paths), m_can_reverse(rhs.m_can_reverse) {}
ExtrusionMultiPath(ExtrusionMultiPath &&rhs) : paths(std::move(rhs.paths)), m_can_reverse(rhs.m_can_reverse) {}
ExtrusionMultiPath(const ExtrusionPaths &paths) : paths(paths) {}
ExtrusionMultiPath(const ExtrusionPath &path) {this->paths.push_back(path); m_can_reverse = path.can_reverse(); }
ExtrusionMultiPath(const ExtrusionMultiPath &rhs) : paths(rhs.paths), m_can_reverse(rhs.m_can_reverse) { this->inset_idx = rhs.inset_idx; }
ExtrusionMultiPath(ExtrusionMultiPath &&rhs) : paths(std::move(rhs.paths)), m_can_reverse(rhs.m_can_reverse) { this->inset_idx = rhs.inset_idx; }
ExtrusionMultiPath(const ExtrusionPaths &paths) : paths(paths) { if (!this->paths.empty()) this->inset_idx = this->paths.front().inset_idx; }
ExtrusionMultiPath(const ExtrusionPath &path) {this->paths.push_back(path); m_can_reverse = path.can_reverse(); this->inset_idx = path.inset_idx; }
ExtrusionMultiPath &operator=(const ExtrusionMultiPath &rhs)
{
this->paths = rhs.paths;
m_can_reverse = rhs.m_can_reverse;
inset_idx = rhs.inset_idx;
return *this;
}
ExtrusionMultiPath &operator=(ExtrusionMultiPath &&rhs)
{
this->paths = std::move(rhs.paths);
m_can_reverse = rhs.m_can_reverse;
inset_idx = rhs.inset_idx;
return *this;
}
@@ -394,12 +398,12 @@ public:
ExtrusionPaths paths;
ExtrusionLoop(ExtrusionLoopRole role = elrDefault) : m_loop_role(role) {}
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {}
ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) {}
ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) { if (!this->paths.empty()) this->inset_idx = this->paths.front().inset_idx; }
ExtrusionLoop(ExtrusionPaths &&paths, ExtrusionLoopRole role = elrDefault) : paths(std::move(paths)), m_loop_role(role) { if (!this->paths.empty()) this->inset_idx = this->paths.front().inset_idx; }
ExtrusionLoop(const ExtrusionPath &path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
{ this->paths.push_back(path); }
{ this->paths.push_back(path); this->inset_idx = path.inset_idx; }
ExtrusionLoop(const ExtrusionPath &&path, ExtrusionLoopRole role = elrDefault) : m_loop_role(role)
{ this->paths.emplace_back(std::move(path)); }
{ this->paths.emplace_back(std::move(path)); this->inset_idx = this->paths.back().inset_idx; }
bool is_loop() const override{ return true; }
bool can_reverse() const override { return false; }
ExtrusionEntity* clone() const override{ return new ExtrusionLoop (*this); }

File diff suppressed because it is too large Load Diff

View File

@@ -104,28 +104,38 @@ bool Fill::use_bridge_flow(const InfillPattern type)
Polylines Fill::fill_surface(const Surface *surface, const FillParams &params)
{
params.check_canceled();
// Perform offset.
Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing)));
params.check_canceled();
// Create the infills for each of the regions.
Polylines polylines_out;
for (size_t i = 0; i < expp.size(); ++ i)
for (size_t i = 0; i < expp.size(); ++ i) {
params.check_canceled();
_fill_surface_single(
params,
surface->thickness_layers,
_infill_direction(surface),
std::move(expp[i]),
polylines_out);
}
params.check_canceled();
return polylines_out;
}
ThickPolylines Fill::fill_surface_arachne(const Surface* surface, const FillParams& params)
{
params.check_canceled();
// Perform offset.
Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing)));
params.check_canceled();
// Create the infills for each of the regions.
ThickPolylines thick_polylines_out;
for (ExPolygon& expoly : expp)
for (ExPolygon& expoly : expp) {
params.check_canceled();
_fill_surface_single(params, surface->thickness_layers, _infill_direction(surface), std::move(expoly), thick_polylines_out);
}
params.check_canceled();
return thick_polylines_out;
}
@@ -135,14 +145,17 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
Polylines polylines;
ThickPolylines thick_polylines;
try {
params.check_canceled();
if (params.use_arachne)
thick_polylines = this->fill_surface_arachne(surface, params);
else
polylines = this->fill_surface(surface, params);
params.check_canceled();
}
catch (InfillFailedException&) {}
if (!polylines.empty() || !thick_polylines.empty()) {
params.check_canceled();
// calculate actual flow from spacing (which might have been adjusted by the infill
// pattern generator)
double flow_mm3_per_mm = params.flow.mm3_per_mm();
@@ -187,6 +200,7 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
// Orca: run gap fill
this->_create_gap_fill(surface, params, eec);
params.check_canceled();
}
}
@@ -194,6 +208,7 @@ void Fill::fill_surface_extrusion(const Surface* surface, const FillParams& para
// and append them to the out ExtrusionEntityCollection.
void Fill::_create_gap_fill(const Surface* surface, const FillParams& params, ExtrusionEntityCollection* out){
params.check_canceled();
//Orca: just to be safe, check against null pointer for the print object config and if NULL return.
if (this->print_object_config == nullptr) return;
@@ -205,9 +220,11 @@ void Fill::_create_gap_fill(const Surface* surface, const FillParams& params, Ex
Flow new_flow = params.flow;
ExPolygons unextruded_areas;
unextruded_areas = diff_ex(this->no_overlap_expolygons, union_ex(out->polygons_covered_by_spacing(10)));
params.check_canceled();
ExPolygons gapfill_areas = union_ex(unextruded_areas);
if (!this->no_overlap_expolygons.empty())
gapfill_areas = intersection_ex(gapfill_areas, this->no_overlap_expolygons);
params.check_canceled();
if (gapfill_areas.size() > 0 && params.density >= 1) {
double min = 0.2 * new_flow.scaled_spacing() * (1 - INSET_OVERLAP_TOLERANCE);
@@ -215,6 +232,7 @@ void Fill::_create_gap_fill(const Surface* surface, const FillParams& params, Ex
ExPolygons gaps_ex = diff_ex(
opening_ex(gapfill_areas, float(min / 2.)),
offset2_ex(gapfill_areas, -float(max / 2.), float(max / 2. + ClipperSafetyOffset)));
params.check_canceled();
//BBS: sort the gap_ex to avoid mess travel
Points ordering_points;
ordering_points.reserve(gaps_ex.size());
@@ -228,10 +246,12 @@ void Fill::_create_gap_fill(const Surface* surface, const FillParams& params, Ex
ThickPolylines polylines;
for (ExPolygon& ex : gaps_ex_sorted) {
params.check_canceled();
//BBS: Use DP simplify to avoid duplicated points and accelerate medial-axis calculation as well.
ex.douglas_peucker(SCALED_RESOLUTION * 0.1);
ex.medial_axis(min, max, &polylines);
}
params.check_canceled();
if (!polylines.empty() && !is_bridge(params.extrusion_role)) {
polylines.erase(std::remove_if(polylines.begin(), polylines.end(),
@@ -243,6 +263,7 @@ void Fill::_create_gap_fill(const Surface* surface, const FillParams& params, Ex
variable_width(polylines, erGapFill, params.flow, gap_fill.entities);
auto gap = std::move(gap_fill.entities);
out->append(gap);
params.check_canceled();
}
}
}
@@ -1240,6 +1261,7 @@ void mark_boundary_segments_touching_infill(
void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const double spacing, const FillParams &params)
{
params.check_canceled();
assert(! boundary_src.contour.points.empty());
auto polygons_src = reserve_vector<const Polygon*>(boundary_src.holes.size() + 1);
polygons_src.emplace_back(&boundary_src.contour);
@@ -1251,6 +1273,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_
void Fill::connect_infill(Polylines &&infill_ordered, const Polygons &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams &params)
{
params.check_canceled();
auto polygons_src = reserve_vector<const Polygon*>(boundary_src.size());
for (const Polygon &polygon : boundary_src)
polygons_src.emplace_back(&polygon);
@@ -1577,6 +1600,7 @@ BoundingBox Fill::extended_object_bounding_box() const
void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams &params)
{
params.check_canceled();
assert(! infill_ordered.empty());
assert(params.anchor_length >= 0.);
assert(params.anchor_length_max >= 0.01f);
@@ -1590,6 +1614,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
#endif
BoundaryInfillGraph graph = create_boundary_infill_graph(infill_ordered, boundary_src, bbox, spacing);
params.check_canceled();
std::vector<size_t> merged_with(infill_ordered.size());
std::iota(merged_with.begin(), merged_with.end(), 0);
@@ -1699,7 +1724,10 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
}
//FIXME improve the Traveling Salesman problem with 2-opt and 3-opt local optimization.
for (Arc &arc : arches)
size_t arc_idx = 0;
for (Arc &arc : arches) {
if ((arc_idx++ & 127) == 0)
params.check_canceled();
if (! arc.intersection->consumed && ! arc.intersection->next_on_contour->consumed) {
// Indices of the polylines to be connected by a perimeter segment.
ContourIntersectionPoint *cp1 = arc.intersection;
@@ -1742,9 +1770,13 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
}
}
}
}
// Connect the remaining open infill lines to the perimeter lines if possible.
for (ContourIntersectionPoint &contour_point : graph.map_infill_end_point_to_boundary)
size_t contour_point_idx = 0;
for (ContourIntersectionPoint &contour_point : graph.map_infill_end_point_to_boundary) {
if ((contour_point_idx++ & 127) == 0)
params.check_canceled();
if (! contour_point.consumed && contour_point.contour_idx != boundary_idx_unconnected) {
const Points &contour = graph.boundary[contour_point.contour_idx];
const std::vector<double> &contour_params = graph.boundary_params[contour_point.contour_idx];
@@ -1808,15 +1840,18 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Po
}
}
}
}
polylines_out.reserve(polylines_out.size() + std::count_if(infill_ordered.begin(), infill_ordered.end(), [](const Polyline &pl) { return ! pl.empty(); }));
for (Polyline &pl : infill_ordered)
if (! pl.empty())
polylines_out.emplace_back(std::move(pl));
params.check_canceled();
}
void Fill::chain_or_connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const double spacing, const FillParams &params)
{
params.check_canceled();
if (!infill_ordered.empty()) {
if (params.dont_connect()) {
if (infill_ordered.size() > 1)

View File

@@ -50,6 +50,7 @@ struct FillParams
bool full_infill() const { return density > 0.9999f; }
// Don't connect the fill lines around the inner perimeter.
bool dont_connect() const { return anchor_length_max < 0.05f; }
void check_canceled() const { if (throw_if_canceled != nullptr) throw_if_canceled(throw_if_canceled_context); }
// Fill density, fraction in <0, 1>
float density { 0.f };
@@ -103,6 +104,8 @@ struct FillParams
bool locked_zag{false};
float infill_lock_depth{0.0};
float skin_infill_depth{0.0};
void (*throw_if_canceled)(void*){ nullptr };
void *throw_if_canceled_context{ nullptr };
};
static_assert(IsTriviallyCopyable<FillParams>::value, "FillParams class is not POD (and it should be - see constructor).");

View File

@@ -7,6 +7,7 @@
#include "SurfaceCollection.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "BoundingBox.hpp"
#include <functional>
namespace Slic3r {
class ExPolygon;
@@ -193,7 +194,10 @@ public:
void make_perimeters();
// Phony version of make_fills() without parameters for Perl integration only.
void make_fills() { this->make_fills(nullptr, nullptr); }
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator = nullptr);
void make_fills(FillAdaptive::Octree* adaptive_fill_octree,
FillAdaptive::Octree* support_fill_octree,
FillLightning::Generator* lightning_generator = nullptr,
std::function<void()> throw_if_canceled = {});
Polylines generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree *adaptive_fill_octree,
FillAdaptive::Octree *support_fill_octree,
FillLightning::Generator* lightning_generator) const;

View File

@@ -9,6 +9,7 @@
#include "BoundingBox.hpp"
#include "SVG.hpp"
#include "TextureMapping.hpp"
#include "TextureMappingContoning.hpp"
#include "TextureMappingOffset.hpp"
#include "MultiMaterialSegmentation.hpp"
#include "Algorithm/RegionExpansion.hpp"
@@ -452,9 +453,13 @@ static std::optional<unsigned int> perimeter_texture_choose_gradient_recolor_com
struct PerimeterTextureRecolorSampler {
bool image_texture { false };
bool contoning { false };
size_t num_physical { 0 };
std::optional<TextureMappingOffsetContext> image_context;
std::vector<std::array<float, 3>> image_component_colors;
TextureMappingContoningSolver contoning_solver;
int contoning_stack_layers { TextureMappingZone::DefaultTopSurfaceContoningStackLayers };
float contoning_angle_threshold_deg { TextureMappingZone::DefaultTopSurfaceContoningAngleThresholdDeg };
std::vector<unsigned int> component_ids;
std::vector<TextureMappingOffsetContext> gradient_contexts;
};
@@ -502,6 +507,21 @@ static std::optional<PerimeterTextureRecolorSampler> perimeter_texture_make_reco
sampler.image_component_colors.push_back({ 0.f, 0.f, 0.f });
}
}
if (zone.top_surface_contoning_perimeters_active()) {
sampler.contoning = true;
sampler.contoning_solver =
TextureMappingContoningSolver(zone, print_config, sampler.image_context->component_ids);
sampler.contoning_stack_layers =
std::clamp(zone.top_surface_contoning_stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers);
sampler.contoning_angle_threshold_deg =
std::clamp(zone.top_surface_contoning_angle_threshold_deg,
TextureMappingZone::MinTopSurfaceContoningAngleThresholdDeg,
TextureMappingZone::MaxTopSurfaceContoningAngleThresholdDeg);
if (!sampler.contoning_solver.valid())
sampler.contoning = false;
}
return sampler;
}
@@ -532,6 +552,32 @@ static std::optional<unsigned int> perimeter_texture_choose_recolor_component_wi
if (sampler.image_texture) {
if (!sampler.image_context)
return std::nullopt;
if (sampler.contoning) {
std::optional<std::array<float, 3>> rgb =
perimeter_texture_average_visible_image_rgb(visible, *sampler.image_context);
if (!rgb && fallback_entity != nullptr) {
std::vector<double> accum(sampler.image_context->component_ids.size(), 0.0);
double total_weight = 0.0;
perimeter_texture_accumulate_path_image_weights(*fallback_entity, *sampler.image_context, accum, total_weight);
if (total_weight > EPSILON) {
std::array<float, 3> fallback_rgb{ 0.f, 0.f, 0.f };
for (size_t idx = 0; idx < accum.size() && idx < sampler.image_component_colors.size(); ++idx) {
const double w = accum[idx] / total_weight;
fallback_rgb[0] += float(sampler.image_component_colors[idx][0] * w);
fallback_rgb[1] += float(sampler.image_component_colors[idx][1] * w);
fallback_rgb[2] += float(sampler.image_component_colors[idx][2] * w);
}
rgb = fallback_rgb;
}
}
if (!rgb)
return std::nullopt;
const unsigned int component_id =
sampler.contoning_solver.component_for_depth(*rgb, sampler.contoning_stack_layers, 0);
if (component_id >= 1 && component_id <= sampler.num_physical)
return component_id;
return std::nullopt;
}
std::optional<unsigned int> chosen =
perimeter_texture_choose_image_recolor_component(visible,
fallback_entity,
@@ -940,7 +986,9 @@ static void perimeter_texture_sample_segment_visible_points(float
static std::optional<unsigned int> perimeter_texture_choose_recolor_component_from_point_samples(
const std::vector<PerimeterTextureVisiblePointSample> &samples,
const PerimeterTextureRecolorSampler &sampler)
const PerimeterTextureRecolorSampler &sampler,
int depth_from_top = 0,
int contoning_stack_layers = 0)
{
if (samples.empty())
return std::nullopt;
@@ -948,6 +996,30 @@ static std::optional<unsigned int> perimeter_texture_choose_recolor_component_fr
if (sampler.image_texture) {
if (!sampler.image_context)
return std::nullopt;
if (sampler.contoning) {
std::array<double, 3> rgb_accum{ 0.0, 0.0, 0.0 };
double rgb_total_weight = 0.0;
for (const PerimeterTextureVisiblePointSample &sample : samples) {
perimeter_texture_accumulate_image_rgb_at_point(*sampler.image_context, sample.point, sample.weight, rgb_accum, rgb_total_weight);
}
if (rgb_total_weight <= EPSILON)
return std::nullopt;
const std::array<float, 3> rgb{
float(std::clamp(rgb_accum[0] / rgb_total_weight, 0.0, 1.0)),
float(std::clamp(rgb_accum[1] / rgb_total_weight, 0.0, 1.0)),
float(std::clamp(rgb_accum[2] / rgb_total_weight, 0.0, 1.0))
};
const int stack_layers = contoning_stack_layers > 0 ?
std::clamp(contoning_stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
sampler.contoning_stack_layers) :
sampler.contoning_stack_layers;
const unsigned int component_id =
sampler.contoning_solver.component_for_depth(rgb, stack_layers, depth_from_top);
if (component_id >= 1 && component_id <= sampler.num_physical)
return component_id;
return std::nullopt;
}
if (!sampler.image_context->weight_field.raw_component_weights_from_texture) {
std::array<double, 3> rgb_accum{ 0.0, 0.0, 0.0 };
double rgb_total_weight = 0.0;
@@ -1425,6 +1497,9 @@ struct PerimeterTextureRecolorEntityPiece {
struct PerimeterTexturePathRecolorContext {
PerimeterTextureMaskIndex path_mask;
const PerimeterTextureRecolorSampler *sampler { nullptr };
float min_recolor_run_length_mm { 0.f };
int path_depth_from_top { 0 };
int contoning_stack_layers { TextureMappingZone::DefaultTopSurfaceContoningStackLayers };
};
struct PerimeterTexturePathSegment {
@@ -1639,23 +1714,22 @@ static void perimeter_texture_clear_short_recolor_runs(std::vector<PerimeterText
}
}
static unsigned int perimeter_texture_recolor_component_for_segment(const PerimeterTexturePathRecolorContext &context,
const Point &a,
const Point &b)
static void perimeter_texture_collect_visible_samples_for_segment(const PerimeterTexturePathRecolorContext &context,
const Point &a,
const Point &b,
std::vector<PerimeterTextureVisiblePointSample> &visible_samples)
{
if (context.sampler == nullptr || context.path_mask.empty() || a == b)
return 0;
return;
const double dx = double(b.x()) - double(a.x());
const double dy = double(b.y()) - double(a.y());
const double len = std::hypot(dx, dy);
if (!std::isfinite(len) || len <= EPSILON)
return 0;
return;
const double inward_x = -dy / len;
const double inward_y = dx / len;
std::vector<PerimeterTextureVisiblePointSample> visible_samples;
visible_samples.reserve(3);
const std::array<Point, 3> sample_points{
perimeter_texture_interpolate_point(a, b, 0.25),
perimeter_texture_interpolate_point(a, b, 0.50),
@@ -1664,9 +1738,24 @@ static unsigned int perimeter_texture_recolor_component_for_segment(const Perime
for (const Point &sample : sample_points)
if (perimeter_texture_mask_index_contains_point(context.path_mask, sample))
visible_samples.push_back(PerimeterTextureVisiblePointSample{ sample, 1.0, inward_x, inward_y });
}
static unsigned int perimeter_texture_recolor_component_for_segment(const PerimeterTexturePathRecolorContext &context,
const Point &a,
const Point &b)
{
if (context.sampler == nullptr)
return 0;
std::vector<PerimeterTextureVisiblePointSample> visible_samples;
visible_samples.reserve(3);
perimeter_texture_collect_visible_samples_for_segment(context, a, b, visible_samples);
const std::optional<unsigned int> component =
perimeter_texture_choose_recolor_component_from_point_samples(visible_samples, *context.sampler);
perimeter_texture_choose_recolor_component_from_point_samples(visible_samples,
*context.sampler,
context.path_depth_from_top,
context.contoning_stack_layers);
return component && *component > 0 ? *component : 0;
}
@@ -1691,13 +1780,94 @@ static void perimeter_texture_append_recolor_path_piece(std::vector<PerimeterTex
unscale<double>(polyline.length()) + unscale<double>(std::max<double>(1.0, double(SCALED_EPSILON))) <
2.0 * double(source.width))
extruder_override = -1;
pieces.push_back(PerimeterTextureRecolorEntityPiece{ extruder_override, new ExtrusionPath(std::move(polyline), source) });
ExtrusionPath *path = new ExtrusionPath(std::move(polyline), source);
path->inset_idx = source.inset_idx;
pieces.push_back(PerimeterTextureRecolorEntityPiece{ extruder_override, path });
}
static bool perimeter_texture_entity_has_recolorable_path(const ExtrusionEntity &entity)
{
if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath *>(&entity))
return perimeter_texture_path_role_can_top_visible_recolor(path->role());
if (const ExtrusionMultiPath *multipath = dynamic_cast<const ExtrusionMultiPath *>(&entity))
return std::any_of(multipath->paths.begin(), multipath->paths.end(), [](const ExtrusionPath &path) {
return perimeter_texture_path_role_can_top_visible_recolor(path.role());
});
if (const ExtrusionLoop *loop = dynamic_cast<const ExtrusionLoop *>(&entity))
return std::any_of(loop->paths.begin(), loop->paths.end(), [](const ExtrusionPath &path) {
return perimeter_texture_path_role_can_top_visible_recolor(path.role());
});
if (const ExtrusionEntityCollection *collection = dynamic_cast<const ExtrusionEntityCollection *>(&entity))
return std::any_of(collection->entities.begin(), collection->entities.end(), [](const ExtrusionEntity *child) {
return child != nullptr && perimeter_texture_entity_has_recolorable_path(*child);
});
return false;
}
static int perimeter_texture_entity_depth_from_top(const ExtrusionEntity &entity, int fallback_depth)
{
if (entity.inset_idx >= 0)
return entity.inset_idx;
if (is_external_perimeter(entity.role()))
return 0;
if (is_internal_perimeter(entity.role()) || entity.role() == erOverhangPerimeter)
return std::max(1, fallback_depth);
return std::max(0, fallback_depth);
}
static void perimeter_texture_collect_recolorable_inset_depths(const ExtrusionEntity &entity, int &max_inset_idx)
{
if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath *>(&entity)) {
if (perimeter_texture_path_role_can_top_visible_recolor(path->role()) && path->inset_idx >= 0)
max_inset_idx = std::max(max_inset_idx, path->inset_idx);
return;
}
if (const ExtrusionMultiPath *multipath = dynamic_cast<const ExtrusionMultiPath *>(&entity)) {
if (perimeter_texture_path_role_can_top_visible_recolor(multipath->role()) && multipath->inset_idx >= 0)
max_inset_idx = std::max(max_inset_idx, multipath->inset_idx);
for (const ExtrusionPath &path : multipath->paths)
if (perimeter_texture_path_role_can_top_visible_recolor(path.role()) && path.inset_idx >= 0)
max_inset_idx = std::max(max_inset_idx, path.inset_idx);
return;
}
if (const ExtrusionLoop *loop = dynamic_cast<const ExtrusionLoop *>(&entity)) {
if (perimeter_texture_path_role_can_top_visible_recolor(loop->role()) && loop->inset_idx >= 0)
max_inset_idx = std::max(max_inset_idx, loop->inset_idx);
for (const ExtrusionPath &path : loop->paths)
if (perimeter_texture_path_role_can_top_visible_recolor(path.role()) && path.inset_idx >= 0)
max_inset_idx = std::max(max_inset_idx, path.inset_idx);
return;
}
if (const ExtrusionEntityCollection *collection = dynamic_cast<const ExtrusionEntityCollection *>(&entity)) {
for (const ExtrusionEntity *child : collection->entities)
if (child != nullptr)
perimeter_texture_collect_recolorable_inset_depths(*child, max_inset_idx);
}
}
static int perimeter_texture_available_contoning_shell_slots(const ExtrusionEntityCollection &perimeters,
int fallback_wall_loops,
int configured_stack_layers)
{
int max_inset_idx = -1;
for (const ExtrusionEntity *entity : perimeters.entities)
if (entity != nullptr)
perimeter_texture_collect_recolorable_inset_depths(*entity, max_inset_idx);
const int configured_layers =
std::clamp(configured_stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers);
const int available_slots = max_inset_idx >= 0 ? max_inset_idx + 1 : std::max(1, fallback_wall_loops);
return std::clamp(available_slots,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
configured_layers);
}
static bool perimeter_texture_split_path_by_recolor_masks(const ExtrusionPath &path,
const PerimeterTexturePathRecolorContext &context,
std::vector<PerimeterTextureRecolorEntityPiece> &pieces,
bool emit_unchanged)
bool emit_unchanged,
int path_depth_from_top)
{
if (!perimeter_texture_path_role_can_top_visible_recolor(path.role()) || path.polyline.points.size() < 2) {
if (emit_unchanged)
@@ -1707,6 +1877,10 @@ static bool perimeter_texture_split_path_by_recolor_masks(const ExtrusionPath
std::vector<PerimeterTexturePathSegment> path_segments;
path_segments.reserve(path.polyline.points.size() - 1);
std::vector<std::vector<PerimeterTextureVisiblePointSample>> segment_visible_samples;
const bool use_chunked_contoning = context.sampler != nullptr && context.sampler->contoning;
if (use_chunked_contoning)
segment_visible_samples.reserve(path.polyline.points.size() - 1);
bool saw_segment = false;
bool saw_override = false;
@@ -1716,13 +1890,82 @@ static bool perimeter_texture_split_path_by_recolor_masks(const ExtrusionPath
if (a == b)
continue;
saw_segment = true;
const unsigned int component = perimeter_texture_recolor_component_for_segment(context, a, b);
const int extruder_override = component > 0 ? int(component) - 1 : -1;
PerimeterTexturePathRecolorContext segment_context = context;
segment_context.path_depth_from_top = path_depth_from_top;
int extruder_override = -1;
if (use_chunked_contoning) {
segment_visible_samples.emplace_back();
segment_visible_samples.back().reserve(3);
perimeter_texture_collect_visible_samples_for_segment(segment_context, a, b, segment_visible_samples.back());
} else {
const unsigned int component = perimeter_texture_recolor_component_for_segment(segment_context, a, b);
extruder_override = component > 0 ? int(component) - 1 : -1;
}
if (extruder_override >= 0)
saw_override = true;
path_segments.push_back(PerimeterTexturePathSegment{ a, b, extruder_override });
}
if (use_chunked_contoning && context.sampler != nullptr) {
PerimeterTexturePathRecolorContext segment_context = context;
segment_context.path_depth_from_top = path_depth_from_top;
const double min_chunk_length_scaled =
double(scale_(std::max(std::isfinite(path.width) && path.width > 0.f ? 2.f * path.width : 0.f,
context.min_recolor_run_length_mm)));
for (size_t start = 0; start < path_segments.size();) {
while (start < path_segments.size() &&
(start >= segment_visible_samples.size() || segment_visible_samples[start].empty()))
++start;
if (start >= path_segments.size())
break;
size_t run_end = start;
while (run_end < path_segments.size() &&
run_end < segment_visible_samples.size() &&
!segment_visible_samples[run_end].empty())
++run_end;
for (size_t chunk_start = start; chunk_start < run_end;) {
size_t chunk_end = chunk_start;
double chunk_length_scaled = 0.0;
std::vector<PerimeterTextureVisiblePointSample> chunk_samples;
while (chunk_end < run_end &&
(chunk_end == chunk_start || chunk_length_scaled < min_chunk_length_scaled)) {
chunk_length_scaled += perimeter_texture_path_segment_length_scaled(path_segments[chunk_end]);
chunk_samples.insert(chunk_samples.end(),
segment_visible_samples[chunk_end].begin(),
segment_visible_samples[chunk_end].end());
++chunk_end;
}
if (chunk_end < run_end) {
const double remaining_length_scaled =
perimeter_texture_path_segments_length_scaled(path_segments, chunk_end, run_end);
if (remaining_length_scaled < min_chunk_length_scaled * 0.5) {
for (; chunk_end < run_end; ++chunk_end) {
chunk_samples.insert(chunk_samples.end(),
segment_visible_samples[chunk_end].begin(),
segment_visible_samples[chunk_end].end());
}
}
}
const std::optional<unsigned int> component =
perimeter_texture_choose_recolor_component_from_point_samples(chunk_samples,
*context.sampler,
segment_context.path_depth_from_top,
segment_context.contoning_stack_layers);
const int extruder_override = component && *component > 0 ? int(*component) - 1 : -1;
if (extruder_override >= 0) {
saw_override = true;
for (size_t idx = chunk_start; idx < chunk_end; ++idx)
path_segments[idx].extruder_override = extruder_override;
}
chunk_start = chunk_end;
}
start = run_end;
}
}
if (!saw_segment || !saw_override) {
if (emit_unchanged)
pieces.push_back(PerimeterTextureRecolorEntityPiece{ -1, path.clone() });
@@ -1730,7 +1973,8 @@ static bool perimeter_texture_split_path_by_recolor_masks(const ExtrusionPath
}
if (std::isfinite(path.width) && path.width > 0.f) {
const double min_recolor_length_scaled = double(scale_(2.f * path.width));
const double min_recolor_length_scaled =
double(scale_(std::max(2.f * path.width, context.min_recolor_run_length_mm)));
perimeter_texture_expand_short_recolor_runs(path_segments, min_recolor_length_scaled);
perimeter_texture_clear_short_recolor_runs(path_segments, min_recolor_length_scaled);
}
@@ -1777,18 +2021,22 @@ static bool perimeter_texture_split_path_by_recolor_masks(const ExtrusionPath
static bool perimeter_texture_split_entity_by_recolor_masks(const ExtrusionEntity &entity,
const PerimeterTexturePathRecolorContext &context,
std::vector<PerimeterTextureRecolorEntityPiece> &pieces,
bool emit_unchanged);
bool emit_unchanged,
int path_depth_from_top);
static bool perimeter_texture_split_paths_by_recolor_masks(const ExtrusionPaths &paths,
const ExtrusionEntity &fallback_entity,
const PerimeterTexturePathRecolorContext &context,
std::vector<PerimeterTextureRecolorEntityPiece> &pieces,
bool emit_unchanged)
bool emit_unchanged,
int path_depth_from_top)
{
std::vector<PerimeterTextureRecolorEntityPiece> path_pieces;
bool changed = false;
for (const ExtrusionPath &path : paths)
changed |= perimeter_texture_split_path_by_recolor_masks(path, context, path_pieces, true);
for (const ExtrusionPath &path : paths) {
const int effective_depth = path.inset_idx >= 0 ? path.inset_idx : path_depth_from_top;
changed |= perimeter_texture_split_path_by_recolor_masks(path, context, path_pieces, true, effective_depth);
}
if (!changed) {
perimeter_texture_delete_recolor_entity_pieces(path_pieces);
@@ -1806,17 +2054,26 @@ static bool perimeter_texture_split_paths_by_recolor_masks(const ExtrusionPaths
static bool perimeter_texture_split_collection_children_by_recolor_masks(const ExtrusionEntityCollection &collection,
const PerimeterTexturePathRecolorContext &context,
std::vector<PerimeterTextureRecolorEntityPiece> &pieces,
bool emit_unchanged)
bool emit_unchanged,
int path_depth_from_top)
{
std::vector<PerimeterTextureRecolorEntityPiece> child_pieces;
bool changed = collection.texture_mapping_extruder_override >= 0;
int local_depth = path_depth_from_top;
for (const ExtrusionEntity *child : collection.entities) {
if (child == nullptr)
continue;
const int child_depth = perimeter_texture_entity_depth_from_top(*child, local_depth);
if (collection.texture_mapping_extruder_override >= 0) {
child_pieces.push_back(PerimeterTextureRecolorEntityPiece{ collection.texture_mapping_extruder_override, child->clone() });
} else {
changed |= perimeter_texture_split_entity_by_recolor_masks(*child, context, child_pieces, true);
changed |= perimeter_texture_split_entity_by_recolor_masks(*child, context, child_pieces, true, child_depth);
}
if (child->inset_idx < 0 && perimeter_texture_entity_has_recolorable_path(*child)) {
if (is_external_perimeter(child->role()))
local_depth = 1;
else
++local_depth;
}
}
@@ -1840,16 +2097,17 @@ static bool perimeter_texture_split_collection_children_by_recolor_masks(const E
static bool perimeter_texture_split_entity_by_recolor_masks(const ExtrusionEntity &entity,
const PerimeterTexturePathRecolorContext &context,
std::vector<PerimeterTextureRecolorEntityPiece> &pieces,
bool emit_unchanged)
bool emit_unchanged,
int path_depth_from_top)
{
if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath *>(&entity))
return perimeter_texture_split_path_by_recolor_masks(*path, context, pieces, emit_unchanged);
return perimeter_texture_split_path_by_recolor_masks(*path, context, pieces, emit_unchanged, path_depth_from_top);
if (const ExtrusionMultiPath *multipath = dynamic_cast<const ExtrusionMultiPath *>(&entity))
return perimeter_texture_split_paths_by_recolor_masks(multipath->paths, *multipath, context, pieces, emit_unchanged);
return perimeter_texture_split_paths_by_recolor_masks(multipath->paths, *multipath, context, pieces, emit_unchanged, path_depth_from_top);
if (const ExtrusionLoop *loop = dynamic_cast<const ExtrusionLoop *>(&entity))
return perimeter_texture_split_paths_by_recolor_masks(loop->paths, *loop, context, pieces, emit_unchanged);
return perimeter_texture_split_paths_by_recolor_masks(loop->paths, *loop, context, pieces, emit_unchanged, path_depth_from_top);
if (const ExtrusionEntityCollection *collection = dynamic_cast<const ExtrusionEntityCollection *>(&entity))
return perimeter_texture_split_collection_children_by_recolor_masks(*collection, context, pieces, emit_unchanged);
return perimeter_texture_split_collection_children_by_recolor_masks(*collection, context, pieces, emit_unchanged, path_depth_from_top);
if (emit_unchanged)
pieces.push_back(PerimeterTextureRecolorEntityPiece{ -1, entity.clone() });
@@ -1892,7 +2150,8 @@ static void perimeter_texture_apply_top_visible_recolor_to_perimeters(const Laye
const ExPolygons &path_mask,
const TextureMappingZone &zone,
unsigned int texture_zone_id,
float texture_external_width_mm)
float texture_external_width_mm,
float min_recolor_run_length_mm = 0.f)
{
if (perimeters.entities.empty() || path_mask.empty())
return;
@@ -1905,6 +2164,12 @@ static void perimeter_texture_apply_top_visible_recolor_to_perimeters(const Laye
PerimeterTexturePathRecolorContext context;
context.path_mask = perimeter_texture_make_mask_index(&path_mask);
context.sampler = &*sampler;
context.min_recolor_run_length_mm = std::max(0.f, min_recolor_run_length_mm);
context.contoning_stack_layers = sampler->contoning ?
perimeter_texture_available_contoning_shell_slots(perimeters,
std::max(1, layer_region.region().config().wall_loops.value),
sampler->contoning_stack_layers) :
sampler->contoning_stack_layers;
if (context.path_mask.empty())
return;
@@ -1920,9 +2185,9 @@ static void perimeter_texture_apply_top_visible_recolor_to_perimeters(const Laye
recolored_entities.emplace_back(collection->clone());
continue;
}
perimeter_texture_split_collection_children_by_recolor_masks(*collection, context, pieces, true);
perimeter_texture_split_collection_children_by_recolor_masks(*collection, context, pieces, true, 0);
} else {
perimeter_texture_split_entity_by_recolor_masks(*entity, context, pieces, true);
perimeter_texture_split_entity_by_recolor_masks(*entity, context, pieces, true, 0);
}
const bool entity_changed = std::any_of(pieces.begin(), pieces.end(), [](const PerimeterTextureRecolorEntityPiece &piece) {
@@ -2392,7 +2657,9 @@ void Layer::apply_perimeter_path_modulation_v2()
layerm->slices,
*zone,
texture_zone_id,
std::nullopt,
zone->top_surface_contoning_perimeters_active() ?
std::optional<float>(std::max(0.05f, float(layerm->flow(frExternalPerimeter).width()))) :
std::nullopt,
nullptr,
nullptr,
nullptr);
@@ -2475,26 +2742,58 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
const float texture_external_width_mm =
std::max(0.05f, float(print_config.texture_mapping_outer_wall_gradient_max_line_width.value));
const float normal_external_width_mm = std::max(0.05f, float(this->flow(frExternalPerimeter).width()));
SurfaceCollection modulated_slices;
const SurfaceCollection *perimeter_slices = &slices;
ExPolygons top_visible_recolor_path_mask;
std::vector<ExPolygons> top_visible_recolor_masks;
PerimeterTextureTopVisibleRecolorThresholds top_visible_recolor_thresholds;
float contoning_min_recolor_run_length_mm = 0.f;
std::optional<TextureMappingOffsetContext> reusable_modulation_context;
unsigned int perimeter_texture_zone_id = 0;
bool use_perimeter_path_modulation = false;
bool use_legacy_perimeter_path_modulation = false;
bool use_perimeter_path_modulation_v2 = false;
bool use_contoning_perimeter = false;
float active_external_width_mm = texture_external_width_mm;
const TextureMappingZone *perimeter_path_zone =
perimeter_path_modulation_zone_for_region(*this->layer()->object()->print(), region_config, perimeter_texture_zone_id);
if (perimeter_path_zone != nullptr) {
if (perimeter_path_zone->recolor_top_visible_perimeter_sections) {
use_contoning_perimeter = perimeter_path_zone->top_surface_contoning_perimeters_active();
active_external_width_mm = use_contoning_perimeter ? normal_external_width_mm : texture_external_width_mm;
if (use_contoning_perimeter) {
ExPolygons wall_band;
const float wall_depth_mm =
active_external_width_mm +
std::max(0, region_config.wall_loops.value - 1) * float(this->flow(frPerimeter).spacing());
top_visible_recolor_path_mask =
perimeter_texture_top_visible_wall_band_mask(*this,
slices,
wall_depth_mm,
perimeter_path_zone->top_visible_perimeter_recolor_above_layers,
&wall_band);
if (perimeter_path_zone->top_surface_contoning_angle_threshold_deg >=
TextureMappingZone::MaxTopSurfaceContoningAngleThresholdDeg - 1e-4f)
top_visible_recolor_path_mask = std::move(wall_band);
contoning_min_recolor_run_length_mm =
texture_mapping_contoning_min_feature_mm(*perimeter_path_zone,
print_config,
TextureMappingManager::effective_texture_component_ids(
*perimeter_path_zone,
print_config.filament_colour.values.size(),
print_config.filament_colour.values),
active_external_width_mm);
top_visible_recolor_thresholds = perimeter_texture_top_visible_recolor_thresholds(
int(TextureMappingZone::TopVisibleRecolorConservative));
top_visible_recolor_thresholds.min_run_length_mm = contoning_min_recolor_run_length_mm;
top_visible_recolor_thresholds.min_visible_area_mm2 = contoning_min_recolor_run_length_mm * contoning_min_recolor_run_length_mm * 0.25f;
} else if (perimeter_path_zone->recolor_top_visible_perimeter_sections) {
perimeter_texture_top_visible_recolor_data(*this,
slices,
*perimeter_path_zone,
perimeter_texture_zone_id,
texture_external_width_mm,
active_external_width_mm,
top_visible_recolor_masks,
top_visible_recolor_path_mask,
top_visible_recolor_thresholds,
@@ -2598,7 +2897,8 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
top_visible_recolor_path_mask,
*perimeter_path_zone,
perimeter_texture_zone_id,
active_texture_external_width_mm.value_or(texture_external_width_mm));
active_texture_external_width_mm.value_or(active_external_width_mm),
contoning_min_recolor_run_length_mm);
};
SurfaceCollection fill_surfaces_before;
@@ -2609,7 +2909,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
}
const std::optional<float> initial_texture_external_width_mm =
use_perimeter_path_modulation ? std::optional<float>(texture_external_width_mm) : std::optional<float>();
use_perimeter_path_modulation ? std::optional<float>(active_external_width_mm) : std::optional<float>();
process_slices_with_top_visible_recolor(perimeter_slices, initial_texture_external_width_mm);
if ((use_legacy_perimeter_path_modulation || use_perimeter_path_modulation_v2) &&
@@ -2621,18 +2921,18 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
*fill_no_overlap = fill_no_overlap_before;
fill_surfaces_before = *fill_surfaces;
fill_no_overlap_before = *fill_no_overlap;
process_slices(fallback_original_slices, std::optional<float>(texture_external_width_mm));
process_slices(fallback_original_slices, std::optional<float>(active_external_width_mm));
const bool original_texture_has_extrusions =
!this->perimeters.entities.empty() || !this->thin_fills.entities.empty();
const std::optional<float> reduced_external_width_mm = perimeter_texture_min_external_width(this->perimeters);
const bool has_reduced_external_width =
reduced_external_width_mm &&
*reduced_external_width_mm < texture_external_width_mm - float(EPSILON);
*reduced_external_width_mm < active_external_width_mm - float(EPSILON);
if (has_reduced_external_width && perimeter_path_zone->recolor_small_perimeter_loops) {
if (perimeter_texture_apply_recolor_small_perimeter_loops(*this,
*perimeter_path_zone,
perimeter_texture_zone_id,
texture_external_width_mm)) {
active_external_width_mm)) {
set_perimeter_path_modulation_v2_fallback_slices(*fallback_original_slices, false);
return;
}
@@ -2662,7 +2962,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
*fill_surfaces = fill_surfaces_before;
*fill_no_overlap = fill_no_overlap_before;
const std::optional<float> fallback_texture_external_width_mm =
original_texture_has_extrusions ? std::optional<float>(texture_external_width_mm) : std::optional<float>();
original_texture_has_extrusions ? std::optional<float>(active_external_width_mm) : std::optional<float>();
process_slices(fallback_original_slices, fallback_texture_external_width_mm);
set_perimeter_path_modulation_v2_fallback_slices(*fallback_original_slices, false);
}

View File

@@ -521,8 +521,11 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
// Append paths to collection.
if (!paths.empty()) {
for (ExtrusionPath &path : paths)
path.inset_idx = int(extrusion->inset_idx);
if (extrusion->is_closed) {
ExtrusionLoop extrusion_loop(std::move(paths), pg_extrusion.is_contour ? elrDefault : elrHole);
extrusion_loop.inset_idx = int(extrusion->inset_idx);
if ((perimeter_generator.config->wall_direction == WallDirection::CounterClockwise) ==
(pg_extrusion.is_contour || pg_extrusions.size() == 2))
extrusion_loop.make_counter_clockwise();
@@ -550,12 +553,14 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
assert(std::prev(it)->polyline.last_point() == it->polyline.first_point());
}
ExtrusionMultiPath multi_path;
multi_path.inset_idx = int(extrusion->inset_idx);
multi_path.paths.emplace_back(std::move(paths.front()));
for (auto it_path = std::next(paths.begin()); it_path != paths.end(); ++it_path) {
if (multi_path.paths.back().last_point() != it_path->first_point()) {
extrusion_coll.append(ExtrusionMultiPath(std::move(multi_path)));
multi_path = ExtrusionMultiPath();
multi_path.inset_idx = int(extrusion->inset_idx);
}
multi_path.paths.emplace_back(std::move(*it_path));
}

View File

@@ -27,6 +27,7 @@
#include <atomic>
#include <cmath>
#include <float.h>
#include <functional>
#include <oneapi/tbb/blocked_range.h>
#include <oneapi/tbb/concurrent_vector.h>
#include <oneapi/tbb/parallel_for.h>
@@ -832,17 +833,28 @@ void PrintObject::infill()
this->prepare_infill();
if (this->set_started(posInfill)) {
m_print->set_status(35, L("Generating infill toolpath"));
const size_t total_layers = m_layers.size();
std::atomic<size_t> completed_layers { 0 };
auto set_infill_progress = [this, total_layers](size_t completed) {
if (total_layers == 0)
m_print->set_status(35, L("Generating infill toolpath"));
else
m_print->set_status(35, Slic3r::format(L("Generating infill toolpath (%1%/%2%)"), completed, total_layers));
};
set_infill_progress(0);
const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first;
const auto& support_fill_octree = this->m_adaptive_fill_octrees.second;
const std::function<void()> throw_if_canceled = [this]() { m_print->throw_if_canceled(); };
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, m_layers.size()),
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range<size_t>& range) {
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree, &throw_if_canceled, &completed_layers, &set_infill_progress](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
m_print->throw_if_canceled();
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), this->m_lightning_generator.get());
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), this->m_lightning_generator.get(), throw_if_canceled);
const size_t completed = completed_layers.fetch_add(1, std::memory_order_relaxed) + 1;
set_infill_progress(completed);
}
}
);

View File

@@ -668,6 +668,8 @@ static int modulation_mode_from_name(const std::string &name)
static std::string top_surface_image_printing_method_name(int method)
{
if (method == int(TextureMappingZone::TopSurfaceImageContoning))
return std::string("contoning");
if (method == int(TextureMappingZone::TopSurfaceImageSameLayer45Partition))
return std::string("same_layer_45_partition");
return std::string("same_angle_45_width");
@@ -675,6 +677,8 @@ static std::string top_surface_image_printing_method_name(int method)
static int top_surface_image_printing_method_from_name(const std::string &name)
{
if (name == "contoning")
return int(TextureMappingZone::TopSurfaceImageContoning);
if (name == "same_layer_45_partition")
return int(TextureMappingZone::TopSurfaceImageSameLayer45Partition);
return int(TextureMappingZone::TopSurfaceImageSameAngle45Width);
@@ -1044,6 +1048,11 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const
std::abs(top_surface_image_max_line_width_mm - rhs.top_surface_image_max_line_width_mm) <= eps &&
top_surface_image_colored_top_layers == rhs.top_surface_image_colored_top_layers &&
top_surface_image_fixed_coloring_filaments == rhs.top_surface_image_fixed_coloring_filaments &&
std::abs(top_surface_contoning_angle_threshold_deg - rhs.top_surface_contoning_angle_threshold_deg) <= eps &&
top_surface_contoning_stack_layers == rhs.top_surface_contoning_stack_layers &&
std::abs(top_surface_contoning_min_feature_mm - rhs.top_surface_contoning_min_feature_mm) <= eps &&
top_surface_contoning_color_lower_surfaces == rhs.top_surface_contoning_color_lower_surfaces &&
top_surface_contoning_only_color_surface_infill == rhs.top_surface_contoning_only_color_surface_infill &&
compact_offset_mode == rhs.compact_offset_mode &&
use_legacy_fixed_color_mode == rhs.use_legacy_fixed_color_mode &&
high_speed_image_texture_sampling == rhs.high_speed_image_texture_sampling &&
@@ -1401,6 +1410,22 @@ std::string TextureMappingManager::serialize_entries()
TextureMappingZone::MinTopSurfaceImageColoredTopLayers,
TextureMappingZone::MaxTopSurfaceImageColoredTopLayers);
texture["top_surface_image_fixed_coloring_filaments"] = zone.top_surface_image_fixed_coloring_filaments;
texture["top_surface_contoning_angle_threshold_deg"] =
std::clamp(finite_or(zone.top_surface_contoning_angle_threshold_deg,
TextureMappingZone::DefaultTopSurfaceContoningAngleThresholdDeg),
TextureMappingZone::MinTopSurfaceContoningAngleThresholdDeg,
TextureMappingZone::MaxTopSurfaceContoningAngleThresholdDeg);
texture["top_surface_contoning_stack_layers"] =
clamp_int(zone.top_surface_contoning_stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers);
texture["top_surface_contoning_min_feature_mm"] =
std::clamp(finite_or(zone.top_surface_contoning_min_feature_mm,
TextureMappingZone::DefaultTopSurfaceContoningMinFeatureMm),
TextureMappingZone::MinTopSurfaceContoningMinFeatureMm,
TextureMappingZone::MaxTopSurfaceContoningMinFeatureMm);
texture["top_surface_contoning_color_lower_surfaces"] = zone.top_surface_contoning_color_lower_surfaces;
texture["top_surface_contoning_only_color_surface_infill"] = zone.top_surface_contoning_only_color_surface_infill;
texture["compact_offset_mode"] = zone.compact_offset_mode;
texture["use_legacy_fixed_color_mode"] = zone.use_legacy_fixed_color_mode;
texture["high_speed_image_texture_sampling"] = true;
@@ -1626,6 +1651,29 @@ void TextureMappingManager::load_entries(const std::string &serialized,
zone.top_surface_image_fixed_coloring_filaments =
texture.value("top_surface_image_fixed_coloring_filaments",
TextureMappingZone::DefaultTopSurfaceImageFixedColoringFilaments);
zone.top_surface_contoning_angle_threshold_deg =
std::clamp(finite_or(texture.value("top_surface_contoning_angle_threshold_deg",
TextureMappingZone::DefaultTopSurfaceContoningAngleThresholdDeg),
TextureMappingZone::DefaultTopSurfaceContoningAngleThresholdDeg),
TextureMappingZone::MinTopSurfaceContoningAngleThresholdDeg,
TextureMappingZone::MaxTopSurfaceContoningAngleThresholdDeg);
zone.top_surface_contoning_stack_layers =
clamp_int(texture.value("top_surface_contoning_stack_layers",
TextureMappingZone::DefaultTopSurfaceContoningStackLayers),
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers);
zone.top_surface_contoning_min_feature_mm =
std::clamp(finite_or(texture.value("top_surface_contoning_min_feature_mm",
TextureMappingZone::DefaultTopSurfaceContoningMinFeatureMm),
TextureMappingZone::DefaultTopSurfaceContoningMinFeatureMm),
TextureMappingZone::MinTopSurfaceContoningMinFeatureMm,
TextureMappingZone::MaxTopSurfaceContoningMinFeatureMm);
zone.top_surface_contoning_color_lower_surfaces =
texture.value("top_surface_contoning_color_lower_surfaces",
TextureMappingZone::DefaultTopSurfaceContoningColorLowerSurfaces);
zone.top_surface_contoning_only_color_surface_infill =
texture.value("top_surface_contoning_only_color_surface_infill",
TextureMappingZone::DefaultTopSurfaceContoningOnlyColorSurfaceInfill);
zone.compact_offset_mode = texture.value("compact_offset_mode", TextureMappingZone::DefaultCompactOffsetMode);
zone.use_legacy_fixed_color_mode =
texture.value("use_legacy_fixed_color_mode", TextureMappingZone::DefaultUseLegacyFixedColorMode);

View File

@@ -68,7 +68,8 @@ struct TextureMappingZone
enum TopSurfaceImagePrintingMethod : uint8_t {
TopSurfaceImageSameAngle45Width = 0,
TopSurfaceImageSameLayer45Partition = 1
TopSurfaceImageSameLayer45Partition = 1,
TopSurfaceImageContoning = 2
};
enum FilamentColorMode : uint8_t {
@@ -155,6 +156,17 @@ struct TextureMappingZone
static constexpr int MaxTopSurfaceImageColoredTopLayers = 20;
static constexpr int DefaultTopSurfaceImageColoredTopLayers = 3;
static constexpr bool DefaultTopSurfaceImageFixedColoringFilaments = true;
static constexpr float MinTopSurfaceContoningAngleThresholdDeg = 0.f;
static constexpr float MaxTopSurfaceContoningAngleThresholdDeg = 180.f;
static constexpr float DefaultTopSurfaceContoningAngleThresholdDeg = 20.f;
static constexpr int MinTopSurfaceContoningStackLayers = 1;
static constexpr int MaxTopSurfaceContoningStackLayers = 20;
static constexpr int DefaultTopSurfaceContoningStackLayers = 6;
static constexpr float MinTopSurfaceContoningMinFeatureMm = 0.f;
static constexpr float MaxTopSurfaceContoningMinFeatureMm = 20.f;
static constexpr float DefaultTopSurfaceContoningMinFeatureMm = 0.f;
static constexpr bool DefaultTopSurfaceContoningColorLowerSurfaces = true;
static constexpr bool DefaultTopSurfaceContoningOnlyColorSurfaceInfill = false;
static constexpr bool DefaultCompactOffsetMode = true;
static constexpr bool DefaultUseLegacyFixedColorMode = false;
static constexpr bool DefaultHighSpeedImageTextureSampling = true;
@@ -249,6 +261,11 @@ struct TextureMappingZone
float top_surface_image_max_line_width_mm = DefaultTopSurfaceImageMaxLineWidthMm;
int top_surface_image_colored_top_layers = DefaultTopSurfaceImageColoredTopLayers;
bool top_surface_image_fixed_coloring_filaments = DefaultTopSurfaceImageFixedColoringFilaments;
float top_surface_contoning_angle_threshold_deg = DefaultTopSurfaceContoningAngleThresholdDeg;
int top_surface_contoning_stack_layers = DefaultTopSurfaceContoningStackLayers;
float top_surface_contoning_min_feature_mm = DefaultTopSurfaceContoningMinFeatureMm;
bool top_surface_contoning_color_lower_surfaces = DefaultTopSurfaceContoningColorLowerSurfaces;
bool top_surface_contoning_only_color_surface_infill = DefaultTopSurfaceContoningOnlyColorSurfaceInfill;
bool compact_offset_mode = DefaultCompactOffsetMode;
bool use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode;
bool high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling;
@@ -298,7 +315,20 @@ struct TextureMappingZone
return top_surface_image_printing_enabled &&
is_image_texture() &&
(top_surface_image_printing_method == int(TopSurfaceImageSameAngle45Width) ||
top_surface_image_printing_method == int(TopSurfaceImageSameLayer45Partition));
top_surface_image_printing_method == int(TopSurfaceImageSameLayer45Partition) ||
(top_surface_image_printing_method == int(TopSurfaceImageContoning) &&
uses_perimeter_path_modulation_v2()));
}
bool top_surface_contoning_active() const
{
return top_surface_image_printing_enabled &&
is_image_texture() &&
top_surface_image_printing_method == int(TopSurfaceImageContoning) &&
uses_perimeter_path_modulation_v2();
}
bool top_surface_contoning_perimeters_active() const
{
return top_surface_contoning_active() && !top_surface_contoning_only_color_surface_infill;
}
void apply_default_modulation_mode()
@@ -343,6 +373,11 @@ struct TextureMappingZone
top_surface_image_max_line_width_mm = DefaultTopSurfaceImageMaxLineWidthMm;
top_surface_image_colored_top_layers = DefaultTopSurfaceImageColoredTopLayers;
top_surface_image_fixed_coloring_filaments = DefaultTopSurfaceImageFixedColoringFilaments;
top_surface_contoning_angle_threshold_deg = DefaultTopSurfaceContoningAngleThresholdDeg;
top_surface_contoning_stack_layers = DefaultTopSurfaceContoningStackLayers;
top_surface_contoning_min_feature_mm = DefaultTopSurfaceContoningMinFeatureMm;
top_surface_contoning_color_lower_surfaces = DefaultTopSurfaceContoningColorLowerSurfaces;
top_surface_contoning_only_color_surface_infill = DefaultTopSurfaceContoningOnlyColorSurfaceInfill;
compact_offset_mode = DefaultCompactOffsetMode;
use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode;
high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling;

View File

@@ -0,0 +1,269 @@
// original author: sentientstardust
#include "TextureMappingContoning.hpp"
#include "Color.hpp"
#include "ColorSolver.hpp"
#include "PrintConfig.hpp"
#include "libslic3r.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <numeric>
namespace Slic3r {
namespace {
float clamp01(float value)
{
if (!std::isfinite(value))
return 0.f;
return std::clamp(value, 0.f, 1.f);
}
float filament_luminance(const PrintConfig &config, unsigned int component_id)
{
ColorRGB color;
if (component_id == 0 || component_id > config.filament_colour.values.size() ||
!decode_color(config.filament_colour.get_at(size_t(component_id - 1)), color))
return std::numeric_limits<float>::max();
return 0.2126f * color.r() + 0.7152f * color.g() + 0.0722f * color.b();
}
float perceptual_error(const std::array<float, 3> &lhs, const std::array<float, 3> &rhs)
{
const float dl = lhs[0] - rhs[0];
const float da = lhs[1] - rhs[1];
const float db = lhs[2] - rhs[2];
return dl * dl + 4.f * da * da + 4.f * db * db;
}
void enumerate_counts(size_t component_idx,
int remaining,
std::vector<int> &counts,
std::vector<std::vector<int>> &out)
{
if (component_idx + 1 == counts.size()) {
counts[component_idx] = remaining;
out.emplace_back(counts);
return;
}
for (int count = 0; count <= remaining; ++count) {
counts[component_idx] = count;
enumerate_counts(component_idx + 1, remaining - count, counts, out);
}
}
}
std::optional<std::array<float, 3>> texture_mapping_contoning_component_colors(
const PrintConfig &config,
const std::vector<unsigned int> &component_ids,
std::vector<std::array<float, 3>> &out)
{
out.clear();
out.reserve(component_ids.size());
for (const unsigned int id : component_ids) {
if (id == 0 || id > config.filament_colour.values.size())
return std::nullopt;
ColorRGB color;
if (!decode_color(config.filament_colour.get_at(size_t(id - 1)), color))
return std::nullopt;
out.push_back({ color.r(), color.g(), color.b() });
}
if (out.empty())
return std::nullopt;
return out.front();
}
std::vector<unsigned int> texture_mapping_contoning_components_bottom_to_top(
const TextureMappingZone &zone,
const PrintConfig &config,
std::vector<unsigned int> component_ids)
{
component_ids.erase(std::remove_if(component_ids.begin(), component_ids.end(), [](unsigned int id) {
return id == 0;
}), component_ids.end());
component_ids.erase(std::unique(component_ids.begin(), component_ids.end()), component_ids.end());
if (component_ids.empty())
return component_ids;
bool has_complete_td = true;
for (unsigned int component_id : component_ids) {
const size_t idx = size_t(component_id - 1);
const float td = idx < zone.filament_transmission_distances_mm.size() ?
zone.filament_transmission_distances_mm[idx] :
0.f;
if (!std::isfinite(td) || td <= 0.f) {
has_complete_td = false;
break;
}
}
if (has_complete_td) {
std::stable_sort(component_ids.begin(), component_ids.end(), [&zone](unsigned int lhs, unsigned int rhs) {
return zone.filament_transmission_distances_mm[size_t(lhs - 1)] <
zone.filament_transmission_distances_mm[size_t(rhs - 1)];
});
return component_ids;
}
std::stable_sort(component_ids.begin(), component_ids.end(), [&config](unsigned int lhs, unsigned int rhs) {
return filament_luminance(config, lhs) < filament_luminance(config, rhs);
});
return component_ids;
}
float texture_mapping_contoning_min_feature_mm(const TextureMappingZone &zone,
const PrintConfig &config,
const std::vector<unsigned int> &component_ids,
float external_width_mm)
{
const float configured =
std::clamp(zone.top_surface_contoning_min_feature_mm,
TextureMappingZone::MinTopSurfaceContoningMinFeatureMm,
TextureMappingZone::MaxTopSurfaceContoningMinFeatureMm);
if (configured > 0.f)
return configured;
float nozzle = 0.4f;
for (const unsigned int id : component_ids) {
if (id >= 1 && size_t(id - 1) < config.nozzle_diameter.values.size())
nozzle = std::max(nozzle, float(config.nozzle_diameter.get_at(size_t(id - 1))));
}
if (!config.nozzle_diameter.values.empty())
nozzle = std::max(nozzle, float(config.nozzle_diameter.values.front()));
const float width = std::isfinite(external_width_mm) && external_width_mm > 0.f ? external_width_mm : nozzle;
return std::max({ 2.0f, 4.f * nozzle, 3.f * width });
}
bool texture_mapping_contoning_normal_eligible(float normal_z, float threshold_deg)
{
if (!std::isfinite(threshold_deg) || threshold_deg >= TextureMappingZone::MaxTopSurfaceContoningAngleThresholdDeg - 1e-4f)
return true;
if (!std::isfinite(normal_z))
return true;
const float clamped_z = std::clamp(normal_z, -1.f, 1.f);
const float angle = float(std::acos(clamped_z) * 180.0 / PI);
return angle <= std::clamp(threshold_deg,
TextureMappingZone::MinTopSurfaceContoningAngleThresholdDeg,
TextureMappingZone::MaxTopSurfaceContoningAngleThresholdDeg);
}
TextureMappingContoningSolver::TextureMappingContoningSolver(const TextureMappingZone &zone,
const PrintConfig &config,
std::vector<unsigned int> component_ids)
{
component_ids.erase(std::remove_if(component_ids.begin(), component_ids.end(), [&config](unsigned int id) {
return id == 0 || id > config.filament_colour.values.size();
}), component_ids.end());
component_ids.erase(std::unique(component_ids.begin(), component_ids.end()), component_ids.end());
m_component_ids = component_ids;
if (!texture_mapping_contoning_component_colors(config, m_component_ids, m_component_colors))
m_component_ids.clear();
if (m_component_ids.empty())
return;
m_component_luminance.reserve(m_component_ids.size());
for (const unsigned int id : m_component_ids)
m_component_luminance.emplace_back(filament_luminance(config, id));
m_components_bottom_to_top = texture_mapping_contoning_components_bottom_to_top(zone, config, m_component_ids);
}
const std::vector<TextureMappingContoningSolver::Candidate>&
TextureMappingContoningSolver::candidates_for_depth(int stack_layers) const
{
const int depth = std::clamp(stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers);
auto found = m_candidates_by_depth.find(depth);
if (found != m_candidates_by_depth.end())
return found->second;
std::vector<Candidate> candidates;
if (!valid())
return m_candidates_by_depth.emplace(depth, std::move(candidates)).first->second;
std::vector<std::vector<int>> counts_list;
std::vector<int> counts(m_component_ids.size(), 0);
enumerate_counts(0, depth, counts, counts_list);
candidates.reserve(counts_list.size());
for (std::vector<int> &candidate_counts : counts_list) {
std::vector<float> weights(candidate_counts.size(), 0.f);
for (size_t idx = 0; idx < candidate_counts.size(); ++idx)
weights[idx] = float(candidate_counts[idx]) / float(std::max(1, depth));
Candidate candidate;
candidate.counts = std::move(candidate_counts);
candidate.rgb = mix_color_solver_components(m_component_colors, weights, ColorSolverMixModel::PigmentPainter);
candidate.oklab = color_solver_oklab_from_srgb(candidate.rgb);
for (size_t idx = 0; idx < candidate.counts.size() && idx < m_component_luminance.size(); ++idx)
candidate.dark_score += float(candidate.counts[idx]) * (1.f - clamp01(m_component_luminance[idx]));
candidates.emplace_back(std::move(candidate));
}
return m_candidates_by_depth.emplace(depth, std::move(candidates)).first->second;
}
TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::array<float, 3> &target_rgb, int stack_layers) const
{
TextureMappingContoningStack out;
if (!valid())
return out;
const int depth = std::clamp(stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers);
const std::vector<Candidate> &candidates = candidates_for_depth(depth);
if (candidates.empty())
return out;
const std::array<float, 3> target_oklab = color_solver_oklab_from_srgb(target_rgb);
size_t best_idx = size_t(-1);
float best_error = std::numeric_limits<float>::max();
float best_dark_score = -std::numeric_limits<float>::max();
for (size_t idx = 0; idx < candidates.size(); ++idx) {
const Candidate &candidate = candidates[idx];
const float error = perceptual_error(candidate.oklab, target_oklab);
const bool near_tie = std::isfinite(best_error) && error <= best_error * 1.03f + 1e-6f;
if (error < best_error || (near_tie && candidate.dark_score > best_dark_score)) {
best_idx = idx;
best_error = error;
best_dark_score = candidate.dark_score;
}
}
if (best_idx >= candidates.size())
return out;
const Candidate &best = candidates[best_idx];
out.bottom_to_top.reserve(size_t(depth));
for (const unsigned int ordered_id : m_components_bottom_to_top) {
const auto component_it = std::find(m_component_ids.begin(), m_component_ids.end(), ordered_id);
if (component_it == m_component_ids.end())
continue;
const size_t component_idx = size_t(component_it - m_component_ids.begin());
const int count = component_idx < best.counts.size() ? best.counts[component_idx] : 0;
for (int i = 0; i < count; ++i)
out.bottom_to_top.emplace_back(ordered_id);
}
while (int(out.bottom_to_top.size()) < depth)
out.bottom_to_top.emplace_back(m_components_bottom_to_top.empty() ? m_component_ids.front() : m_components_bottom_to_top.back());
if (int(out.bottom_to_top.size()) > depth)
out.bottom_to_top.resize(size_t(depth));
return out;
}
unsigned int TextureMappingContoningSolver::component_for_depth(const std::array<float, 3> &target_rgb,
int stack_layers,
int depth_from_top) const
{
TextureMappingContoningStack stack = solve(target_rgb, stack_layers);
if (stack.bottom_to_top.empty())
return 0;
const int depth = int(stack.bottom_to_top.size());
const int idx = std::clamp(depth - 1 - depth_from_top, 0, depth - 1);
return stack.bottom_to_top[size_t(idx)];
}
}

View File

@@ -0,0 +1,67 @@
// original author: sentientstardust
#ifndef slic3r_TextureMappingContoning_hpp_
#define slic3r_TextureMappingContoning_hpp_
#include "TextureMapping.hpp"
#include <array>
#include <map>
#include <optional>
#include <vector>
namespace Slic3r {
class PrintConfig;
struct TextureMappingContoningStack {
std::vector<unsigned int> bottom_to_top;
};
class TextureMappingContoningSolver
{
public:
TextureMappingContoningSolver() = default;
TextureMappingContoningSolver(const TextureMappingZone &zone,
const PrintConfig &config,
std::vector<unsigned int> component_ids);
bool valid() const { return !m_component_ids.empty() && m_component_ids.size() == m_component_colors.size(); }
const std::vector<unsigned int>& component_ids() const { return m_component_ids; }
const std::vector<unsigned int>& components_bottom_to_top() const { return m_components_bottom_to_top; }
TextureMappingContoningStack solve(const std::array<float, 3> &target_rgb, int stack_layers) const;
unsigned int component_for_depth(const std::array<float, 3> &target_rgb, int stack_layers, int depth_from_top) const;
private:
struct Candidate {
std::array<float, 3> rgb { { 0.f, 0.f, 0.f } };
std::array<float, 3> oklab { { 0.f, 0.f, 0.f } };
std::vector<int> counts;
float dark_score { 0.f };
};
const std::vector<Candidate>& candidates_for_depth(int stack_layers) const;
std::vector<unsigned int> m_component_ids;
std::vector<unsigned int> m_components_bottom_to_top;
std::vector<std::array<float, 3>> m_component_colors;
std::vector<float> m_component_luminance;
mutable std::map<int, std::vector<Candidate>> m_candidates_by_depth;
};
std::vector<unsigned int> texture_mapping_contoning_components_bottom_to_top(const TextureMappingZone &zone,
const PrintConfig &config,
std::vector<unsigned int> component_ids);
float texture_mapping_contoning_min_feature_mm(const TextureMappingZone &zone,
const PrintConfig &config,
const std::vector<unsigned int> &component_ids,
float external_width_mm);
bool texture_mapping_contoning_normal_eligible(float normal_z, float threshold_deg);
std::optional<std::array<float, 3>> texture_mapping_contoning_component_colors(const PrintConfig &config,
const std::vector<unsigned int> &component_ids,
std::vector<std::array<float, 3>> &out);
} // namespace Slic3r
#endif

View File

@@ -33,6 +33,7 @@ struct TextureSampleData {
std::array<float, 4> rgba { { 0.f, 0.f, 0.f, 1.f } };
std::vector<float> raw_component_weights;
bool raw_component_weights_from_texture { false };
float normal_z { std::numeric_limits<float>::quiet_NaN() };
};
struct WeightedTextureSample {
@@ -41,6 +42,7 @@ struct WeightedTextureSample {
std::array<float, 4> rgba { { 0.f, 0.f, 0.f, 1.f } };
std::vector<float> raw_component_weights;
bool raw_component_weights_from_texture { false };
float normal_z { std::numeric_limits<float>::quiet_NaN() };
float weight { 0.f };
};
@@ -1412,7 +1414,8 @@ bool accumulate_layer_plane_triangle_samples(const Vec3d &p0,
sample_data.rgba,
sample_weight,
std::move(sample_data.raw_component_weights),
sample_data.raw_component_weights_from_texture);
sample_data.raw_component_weights_from_texture,
sample_data.normal_z);
}
return true;
@@ -1550,7 +1553,8 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
const std::array<float, 4> &rgba,
float sample_weight,
std::vector<float> raw_component_weights = {},
bool raw_component_weights_from_texture = false) {
bool raw_component_weights_from_texture = false,
float normal_z = std::numeric_limits<float>::quiet_NaN()) {
if (!std::isfinite(x_mm) || !std::isfinite(y_mm) || sample_weight <= EPSILON)
return;
if (!std::isfinite(sample_weight) ||
@@ -1561,16 +1565,18 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
return;
if (raw_component_weights.size() != component_count)
raw_component_weights_from_texture = false;
samples.push_back({ x_mm, y_mm, rgba, std::move(raw_component_weights), raw_component_weights_from_texture, sample_weight });
samples.push_back({ x_mm, y_mm, rgba, std::move(raw_component_weights), raw_component_weights_from_texture, normal_z, sample_weight });
};
auto accumulate_constant_surface_triangle_samples = [&](const Vec3d &p0,
const Vec3d &p1,
const Vec3d &p2,
const std::array<float, 4> &rgba) {
const Vec3d normal = (p1 - p0).cross(p2 - p0).normalized();
const float normal_z = normal.allFinite() ? float(normal.z()) : std::numeric_limits<float>::quiet_NaN();
if (accumulate_layer_plane_triangle_samples(p0, p1, p2, layer_z_mm, safe_layer_z_falloff_mm, high_resolution_texture_sampling,
physical_sample_pitch_mm,
[&rgba](const Vec3f &) { return TextureSampleData{ rgba, {}, false }; }, accumulate_sample))
[&rgba, normal_z](const Vec3f &) { return TextureSampleData{ rgba, {}, false, normal_z }; }, accumulate_sample))
return;
const float max_world_edge_mm = std::max({ float((p1 - p0).norm()), float((p2 - p1).norm()), float((p0 - p2).norm()) });
@@ -1601,7 +1607,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
const float z_weight = std::exp(-0.5f * z_norm * z_norm);
if (!std::isfinite(z_weight) || z_weight <= EPSILON)
continue;
accumulate_sample(float(world_pos.x()), float(world_pos.y()), rgba, area_weight * z_weight);
accumulate_sample(float(world_pos.x()), float(world_pos.y()), rgba, area_weight * z_weight, {}, false, normal_z);
}
}
};
@@ -1719,6 +1725,8 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
const Vec3d p2 = volume_trafo * its.vertices[size_t(tri[2])].cast<double>();
if (!p0.allFinite() || !p1.allFinite() || !p2.allFinite())
continue;
const Vec3d normal = (p1 - p0).cross(p2 - p0).normalized();
const float normal_z = normal.allFinite() ? float(normal.z()) : std::numeric_limits<float>::quiet_NaN();
const size_t uv_off = tri_idx * 6;
const std::array<Vec2f, 3> uvs = unwrap_triangle_uvs(
@@ -1728,9 +1736,11 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
if (accumulate_layer_plane_triangle_samples(p0, p1, p2, layer_z_mm, safe_layer_z_falloff_mm, high_resolution_texture_sampling,
physical_sample_pitch_mm,
[&uvs, &sample_data_for_uv](const Vec3f &barycentric) {
[&uvs, &sample_data_for_uv, normal_z](const Vec3f &barycentric) {
const Vec2f uv = uvs[0] * barycentric.x() + uvs[1] * barycentric.y() + uvs[2] * barycentric.z();
return sample_data_for_uv(uv);
TextureSampleData sample_data = sample_data_for_uv(uv);
sample_data.normal_z = normal_z;
return sample_data;
}, accumulate_sample))
continue;
@@ -1774,7 +1784,8 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
sample_data.rgba,
area_weight * z_weight,
std::move(sample_data.raw_component_weights),
sample_data.raw_component_weights_from_texture);
sample_data.raw_component_weights_from_texture,
normal_z);
}
}
}
@@ -1960,6 +1971,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
weight_field.sample_r.resize(sample_count);
weight_field.sample_g.resize(sample_count);
weight_field.sample_b.resize(sample_count);
weight_field.sample_normal_z.resize(sample_count);
weight_field.sample_component_weights.assign(sample_count * component_count, 0.f);
weight_field.raw_component_weights_from_texture = false;
weight_field.binary_dithered = !binary_dither_masks.empty();
@@ -1979,6 +1991,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(
weight_field.sample_r[sample_idx] = target_rgb[0];
weight_field.sample_g[sample_idx] = target_rgb[1];
weight_field.sample_b[sample_idx] = target_rgb[2];
weight_field.sample_normal_z[sample_idx] = sample.normal_z;
std::vector<float> desired(component_count, 0.f);
const bool has_binary_dither = sample_idx < binary_dither_masks.size() && binary_dither_masks[sample_idx] != 0;
@@ -2735,6 +2748,81 @@ std::optional<std::array<float, 3>> sample_weight_field_rgb(const TextureMapping
return std::nullopt;
}
std::optional<float> sample_weight_field_normal_z(const TextureMappingOffsetWeightField &weight_field,
float x_mm,
float y_mm,
bool high_resolution_texture_sampling)
{
if (weight_field.empty() ||
weight_field.sample_normal_z.size() != weight_field.sample_x_mm.size() ||
!std::isfinite(x_mm) ||
!std::isfinite(y_mm))
return std::nullopt;
const float gx = (x_mm - weight_field.min_x_mm) / std::max(weight_field.bucket_width_mm, 1e-6f);
const float gy = (y_mm - weight_field.min_y_mm) / std::max(weight_field.bucket_height_mm, 1e-6f);
const int cx = std::clamp(int(std::floor(gx)), 0, weight_field.bucket_width - 1);
const int cy = std::clamp(int(std::floor(gy)), 0, weight_field.bucket_height - 1);
const float max_radius_mm =
high_resolution_texture_sampling ?
std::max(0.18f, std::max(weight_field.bucket_width_mm, weight_field.bucket_height_mm) * 2.5f) :
std::max(0.32f, std::max(weight_field.bucket_width_mm, weight_field.bucket_height_mm) * 3.0f);
const int max_ring = std::clamp(int(std::ceil(max_radius_mm / std::max(std::min(weight_field.bucket_width_mm,
weight_field.bucket_height_mm),
1e-3f))),
1,
std::max(weight_field.bucket_width, weight_field.bucket_height));
double weighted_normal_z = 0.0;
double total_weight = 0.0;
size_t nearest_sample_idx = size_t(-1);
float nearest_d2 = std::numeric_limits<float>::max();
for (int ring = 0; ring <= max_ring; ++ring) {
const int min_x = std::max(0, cx - ring);
const int max_x = std::min(weight_field.bucket_width - 1, cx + ring);
const int min_y = std::max(0, cy - ring);
const int max_y = std::min(weight_field.bucket_height - 1, cy + ring);
for (int by = min_y; by <= max_y; ++by) {
for (int bx = min_x; bx <= max_x; ++bx) {
const size_t bucket_idx = size_t(by) * size_t(weight_field.bucket_width) + size_t(bx);
if (bucket_idx >= weight_field.buckets.size())
continue;
for (const uint32_t sample_idx_u32 : weight_field.buckets[bucket_idx]) {
const size_t sample_idx = size_t(sample_idx_u32);
if (sample_idx >= weight_field.sample_x_mm.size() || sample_idx >= weight_field.sample_normal_z.size())
continue;
const float normal_z = weight_field.sample_normal_z[sample_idx];
if (!std::isfinite(normal_z))
continue;
const float dx = x_mm - weight_field.sample_x_mm[sample_idx];
const float dy = y_mm - weight_field.sample_y_mm[sample_idx];
const float d2 = dx * dx + dy * dy;
if (d2 < nearest_d2) {
nearest_d2 = d2;
nearest_sample_idx = sample_idx;
}
if (d2 > max_radius_mm * max_radius_mm)
continue;
const float kernel = std::exp(-0.5f * d2 / std::max(max_radius_mm * max_radius_mm * 0.16f, 1e-6f));
const float sample_w = weight_field.sample_weight[sample_idx] * kernel;
if (!std::isfinite(sample_w) || sample_w <= EPSILON)
continue;
weighted_normal_z += double(normal_z) * double(sample_w);
total_weight += double(sample_w);
}
}
}
if (total_weight > EPSILON)
break;
}
if (total_weight > EPSILON)
return std::clamp(float(weighted_normal_z / total_weight), -1.f, 1.f);
if (nearest_sample_idx != size_t(-1) && nearest_sample_idx < weight_field.sample_normal_z.size())
return std::clamp(weight_field.sample_normal_z[nearest_sample_idx], -1.f, 1.f);
return std::nullopt;
}
std::vector<unsigned int> decode_texture_mapping_offset_component_ids(const TextureMappingZone &zone, size_t num_physical)
{
if (zone.is_linear_gradient())
@@ -2817,7 +2905,8 @@ std::optional<TextureMappingOffsetContext> build_texture_mapping_offset_context_
std::optional<float> layer_height_mm_override,
std::optional<Vec2d> plate_origin_mm_override,
std::optional<float> min_outer_width_mm_override,
std::optional<std::array<float, 4>> image_background_rgba_override)
std::optional<std::array<float, 4>> image_background_rgba_override,
std::optional<float> sample_z_mm_override)
{
const Print *print = print_object.print();
if (print == nullptr)
@@ -2958,6 +3047,9 @@ std::optional<TextureMappingOffsetContext> build_texture_mapping_offset_context_
const float layer_height_mm = layer_height_mm_override ?
std::max(0.01f, *layer_height_mm_override) :
std::max(0.01f, float(layer.height));
const float sample_z_mm = sample_z_mm_override && std::isfinite(*sample_z_mm_override) ?
*sample_z_mm_override :
float(layer.print_z);
const float min_width_for_positive_spacing_mm = layer_height_mm * float(1. - 0.25 * PI) + 1e-4f;
const float safe_min_gradient_width_mm = std::clamp(
std::max(config_min_gradient_width_mm, min_width_for_positive_spacing_mm),
@@ -3021,7 +3113,7 @@ std::optional<TextureMappingOffsetContext> build_texture_mapping_offset_context_
component_minimum_offset_factors,
texture_contrast_pct,
texture_tone_gamma,
float(layer.print_z),
sample_z_mm,
layer_sample_falloff_mm,
high_resolution_texture_sampling,
zone.high_speed_image_texture_sampling,

View File

@@ -33,6 +33,7 @@ struct TextureMappingOffsetWeightField {
std::vector<float> sample_r;
std::vector<float> sample_g;
std::vector<float> sample_b;
std::vector<float> sample_normal_z;
std::vector<float> sample_component_weights;
std::vector<std::vector<uint32_t>> buckets;
std::vector<float> fallback_weights;
@@ -105,7 +106,8 @@ std::optional<TextureMappingOffsetContext> build_texture_mapping_offset_context_
std::optional<float> layer_height_mm_override = std::nullopt,
std::optional<Vec2d> plate_origin_mm_override = std::nullopt,
std::optional<float> min_outer_width_mm_override = std::nullopt,
std::optional<std::array<float, 4>> image_background_rgba_override = std::nullopt);
std::optional<std::array<float, 4>> image_background_rgba_override = std::nullopt,
std::optional<float> sample_z_mm_override = std::nullopt);
std::vector<float> sample_weight_field_components(const TextureMappingOffsetWeightField &weight_field,
float x_mm,
@@ -117,6 +119,11 @@ std::optional<std::array<float, 3>> sample_weight_field_rgb(const TextureMapping
float y_mm,
bool high_resolution_texture_sampling);
std::optional<float> sample_weight_field_normal_z(const TextureMappingOffsetWeightField &weight_field,
float x_mm,
float y_mm,
bool high_resolution_texture_sampling);
float texture_mapping_offset_surface_inset_mm(const TextureMappingOffsetContext &context,
const Point &point,
double inward_x,

View File

@@ -1873,6 +1873,11 @@ public:
float top_surface_image_max_line_width_mm,
int top_surface_image_colored_top_layers,
bool top_surface_image_fixed_coloring_filaments,
float top_surface_contoning_angle_threshold_deg,
int top_surface_contoning_stack_layers,
float top_surface_contoning_min_feature_mm,
bool top_surface_contoning_color_lower_surfaces,
bool top_surface_contoning_only_color_surface_infill,
const TextureMappingManager &texture_mapping_zones,
const TextureMappingGlobalSettings &global_settings,
const TextureMappingPrimeTowerImage &prime_tower_image,
@@ -2338,11 +2343,12 @@ public:
wxArrayString top_surface_method_choices;
top_surface_method_choices.Add(_L("45 degree width modulation"));
top_surface_method_choices.Add(_L("Same-layer 45 partition"));
top_surface_method_choices.Add(_L("Contoning"));
m_top_surface_image_method_choice =
new wxChoice(top_surface_page, wxID_ANY, wxDefaultPosition, wxDefaultSize, top_surface_method_choices);
m_top_surface_image_method_choice->SetSelection(std::clamp(top_surface_image_printing_method,
int(TextureMappingZone::TopSurfaceImageSameAngle45Width),
int(TextureMappingZone::TopSurfaceImageSameLayer45Partition)));
int(TextureMappingZone::TopSurfaceImageContoning)));
top_surface_method_row->Add(m_top_surface_image_method_choice, 1, wxALIGN_CENTER_VERTICAL);
top_surface_box->Add(top_surface_method_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap);
auto *top_surface_width_row = new wxBoxSizer(wxHORIZONTAL);
@@ -2410,10 +2416,86 @@ public:
0,
wxALIGN_CENTER_VERTICAL);
top_surface_box->Add(m_top_surface_image_colored_top_layers_panel, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap);
m_top_surface_contoning_panel = new wxPanel(top_surface_page, wxID_ANY);
auto *top_surface_contoning_root = new wxBoxSizer(wxVERTICAL);
m_top_surface_contoning_panel->SetSizer(top_surface_contoning_root);
auto *contoning_angle_row = new wxBoxSizer(wxHORIZONTAL);
contoning_angle_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("Surface angle threshold")),
0,
wxALIGN_CENTER_VERTICAL | wxRIGHT,
gap);
m_top_surface_contoning_angle_threshold_spin =
new wxSpinCtrlDouble(m_top_surface_contoning_panel,
wxID_ANY,
wxEmptyString,
wxDefaultPosition,
wxSize(FromDIP(84), -1),
wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER,
double(TextureMappingZone::MinTopSurfaceContoningAngleThresholdDeg),
double(TextureMappingZone::MaxTopSurfaceContoningAngleThresholdDeg),
std::clamp(double(top_surface_contoning_angle_threshold_deg),
double(TextureMappingZone::MinTopSurfaceContoningAngleThresholdDeg),
double(TextureMappingZone::MaxTopSurfaceContoningAngleThresholdDeg)),
1.0);
m_top_surface_contoning_angle_threshold_spin->SetDigits(0);
contoning_angle_row->Add(m_top_surface_contoning_angle_threshold_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2);
contoning_angle_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("deg")), 0, wxALIGN_CENTER_VERTICAL);
top_surface_contoning_root->Add(contoning_angle_row, 0, wxEXPAND | wxBOTTOM, gap);
auto *contoning_layers_row = new wxBoxSizer(wxHORIZONTAL);
contoning_layers_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("Stack layers")),
0,
wxALIGN_CENTER_VERTICAL | wxRIGHT,
gap);
m_top_surface_contoning_stack_layers_spin =
new wxSpinCtrl(m_top_surface_contoning_panel,
wxID_ANY,
wxEmptyString,
wxDefaultPosition,
wxSize(FromDIP(70), -1),
wxSP_ARROW_KEYS | wxALIGN_RIGHT,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers,
std::clamp(top_surface_contoning_stack_layers,
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers));
contoning_layers_row->Add(m_top_surface_contoning_stack_layers_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2);
contoning_layers_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("layers")), 0, wxALIGN_CENTER_VERTICAL);
top_surface_contoning_root->Add(contoning_layers_row, 0, wxEXPAND | wxBOTTOM, gap);
auto *contoning_feature_row = new wxBoxSizer(wxHORIZONTAL);
contoning_feature_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("Minimum feature")),
0,
wxALIGN_CENTER_VERTICAL | wxRIGHT,
gap);
m_top_surface_contoning_min_feature_spin =
new wxSpinCtrlDouble(m_top_surface_contoning_panel,
wxID_ANY,
wxEmptyString,
wxDefaultPosition,
wxSize(FromDIP(84), -1),
wxSP_ARROW_KEYS | wxALIGN_RIGHT | wxTE_PROCESS_ENTER,
double(TextureMappingZone::MinTopSurfaceContoningMinFeatureMm),
double(TextureMappingZone::MaxTopSurfaceContoningMinFeatureMm),
std::clamp(double(top_surface_contoning_min_feature_mm),
double(TextureMappingZone::MinTopSurfaceContoningMinFeatureMm),
double(TextureMappingZone::MaxTopSurfaceContoningMinFeatureMm)),
0.1);
m_top_surface_contoning_min_feature_spin->SetDigits(1);
contoning_feature_row->Add(m_top_surface_contoning_min_feature_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2);
contoning_feature_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("mm")), 0, wxALIGN_CENTER_VERTICAL);
top_surface_contoning_root->Add(contoning_feature_row, 0, wxEXPAND);
top_surface_box->Add(m_top_surface_contoning_panel, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap);
m_top_surface_image_fixed_coloring_filaments_checkbox =
new wxCheckBox(top_surface_page, wxID_ANY, _L("Fixed top-surface coloring filaments"));
m_top_surface_image_fixed_coloring_filaments_checkbox->SetValue(top_surface_image_fixed_coloring_filaments);
top_surface_box->Add(m_top_surface_image_fixed_coloring_filaments_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP | wxBOTTOM, gap);
top_surface_box->Add(m_top_surface_image_fixed_coloring_filaments_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap);
m_top_surface_contoning_color_lower_surfaces_checkbox =
new wxCheckBox(top_surface_page, wxID_ANY, _L("Also color lower surfaces"));
m_top_surface_contoning_color_lower_surfaces_checkbox->SetValue(top_surface_contoning_color_lower_surfaces);
top_surface_box->Add(m_top_surface_contoning_color_lower_surfaces_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap);
m_top_surface_contoning_only_color_surface_infill_checkbox =
new wxCheckBox(top_surface_page, wxID_ANY, _L("Only color surface infill"));
m_top_surface_contoning_only_color_surface_infill_checkbox->SetValue(top_surface_contoning_only_color_surface_infill);
top_surface_box->Add(m_top_surface_contoning_only_color_surface_infill_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP | wxBOTTOM, gap);
m_top_surface_image_printing_enabled_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) {
update_top_surface_image_options_visibility(false);
});
@@ -2682,7 +2764,7 @@ public:
return m_top_surface_image_method_choice ?
std::clamp(m_top_surface_image_method_choice->GetSelection(),
int(TextureMappingZone::TopSurfaceImageSameAngle45Width),
int(TextureMappingZone::TopSurfaceImageSameLayer45Partition)) :
int(TextureMappingZone::TopSurfaceImageContoning)) :
TextureMappingZone::DefaultTopSurfaceImagePrintingMethod;
}
float top_surface_image_min_line_width_mm() const
@@ -2721,6 +2803,40 @@ public:
return m_top_surface_image_fixed_coloring_filaments_checkbox == nullptr ||
m_top_surface_image_fixed_coloring_filaments_checkbox->GetValue();
}
float top_surface_contoning_angle_threshold_deg() const
{
return float(std::clamp(m_top_surface_contoning_angle_threshold_spin != nullptr ?
m_top_surface_contoning_angle_threshold_spin->GetValue() :
double(TextureMappingZone::DefaultTopSurfaceContoningAngleThresholdDeg),
double(TextureMappingZone::MinTopSurfaceContoningAngleThresholdDeg),
double(TextureMappingZone::MaxTopSurfaceContoningAngleThresholdDeg)));
}
int top_surface_contoning_stack_layers() const
{
return m_top_surface_contoning_stack_layers_spin ?
std::clamp(m_top_surface_contoning_stack_layers_spin->GetValue(),
TextureMappingZone::MinTopSurfaceContoningStackLayers,
TextureMappingZone::MaxTopSurfaceContoningStackLayers) :
TextureMappingZone::DefaultTopSurfaceContoningStackLayers;
}
float top_surface_contoning_min_feature_mm() const
{
return float(std::clamp(m_top_surface_contoning_min_feature_spin != nullptr ?
m_top_surface_contoning_min_feature_spin->GetValue() :
double(TextureMappingZone::DefaultTopSurfaceContoningMinFeatureMm),
double(TextureMappingZone::MinTopSurfaceContoningMinFeatureMm),
double(TextureMappingZone::MaxTopSurfaceContoningMinFeatureMm)));
}
bool top_surface_contoning_color_lower_surfaces() const
{
return m_top_surface_contoning_color_lower_surfaces_checkbox == nullptr ||
m_top_surface_contoning_color_lower_surfaces_checkbox->GetValue();
}
bool top_surface_contoning_only_color_surface_infill() const
{
return m_top_surface_contoning_only_color_surface_infill_checkbox != nullptr &&
m_top_surface_contoning_only_color_surface_infill_checkbox->GetValue();
}
bool minimum_visibility_offset_enabled() const
{
return m_minimum_visibility_offset_checkbox && m_minimum_visibility_offset_checkbox->GetValue();
@@ -3106,6 +3222,15 @@ private:
m_top_surface_image_min_line_width_spin->Enable(enabled);
if (m_top_surface_image_max_line_width_spin != nullptr)
m_top_surface_image_max_line_width_spin->Enable(enabled);
const bool contoning =
enabled &&
m_top_surface_image_method_choice != nullptr &&
m_top_surface_image_method_choice->GetSelection() == int(TextureMappingZone::TopSurfaceImageContoning);
if (contoning && m_modulation_mode_choice != nullptr) {
m_modulation_mode_choice->SetSelection(int(TextureMappingZone::ModulationPerimeterPathV2));
m_modulation_mode_manually_changed = true;
update_modulation_mode_options_visibility(false);
}
const bool same_layer =
enabled &&
m_top_surface_image_method_choice != nullptr &&
@@ -3114,6 +3239,22 @@ private:
m_top_surface_image_colored_top_layers_panel->Show(same_layer);
if (m_top_surface_image_colored_top_layers_spin != nullptr)
m_top_surface_image_colored_top_layers_spin->Enable(same_layer);
if (m_top_surface_contoning_panel != nullptr)
m_top_surface_contoning_panel->Show(contoning);
if (m_top_surface_contoning_angle_threshold_spin != nullptr)
m_top_surface_contoning_angle_threshold_spin->Enable(contoning);
if (m_top_surface_contoning_stack_layers_spin != nullptr)
m_top_surface_contoning_stack_layers_spin->Enable(contoning);
if (m_top_surface_contoning_min_feature_spin != nullptr)
m_top_surface_contoning_min_feature_spin->Enable(contoning);
if (m_top_surface_contoning_color_lower_surfaces_checkbox != nullptr) {
m_top_surface_contoning_color_lower_surfaces_checkbox->Show(contoning);
m_top_surface_contoning_color_lower_surfaces_checkbox->Enable(contoning);
}
if (m_top_surface_contoning_only_color_surface_infill_checkbox != nullptr) {
m_top_surface_contoning_only_color_surface_infill_checkbox->Show(contoning);
m_top_surface_contoning_only_color_surface_infill_checkbox->Enable(contoning);
}
if (m_top_surface_image_fixed_coloring_filaments_checkbox != nullptr)
m_top_surface_image_fixed_coloring_filaments_checkbox->Enable(enabled);
layout_current_options_page();
@@ -3165,6 +3306,12 @@ private:
wxPanel *m_top_surface_image_colored_top_layers_panel {nullptr};
wxSpinCtrl *m_top_surface_image_colored_top_layers_spin {nullptr};
wxCheckBox *m_top_surface_image_fixed_coloring_filaments_checkbox {nullptr};
wxPanel *m_top_surface_contoning_panel {nullptr};
wxSpinCtrlDouble *m_top_surface_contoning_angle_threshold_spin {nullptr};
wxSpinCtrl *m_top_surface_contoning_stack_layers_spin {nullptr};
wxSpinCtrlDouble *m_top_surface_contoning_min_feature_spin {nullptr};
wxCheckBox *m_top_surface_contoning_color_lower_surfaces_checkbox {nullptr};
wxCheckBox *m_top_surface_contoning_only_color_surface_infill_checkbox {nullptr};
wxCheckBox *m_use_legacy_fixed_color_mode_checkbox {nullptr};
wxCheckBox *m_minimum_visibility_offset_checkbox {nullptr};
wxSpinCtrl *m_minimum_visibility_offset_spin {nullptr};
@@ -8114,6 +8261,11 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
updated.top_surface_image_max_line_width_mm,
updated.top_surface_image_colored_top_layers,
updated.top_surface_image_fixed_coloring_filaments,
updated.top_surface_contoning_angle_threshold_deg,
updated.top_surface_contoning_stack_layers,
updated.top_surface_contoning_min_feature_mm,
updated.top_surface_contoning_color_lower_surfaces,
updated.top_surface_contoning_only_color_surface_infill,
bundle->texture_mapping_zones,
bundle->texture_mapping_global_settings,
wxGetApp().model().texture_mapping_prime_tower_image,
@@ -8163,6 +8315,16 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager)
updated.top_surface_image_max_line_width_mm = dlg.top_surface_image_max_line_width_mm();
updated.top_surface_image_colored_top_layers = dlg.top_surface_image_colored_top_layers();
updated.top_surface_image_fixed_coloring_filaments = dlg.top_surface_image_fixed_coloring_filaments();
updated.top_surface_contoning_angle_threshold_deg = dlg.top_surface_contoning_angle_threshold_deg();
updated.top_surface_contoning_stack_layers = dlg.top_surface_contoning_stack_layers();
updated.top_surface_contoning_min_feature_mm = dlg.top_surface_contoning_min_feature_mm();
updated.top_surface_contoning_color_lower_surfaces = dlg.top_surface_contoning_color_lower_surfaces();
updated.top_surface_contoning_only_color_surface_infill = dlg.top_surface_contoning_only_color_surface_infill();
if (updated.top_surface_image_printing_enabled &&
updated.top_surface_image_printing_method == int(TextureMappingZone::TopSurfaceImageContoning)) {
updated.modulation_mode = int(TextureMappingZone::ModulationPerimeterPathV2);
updated.modulation_mode_manually_changed = true;
}
if (updated.dithering_enabled)
updated.compact_offset_mode = true;
updated.minimum_visibility_offset_enabled = dlg.minimum_visibility_offset_enabled();