Add contoning top surface coloring method
This commit is contained in:
@@ -480,6 +480,8 @@ set(lisbslic3r_sources
|
||||
TriangleSetSampling.hpp
|
||||
TextureMappingOffset.cpp
|
||||
TextureMappingOffset.hpp
|
||||
TextureMappingContoning.cpp
|
||||
TextureMappingContoning.hpp
|
||||
TextureMapping.cpp
|
||||
TextureMapping.hpp
|
||||
TriangulateWall.cpp
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
namespace Slic3r {
|
||||
using RGB = std::array<float, 3>;
|
||||
|
||||
@@ -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
@@ -104,28 +104,38 @@ bool Fill::use_bridge_flow(const InfillPattern type)
|
||||
|
||||
Polylines Fill::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
params.check_canceled();
|
||||
if (!infill_ordered.empty()) {
|
||||
if (params.dont_connect()) {
|
||||
if (infill_ordered.size() > 1)
|
||||
|
||||
@@ -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).");
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
269
src/libslic3r/TextureMappingContoning.cpp
Normal file
269
src/libslic3r/TextureMappingContoning.cpp
Normal 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)];
|
||||
}
|
||||
|
||||
}
|
||||
67
src/libslic3r/TextureMappingContoning.hpp
Normal file
67
src/libslic3r/TextureMappingContoning.hpp
Normal 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
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user