diff --git a/src/libslic3r/ColorSolver.cpp b/src/libslic3r/ColorSolver.cpp index 126d1acaf4c..73b5ca26058 100644 --- a/src/libslic3r/ColorSolver.cpp +++ b/src/libslic3r/ColorSolver.cpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace Slic3r { namespace { @@ -355,6 +356,176 @@ size_t ordered_stack_candidate_count(size_t component_count, int stack_depth, si return candidate_limit > 0 && count > candidate_limit ? 0 : count; } +size_t ordered_stack_candidate_storage_limit(int stack_depth, size_t candidate_limit, size_t stack_item_limit) +{ + size_t limit = candidate_limit > 0 ? candidate_limit : std::numeric_limits::max(); + if (stack_item_limit > 0 && stack_depth > 0) + limit = std::min(limit, stack_item_limit / size_t(stack_depth)); + return limit; +} + +int ordered_stack_control_depth(size_t component_count, int stack_depth, size_t candidate_limit) +{ + if (component_count == 0 || stack_depth <= 0 || candidate_limit == 0) + return 0; + size_t count = 1; + int depth = 0; + for (int idx = 0; idx < stack_depth; ++idx) { + if (count > candidate_limit / component_count) + break; + count *= component_count; + ++depth; + } + return depth > 0 ? depth : 1; +} + +std::vector repeat_ordered_stack_to_depth(const std::vector &surface_to_deep, int depth) +{ + std::vector out; + if (surface_to_deep.empty() || depth <= 0) + return out; + out.reserve(size_t(depth)); + for (int idx = 0; idx < depth; ++idx) + out.emplace_back(surface_to_deep[size_t(idx) % surface_to_deep.size()]); + return out; +} + +void append_unique_ordered_stack_variant(std::vector> &variants, std::vector candidate) +{ + if (candidate.empty()) + return; + if (std::find(variants.begin(), variants.end(), candidate) == variants.end()) + variants.emplace_back(std::move(candidate)); +} + +std::vector stretch_ordered_stack_tiled(const std::vector &control, int stack_depth) +{ + return repeat_ordered_stack_to_depth(control, stack_depth); +} + +std::vector stretch_ordered_stack_proportional(const std::vector &control, int stack_depth) +{ + std::vector out; + if (control.empty() || stack_depth <= 0) + return out; + out.reserve(size_t(stack_depth)); + const size_t control_depth = control.size(); + for (int idx = 0; idx < stack_depth; ++idx) { + const size_t source_idx = std::min(control_depth - 1, size_t(idx) * control_depth / size_t(stack_depth)); + out.emplace_back(control[source_idx]); + } + return out; +} + +std::vector stretch_ordered_stack_by_scores(const std::vector &control, + const std::vector &scores, + int stack_depth) +{ + std::vector out; + if (control.empty() || stack_depth <= 0) + return out; + if (int(control.size()) >= stack_depth) { + out.assign(control.begin(), control.begin() + std::min(control.size(), size_t(stack_depth))); + return out; + } + + std::vector duplicates(control.size(), 0); + const int extra = stack_depth - int(control.size()); + for (int idx = 0; idx < extra; ++idx) { + size_t best_idx = 0; + float best_score = -std::numeric_limits::max(); + for (size_t score_idx = 0; score_idx < control.size(); ++score_idx) { + const float base_score = + score_idx < scores.size() && std::isfinite(scores[score_idx]) ? + std::max(scores[score_idx], 0.f) : + 0.f; + const float score = base_score / float(duplicates[score_idx] + 1); + if (score > best_score) { + best_score = score; + best_idx = score_idx; + } + } + ++duplicates[best_idx]; + } + + out.reserve(size_t(stack_depth)); + for (size_t idx = 0; idx < control.size(); ++idx) { + out.emplace_back(control[idx]); + for (int duplicate_idx = 0; duplicate_idx < duplicates[idx]; ++duplicate_idx) + out.emplace_back(control[idx]); + } + return out; +} + +std::vector> stretched_ordered_stack_variants(const std::vector &control, + const std::vector &layer_opacities, + int stack_depth) +{ + std::vector> variants; + if (control.empty() || stack_depth <= 0) + return variants; + if (int(control.size()) >= stack_depth) { + std::vector variant(control.begin(), control.begin() + std::min(control.size(), size_t(stack_depth))); + append_unique_ordered_stack_variant(variants, std::move(variant)); + return variants; + } + + append_unique_ordered_stack_variant(variants, stretch_ordered_stack_tiled(control, stack_depth)); + append_unique_ordered_stack_variant(variants, stretch_ordered_stack_proportional(control, stack_depth)); + + std::vector even(control.size(), 1.f); + std::vector opacity(control.size(), 1.f); + std::vector surface(control.size(), 1.f); + std::vector deep(control.size(), 1.f); + for (size_t idx = 0; idx < control.size(); ++idx) { + const size_t component_idx = size_t(control[idx]); + const float layer_opacity = + component_idx < layer_opacities.size() && std::isfinite(layer_opacities[component_idx]) ? + std::clamp(layer_opacities[component_idx], 1e-4f, 0.9999f) : + 0.5f; + opacity[idx] = 1.f - layer_opacity; + surface[idx] = float(control.size() - idx); + deep[idx] = float(idx + 1); + } + + append_unique_ordered_stack_variant(variants, stretch_ordered_stack_by_scores(control, even, stack_depth)); + append_unique_ordered_stack_variant(variants, stretch_ordered_stack_by_scores(control, opacity, stack_depth)); + append_unique_ordered_stack_variant(variants, stretch_ordered_stack_by_scores(control, surface, stack_depth)); + append_unique_ordered_stack_variant(variants, stretch_ordered_stack_by_scores(control, deep, stack_depth)); + for (size_t idx = 0; idx < control.size(); ++idx) { + surface[idx] *= opacity[idx]; + deep[idx] *= opacity[idx]; + } + append_unique_ordered_stack_variant(variants, stretch_ordered_stack_by_scores(control, surface, stack_depth)); + append_unique_ordered_stack_variant(variants, stretch_ordered_stack_by_scores(control, deep, stack_depth)); + return variants; +} + +void append_ordered_stack_candidate(ColorSolverOrderedStackCandidateSet &candidates, + const std::vector> &colors_with_background, + std::vector &weights, + const std::vector &surface_to_deep, + const std::vector &layer_opacities, + int simulated_stack_depth) +{ + std::vector simulated_surface_to_deep; + const std::vector *mix_stack = &surface_to_deep; + if (simulated_stack_depth > 0 && simulated_stack_depth != int(surface_to_deep.size())) { + simulated_surface_to_deep = repeat_ordered_stack_to_depth(surface_to_deep, simulated_stack_depth); + mix_stack = &simulated_surface_to_deep; + } + const std::array mixed = + mix_ordered_stack_with_buffers(colors_with_background, weights, *mix_stack, layer_opacities); + const std::array perceptual = oklab_from_srgb(mixed); + candidates.rgbs.emplace_back(mixed[0]); + candidates.rgbs.emplace_back(mixed[1]); + candidates.rgbs.emplace_back(mixed[2]); + candidates.perceptual_coords.emplace_back(perceptual[0]); + candidates.perceptual_coords.emplace_back(perceptual[1]); + candidates.perceptual_coords.emplace_back(perceptual[2]); + candidates.stacks.insert(candidates.stacks.end(), surface_to_deep.begin(), surface_to_deep.end()); +} + } // namespace ColorSolverMixModel color_solver_mix_model_from_index(int model) @@ -552,13 +723,17 @@ std::string color_solver_ordered_stack_candidate_cache_key(const std::vector &background_rgb, ColorSolverMixModel mix_model, int stack_depth, - size_t candidate_limit) + int simulated_stack_depth, + size_t candidate_limit, + size_t stack_item_limit) { std::ostringstream key; key << component_colors.size(); key << "|mx" << mix_model_index(mix_model); key << "|sd" << stack_depth; + key << "|vd" << simulated_stack_depth; key << "|lim" << candidate_limit; + key << "|item" << stack_item_limit; key << "|bg" << int(std::lround(clamp01(background_rgb[0]) * 65535.f)) << ',' << int(std::lround(clamp01(background_rgb[1]) * 65535.f)) << ',' @@ -586,47 +761,101 @@ ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates( const std::array &background_rgb, ColorSolverMixModel mix_model, int stack_depth, - size_t candidate_limit) + int simulated_stack_depth, + size_t candidate_limit, + size_t stack_item_limit) { (void) mix_model; ColorSolverOrderedStackCandidateSet candidates; - if (component_colors.empty() || stack_depth <= 0 || component_colors.size() > size_t(std::numeric_limits::max())) + int simulated_depth = simulated_stack_depth > 0 ? simulated_stack_depth : stack_depth; + if (component_colors.empty() || stack_depth <= 0 || simulated_depth <= 0 || + component_colors.size() > size_t(std::numeric_limits::max())) return candidates; const size_t component_count = component_colors.size(); - const size_t candidate_count = ordered_stack_candidate_count(component_count, stack_depth, candidate_limit); - if (candidate_count == 0) + stack_depth = std::min(stack_depth, simulated_depth); + const size_t max_candidate_count = ordered_stack_candidate_storage_limit(stack_depth, candidate_limit, stack_item_limit); + if (max_candidate_count == 0) return candidates; candidates.component_count = component_count; candidates.stack_depth = stack_depth; - candidates.rgbs.reserve(candidate_count * 3); - candidates.perceptual_coords.reserve(candidate_count * 3); - candidates.stacks.reserve(candidate_count * size_t(stack_depth)); + candidates.simulated_stack_depth = simulated_depth; std::vector> colors_with_background = component_colors; colors_with_background.emplace_back(background_rgb); std::vector weights(colors_with_background.size(), 0.f); - std::vector surface_to_deep(size_t(stack_depth), 0); + const size_t exact_candidate_count = ordered_stack_candidate_count(component_count, stack_depth, candidate_limit); + if (exact_candidate_count > 0 && exact_candidate_count <= max_candidate_count) { + candidates.rgbs.reserve(exact_candidate_count * 3); + candidates.perceptual_coords.reserve(exact_candidate_count * 3); + candidates.stacks.reserve(exact_candidate_count * size_t(stack_depth)); + + std::vector surface_to_deep(size_t(stack_depth), 0); + std::function recurse = [&](int depth_idx) { + if (depth_idx == stack_depth) { + append_ordered_stack_candidate(candidates, + colors_with_background, + weights, + surface_to_deep, + layer_opacities, + simulated_depth); + return; + } + + for (size_t component_idx = 0; component_idx < component_count; ++component_idx) { + surface_to_deep[size_t(depth_idx)] = uint16_t(component_idx); + recurse(depth_idx + 1); + } + }; + recurse(0); + build_color_solver_kd_trees(candidates); + return candidates; + } + if (candidate_limit == 0 && stack_item_limit == 0) + return candidates; + + const size_t variant_budget = 8; + const size_t max_control_candidates = std::max(1, max_candidate_count / variant_budget); + const int control_depth = std::min(stack_depth, ordered_stack_control_depth(component_count, stack_depth, max_control_candidates)); + if (control_depth <= 0) + return candidates; + + const size_t control_candidate_count = ordered_stack_candidate_count(component_count, control_depth, 0); + const size_t reserve_count = std::min(max_candidate_count, control_candidate_count * variant_budget); + candidates.rgbs.reserve(reserve_count * 3); + candidates.perceptual_coords.reserve(reserve_count * 3); + candidates.stacks.reserve(reserve_count * size_t(stack_depth)); + + bool limit_reached = false; + std::vector control(size_t(control_depth), 0); std::function recurse = [&](int depth_idx) { - if (depth_idx == stack_depth) { - const std::array mixed = - mix_ordered_stack_with_buffers(colors_with_background, weights, surface_to_deep, layer_opacities); - const std::array perceptual = oklab_from_srgb(mixed); - candidates.rgbs.emplace_back(mixed[0]); - candidates.rgbs.emplace_back(mixed[1]); - candidates.rgbs.emplace_back(mixed[2]); - candidates.perceptual_coords.emplace_back(perceptual[0]); - candidates.perceptual_coords.emplace_back(perceptual[1]); - candidates.perceptual_coords.emplace_back(perceptual[2]); - candidates.stacks.insert(candidates.stacks.end(), surface_to_deep.begin(), surface_to_deep.end()); + if (limit_reached) + return; + if (depth_idx == control_depth) { + std::vector> variants = + stretched_ordered_stack_variants(control, layer_opacities, stack_depth); + for (const std::vector &surface_to_deep : variants) { + if (candidates.rgbs.size() / 3 >= max_candidate_count) { + limit_reached = true; + return; + } + append_ordered_stack_candidate(candidates, + colors_with_background, + weights, + surface_to_deep, + layer_opacities, + simulated_depth); + } return; } for (size_t component_idx = 0; component_idx < component_count; ++component_idx) { - surface_to_deep[size_t(depth_idx)] = uint16_t(component_idx); + control[size_t(depth_idx)] = uint16_t(component_idx); recurse(depth_idx + 1); + if (limit_reached) + return; } }; recurse(0); @@ -641,7 +870,9 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates const std::array &background_rgb, ColorSolverMixModel mix_model, int stack_depth, - size_t candidate_limit) + int simulated_stack_depth, + size_t candidate_limit, + size_t stack_item_limit) { const std::string key = color_solver_ordered_stack_candidate_cache_key(component_colors, @@ -649,7 +880,9 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates background_rgb, mix_model, stack_depth, - candidate_limit); + simulated_stack_depth, + candidate_limit, + stack_item_limit); auto it = cache.find(key); if (it != cache.end()) return it->second; @@ -660,7 +893,9 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates background_rgb, mix_model, stack_depth, - candidate_limit)).first->second; + simulated_stack_depth, + candidate_limit, + stack_item_limit)).first->second; } std::vector solve_color_solver_ordered_stack_for_target( diff --git a/src/libslic3r/ColorSolver.hpp b/src/libslic3r/ColorSolver.hpp index eb76c9eb146..6374285aa9f 100644 --- a/src/libslic3r/ColorSolver.hpp +++ b/src/libslic3r/ColorSolver.hpp @@ -74,6 +74,7 @@ struct ColorSolverOrderedStackCandidateSet { size_t component_count { 0 }; int stack_depth { 0 }; + int simulated_stack_depth { 0 }; std::vector rgbs; std::vector perceptual_coords; std::vector stacks; @@ -130,14 +131,18 @@ std::string color_solver_ordered_stack_candidate_cache_key(const std::vector &background_rgb, ColorSolverMixModel mix_model, int stack_depth, - size_t candidate_limit = 0); + int simulated_stack_depth = 0, + size_t candidate_limit = 0, + size_t stack_item_limit = 0); ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates( const std::vector> &component_colors, const std::vector &layer_opacities, const std::array &background_rgb, ColorSolverMixModel mix_model, int stack_depth, - size_t candidate_limit = 0); + int simulated_stack_depth = 0, + size_t candidate_limit = 0, + size_t stack_item_limit = 0); const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates( ColorSolverOrderedStackCandidateCache &cache, const std::vector> &component_colors, @@ -145,7 +150,9 @@ const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates const std::array &background_rgb, ColorSolverMixModel mix_model, int stack_depth, - size_t candidate_limit = 0); + int simulated_stack_depth = 0, + size_t candidate_limit = 0, + size_t stack_item_limit = 0); std::vector solve_color_solver_ordered_stack_for_target( const ColorSolverOrderedStackCandidateSet &candidates, const std::array &target_rgb, diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 8e87fb2d277..abb41022e2b 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -1952,19 +1952,21 @@ static std::optional top_surface_image_conto static std::optional top_surface_image_contoning_solve_label( const std::array &rgb, int solve_layers, + int visible_layers, const TextureMappingContoningSolver &solver, bool lower_surface, std::vector &labels, - std::map, int> &label_by_stack) + std::map, int>, int> &label_by_stack) { - TextureMappingContoningStack stack = solver.solve(rgb, solve_layers, lower_surface); + TextureMappingContoningStack stack = solver.solve(rgb, solve_layers, lower_surface, visible_layers); if (stack.bottom_to_top.empty()) return std::nullopt; std::optional> stack_rgb = - solver.stack_rgb(stack.bottom_to_top, lower_surface); + solver.stack_rgb(stack.bottom_to_top, lower_surface, visible_layers); if (!stack_rgb) return std::nullopt; - auto label_it = label_by_stack.find(stack.bottom_to_top); + const auto label_key = std::make_pair(stack.bottom_to_top, std::max(0, visible_layers)); + auto label_it = label_by_stack.find(label_key); int label = -1; if (label_it == label_by_stack.end()) { TopSurfaceImageContoningVectorLabel label_data; @@ -1973,7 +1975,7 @@ static std::optional top_surface_image_cont label_data.oklab = color_solver_oklab_from_srgb(*stack_rgb); label = int(labels.size()); labels.emplace_back(std::move(label_data)); - label_by_stack.emplace(labels.back().bottom_to_top, label); + label_by_stack.emplace(label_key, label); } else { label = label_it->second; } @@ -2104,12 +2106,13 @@ static std::vector top_surface_image_conto std::vector grid(size_t(cols) * size_t(rows), -1); std::vector labels; - std::map, int> label_by_stack; + std::map, int>, int> label_by_stack; - auto solve_cell = [&](int row, int col, const std::array &target_rgb, int solve_layers) { + auto solve_cell = [&](int row, int col, const std::array &target_rgb, int solve_layers, int available_depth) { std::optional solved = top_surface_image_contoning_solve_label(target_rgb, solve_layers, + available_depth, solver, source_surface == TopSurfaceImageSourceSurface::Bottom && plan.contoning_td_adjustment_enabled, @@ -2178,7 +2181,7 @@ static std::vector top_surface_image_conto std::clamp(sample->rgb[2] + errors[grid_idx][2] + top_surface_image_contoning_jitter(col, row, depth, 2), 0.f, 1.f) }; const std::optional solved = - solve_cell(row, col, target_rgb, sample->solve_layers); + solve_cell(row, col, target_rgb, sample->solve_layers, sample->available_depth); if (!solved) continue; const std::array error { @@ -2207,7 +2210,7 @@ static std::vector top_surface_image_conto const std::optional &sample = cell_samples[size_t(row * cols + col)]; if (!sample) continue; - solve_cell(row, col, sample->rgb, sample->solve_layers); + solve_cell(row, col, sample->rgb, sample->solve_layers, sample->available_depth); } } } @@ -2294,12 +2297,13 @@ static std::shared_ptr top_surface_imag out->rows = rows; out->cells.assign(size_t(cols) * size_t(rows), TopSurfaceImageContoningStackPlanCell()); - std::map, int> label_by_stack; + std::map, int>, int> label_by_stack; auto solve_cell = [&](int row, int col, const std::array &target_rgb, int solve_layers, int available_depth) { std::optional solved = top_surface_image_contoning_solve_label(target_rgb, solve_layers, + available_depth, solver, source_surface == TopSurfaceImageSourceSurface::Bottom && plan.contoning_td_adjustment_enabled, diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp index a1cba4cd1e8..e671c28aea0 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -181,10 +181,10 @@ struct TextureMappingZone 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 = 15; + static constexpr int MaxTopSurfaceContoningStackLayers = 100; + static constexpr int DefaultTopSurfaceContoningStackLayers = 100; static constexpr int MinTopSurfaceContoningPatternFilaments = 1; - static constexpr int MaxTopSurfaceContoningPatternFilaments = 20; + static constexpr int MaxTopSurfaceContoningPatternFilaments = 100; static constexpr int DefaultTopSurfaceContoningPatternFilaments = 5; static constexpr float MinTopSurfaceContoningMinFeatureMm = 0.f; static constexpr float MaxTopSurfaceContoningMinFeatureMm = 20.f; diff --git a/src/libslic3r/TextureMappingContoning.cpp b/src/libslic3r/TextureMappingContoning.cpp index 04d88ed9634..c4150bcf267 100644 --- a/src/libslic3r/TextureMappingContoning.cpp +++ b/src/libslic3r/TextureMappingContoning.cpp @@ -19,6 +19,7 @@ namespace { constexpr float OPAQUE_CONTONING_TD_THRESHOLD_MM = 0.5f; constexpr float INFERRED_BLACK_TD_MM = 0.1f; constexpr size_t MAX_TD_ORDERED_CONTONING_CANDIDATES = 2500000; +constexpr size_t MAX_TD_ORDERED_CONTONING_STACK_ITEMS = 20000000; float clamp01(float value) { @@ -422,7 +423,8 @@ std::optional TextureMappingContoningSolver::component_index(unsigned in std::optional> TextureMappingContoningSolver::stack_rgb( const std::vector &bottom_to_top, - bool lower_surface) const + bool lower_surface, + int visible_stack_layers) const { if (bottom_to_top.empty() || !valid()) return std::nullopt; @@ -443,8 +445,13 @@ std::optional> TextureMappingContoningSolver::stack_rgb( return mix_color_solver_components(colors, weights, ColorSolverMixModel::PigmentPainter); } + const int visible_depth = visible_stack_layers > 0 ? + std::clamp(visible_stack_layers, + TextureMappingZone::MinTopSurfaceContoningStackLayers, + TextureMappingZone::MaxTopSurfaceContoningStackLayers) : + int(bottom_to_top.size()); std::vector surface_to_deep; - surface_to_deep.reserve(bottom_to_top.size()); + surface_to_deep.reserve(size_t(visible_depth)); auto append_component = [this, &surface_to_deep](unsigned int component_id) { const std::optional idx = component_index(component_id); if (!idx || *idx > size_t(std::numeric_limits::max())) @@ -461,6 +468,13 @@ std::optional> TextureMappingContoningSolver::stack_rgb( if (!append_component(*it)) return std::nullopt; } + if (visible_depth > 0 && int(surface_to_deep.size()) != visible_depth) { + std::vector repeated; + repeated.reserve(size_t(visible_depth)); + for (int idx = 0; idx < visible_depth; ++idx) + repeated.emplace_back(surface_to_deep[size_t(idx) % surface_to_deep.size()]); + surface_to_deep = std::move(repeated); + } return mix_color_solver_ordered_stack(m_component_colors, surface_to_deep, @@ -536,15 +550,22 @@ void TextureMappingContoningSolver::arrange_stack_for_light_path(std::vector &target_rgb, int stack_layers, - bool lower_surface) const + bool lower_surface, + int visible_stack_layers) const { TextureMappingContoningStack out; if (!valid()) return out; - const int depth = std::clamp(stack_layers, - TextureMappingZone::MinTopSurfaceContoningStackLayers, - TextureMappingZone::MaxTopSurfaceContoningStackLayers); + const int requested_depth = std::clamp(stack_layers, + TextureMappingZone::MinTopSurfaceContoningStackLayers, + TextureMappingZone::MaxTopSurfaceContoningStackLayers); + const int visible_depth = visible_stack_layers > 0 ? + std::clamp(visible_stack_layers, + TextureMappingZone::MinTopSurfaceContoningStackLayers, + TextureMappingZone::MaxTopSurfaceContoningStackLayers) : + requested_depth; + const int depth = std::min(requested_depth, visible_depth); if (m_td_adjustment_enabled) { const ColorSolverOrderedStackCandidateSet *ordered_candidates = nullptr; { @@ -556,7 +577,9 @@ TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::arr m_background_rgb, ColorSolverMixModel::PigmentPainter, depth, - MAX_TD_ORDERED_CONTONING_CANDIDATES); + visible_depth, + MAX_TD_ORDERED_CONTONING_CANDIDATES, + MAX_TD_ORDERED_CONTONING_STACK_ITEMS); } const std::vector surface_to_deep = solve_color_solver_ordered_stack_for_target(*ordered_candidates, target_rgb, ColorSolverMode::V2); diff --git a/src/libslic3r/TextureMappingContoning.hpp b/src/libslic3r/TextureMappingContoning.hpp index 983e152f458..15b07d889d1 100644 --- a/src/libslic3r/TextureMappingContoning.hpp +++ b/src/libslic3r/TextureMappingContoning.hpp @@ -35,9 +35,14 @@ public: const std::vector& component_ids() const { return m_component_ids; } const std::vector& components_bottom_to_top() const { return m_components_bottom_to_top; } - TextureMappingContoningStack solve(const std::array &target_rgb, int stack_layers, bool lower_surface = false) const; + TextureMappingContoningStack solve(const std::array &target_rgb, + int stack_layers, + bool lower_surface = false, + int visible_stack_layers = 0) const; unsigned int component_for_depth(const std::array &target_rgb, int stack_layers, int depth_from_top, bool lower_surface = false) const; - std::optional> stack_rgb(const std::vector &bottom_to_top, bool lower_surface = false) const; + std::optional> stack_rgb(const std::vector &bottom_to_top, + bool lower_surface = false, + int visible_stack_layers = 0) const; private: struct Candidate { diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp index 579508f3767..0d4a07d48b7 100644 --- a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp +++ b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp @@ -46,6 +46,7 @@ constexpr size_t k_surface_gradient_preview_max_components = 10; constexpr size_t k_surface_gradient_preview_lut_size = 33; constexpr size_t k_contoning_flat_surface_preview_max_candidates = 250000; constexpr size_t k_contoning_flat_surface_preview_max_ordered_candidates = 2500000; +constexpr size_t k_contoning_flat_surface_preview_max_ordered_stack_items = 20000000; constexpr double k_contoning_top_surface_preview_lod_max_samples = 350000.0; constexpr const char *TEXTURE_MAPPING_BACKGROUND_COLOR_CONFIG_KEY = "texture_mapping_background_color"; constexpr float k_contoning_preview_inferred_black_td_mm = 0.1f; @@ -2493,12 +2494,21 @@ std::array contoning_flat_surface_rgb_for_texture_preview( if (settings.contoning_flat_surface_td_adjustment && !ordered_candidates.empty()) { const std::vector surface_to_deep = solve_color_solver_ordered_stack_for_target(ordered_candidates, target_rgb, ColorSolverMode::V2); - if (!surface_to_deep.empty()) + if (!surface_to_deep.empty()) { + std::vector simulated_surface_to_deep = surface_to_deep; + if (ordered_candidates.simulated_stack_depth > 0 && + ordered_candidates.simulated_stack_depth != int(surface_to_deep.size())) { + simulated_surface_to_deep.clear(); + simulated_surface_to_deep.reserve(size_t(ordered_candidates.simulated_stack_depth)); + for (int idx = 0; idx < ordered_candidates.simulated_stack_depth; ++idx) + simulated_surface_to_deep.emplace_back(surface_to_deep[size_t(idx) % surface_to_deep.size()]); + } return mix_color_solver_ordered_stack(settings.component_colors, - surface_to_deep, + simulated_surface_to_deep, settings.contoning_flat_surface_layer_opacities, settings.contoning_flat_surface_background_rgb, ColorSolverMixModel::PigmentPainter); + } } if (!candidates.empty()) { const TexturePreviewMixNearestResult nearest = @@ -2832,7 +2842,9 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig settings.contoning_flat_surface_background_rgb, ColorSolverMixModel::PigmentPainter, contoning_flat_surface_pattern_filaments, - k_contoning_flat_surface_preview_max_ordered_candidates) : + contoning_flat_surface_pattern_filaments, + k_contoning_flat_surface_preview_max_ordered_candidates, + k_contoning_flat_surface_preview_max_ordered_stack_items) : ColorSolverOrderedStackCandidateSet{}; const std::vector contoning_flat_surface_candidates = use_contoning_flat_surface_quantization && contoning_flat_surface_ordered_candidates.empty() ? diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 37194bb5f7f..d1afaa7be2f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -2561,7 +2563,7 @@ public: 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("Max infill/perimeter layer depth")), + contoning_layers_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("Max infill layer depth")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); @@ -2599,7 +2601,11 @@ public: TextureMappingZone::MaxTopSurfaceContoningPatternFilaments)); contoning_pattern_row->Add(m_top_surface_contoning_pattern_filaments_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); contoning_pattern_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("layers")), 0, wxALIGN_CENTER_VERTICAL); - top_surface_contoning_root->Add(contoning_pattern_row, 0, wxEXPAND | wxBOTTOM, gap); + top_surface_contoning_root->Add(contoning_pattern_row, 0, wxEXPAND); + m_top_surface_contoning_pattern_recommendation_text = + new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, wxEmptyString); + m_top_surface_contoning_pattern_recommendation_text->Wrap(FromDIP(520)); + top_surface_contoning_root->Add(m_top_surface_contoning_pattern_recommendation_text, 0, wxEXPAND | wxTOP | wxBOTTOM, gap / 2); auto *contoning_feature_row = new wxBoxSizer(wxHORIZONTAL); contoning_feature_row->Add(new wxStaticText(m_top_surface_contoning_panel, wxID_ANY, _L("Minimum feature")), 0, @@ -2841,6 +2847,21 @@ public: update_top_surface_image_options_visibility(true); }); } + if (m_top_surface_contoning_td_adjustment_checkbox != nullptr) { + m_top_surface_contoning_td_adjustment_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) { + update_top_surface_contoning_td_recommendation(true); + }); + } + for (wxSpinCtrlDouble *spin : m_transmission_distance_spins) { + if (spin == nullptr) + continue; + spin->Bind(wxEVT_SPINCTRLDOUBLE, [this](wxSpinDoubleEvent &) { + update_top_surface_contoning_td_recommendation(true); + }); + spin->Bind(wxEVT_TEXT, [this](wxCommandEvent &) { + update_top_surface_contoning_td_recommendation(false); + }); + } m_top_surface_image_printing_enabled_checkbox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &) { update_top_surface_image_options_visibility(true); }); @@ -3588,6 +3609,93 @@ private: } } + float top_surface_contoning_layer_height_mm() const + { + auto read_layer_height = [](const DynamicPrintConfig &config, float &value) { + if (!config.has("layer_height")) + return false; + const ConfigOptionFloat *opt = config.option("layer_height"); + if (opt == nullptr || !std::isfinite(opt->value) || opt->value <= 0.0) + return false; + value = float(opt->value); + return true; + }; + + float value = 0.2f; + PresetBundle *bundle = wxGetApp().preset_bundle; + if (bundle != nullptr) { + if (read_layer_height(bundle->project_config, value)) + return std::clamp(value, 0.01f, 2.f); + if (read_layer_height(bundle->prints.get_edited_preset().config, value)) + return std::clamp(value, 0.01f, 2.f); + } + return value; + } + + int top_surface_contoning_layers_for_strength(float td_mm, float strength) const + { + if (!std::isfinite(td_mm) || td_mm <= 0.f) + return 0; + const float layer_height = top_surface_contoning_layer_height_mm(); + const float safe_strength = std::clamp(strength, 0.01f, 0.99f); + const float layers = (td_mm / layer_height) * (std::log(1.f - safe_strength) / std::log(0.05f)); + return std::max(1, int(std::ceil(layers))); + } + + wxString top_surface_contoning_td_recommendation_text() const + { + if (!top_surface_contoning_td_adjustment_enabled() || m_transmission_distance_spins.empty()) + return wxEmptyString; + + int lower = 0; + int upper = 0; + int strong = 0; + bool has_translucent = false; + for (wxSpinCtrlDouble *spin : m_transmission_distance_spins) { + const double td_value = spin != nullptr ? spin->GetValue() : 0.0; + if (!std::isfinite(td_value) || td_value <= 0.0) + return wxEmptyString; + const float td_mm = float(std::clamp(td_value, 0.01, 50.0)); + if (top_surface_contoning_layers_for_strength(td_mm, 0.95f) <= 3) + continue; + has_translucent = true; + lower = std::max(lower, top_surface_contoning_layers_for_strength(td_mm, 0.30f)); + upper = std::max(upper, top_surface_contoning_layers_for_strength(td_mm, 0.50f)); + strong = std::max(strong, top_surface_contoning_layers_for_strength(td_mm, 0.75f)); + } + + if (!has_translucent) + return _L("TD recommendation: 1-3 layers."); + upper = std::max(upper, lower); + strong = std::max(strong, upper); + return wxString::Format(_L("TD recommendation: %d-%d layers. %d+ layers for stronger saturation."), + lower, + upper, + strong); + } + + void update_top_surface_contoning_td_recommendation(bool fit_dialog) + { + if (m_top_surface_contoning_pattern_recommendation_text == nullptr) + return; + const bool contoning = + m_top_surface_image_printing_enabled_checkbox != nullptr && + m_top_surface_image_printing_enabled_checkbox->GetValue() && + m_top_surface_image_method_choice != nullptr && + m_top_surface_image_method_choice->GetSelection() == int(TextureMappingZone::TopSurfaceImageContoning); + const wxString text = contoning ? top_surface_contoning_td_recommendation_text() : wxString(); + m_top_surface_contoning_pattern_recommendation_text->SetLabel(text); + m_top_surface_contoning_pattern_recommendation_text->Show(!text.empty()); + if (!fit_dialog) + return; + layout_current_options_page(); + update_options_book_min_size(); + if (GetSizer() != nullptr) { + Layout(); + Fit(); + } + } + void update_modulation_mode_options_visibility(bool fit_dialog) { const bool perimeter_path_mode = modulation_mode() == int(TextureMappingZone::ModulationPerimeterPath) || @@ -3803,6 +3911,7 @@ private: m_top_surface_image_fixed_coloring_filaments_checkbox->Show(!contoning_selected); m_top_surface_image_fixed_coloring_filaments_checkbox->Enable(enabled && !contoning_selected); } + update_top_surface_contoning_td_recommendation(false); layout_current_options_page(); if (!fit_dialog) return; @@ -3858,6 +3967,7 @@ private: wxSpinCtrlDouble *m_top_surface_contoning_angle_threshold_spin {nullptr}; wxSpinCtrl *m_top_surface_contoning_stack_layers_spin {nullptr}; wxSpinCtrl *m_top_surface_contoning_pattern_filaments_spin {nullptr}; + wxStaticText *m_top_surface_contoning_pattern_recommendation_text {nullptr}; wxSpinCtrlDouble *m_top_surface_contoning_min_feature_spin {nullptr}; wxChoice *m_top_surface_contoning_flat_surface_infill_choice {nullptr}; wxPanel *m_top_surface_contoning_checkboxes_panel {nullptr};