diff --git a/src/libslic3r/ColorSolver.cpp b/src/libslic3r/ColorSolver.cpp index 666aafb6870..126d1acaf4c 100644 --- a/src/libslic3r/ColorSolver.cpp +++ b/src/libslic3r/ColorSolver.cpp @@ -124,7 +124,8 @@ int build_color_solver_kd_tree(const std::vector &coor return build_color_solver_kd_tree(coords, nodes, indices, 0, candidate_count, uint8_t(0)); } -void build_color_solver_kd_trees(ColorSolverCandidateSet &candidates) +template +void build_color_solver_kd_trees(CandidateSet &candidates) { candidates.kd_root = build_color_solver_kd_tree(candidates.rgbs, candidates.kd_nodes); if (candidates.perceptual_coords.size() == candidates.rgbs.size()) { @@ -159,7 +160,8 @@ void update_color_solver_nearest_result(ColorSolverNearestResult &result, size_t } } -float color_solver_candidate_error(const ColorSolverCandidateSet &candidates, +template +float color_solver_candidate_error(const CandidateSet &candidates, size_t candidate_idx, const std::array &target_rgb) { @@ -170,7 +172,8 @@ float color_solver_candidate_error(const ColorSolverCandidateSet &candidates, return dr * dr + dg * dg + db * db; } -ColorSolverNearestResult nearest_color_solver_candidates_linear(const ColorSolverCandidateSet &candidates, +template +ColorSolverNearestResult nearest_color_solver_candidates_linear(const CandidateSet &candidates, const std::array &target_rgb) { ColorSolverNearestResult result; @@ -184,7 +187,8 @@ ColorSolverNearestResult nearest_color_solver_candidates_linear(const ColorSolve return result; } -void query_color_solver_kd_tree(const ColorSolverCandidateSet &candidates, +template +void query_color_solver_kd_tree(const CandidateSet &candidates, const std::array &target_rgb, int node_idx, ColorSolverNearestResult &result) @@ -216,7 +220,8 @@ void query_color_solver_kd_tree(const ColorSolverCandidateSet &candidates, query_color_solver_kd_tree(candidates, target_rgb, far_node, result); } -ColorSolverNearestResult nearest_color_solver_candidates(const ColorSolverCandidateSet &candidates, +template +ColorSolverNearestResult nearest_color_solver_candidates(const CandidateSet &candidates, const std::array &target_rgb) { ColorSolverNearestResult result; @@ -228,7 +233,8 @@ ColorSolverNearestResult nearest_color_solver_candidates(const ColorSolverCandid return result; } -float color_solver_candidate_perceptual_error(const ColorSolverCandidateSet &candidates, +template +float color_solver_candidate_perceptual_error(const CandidateSet &candidates, size_t candidate_idx, const std::array &target_oklab, const std::array &axis_weights) @@ -240,7 +246,8 @@ float color_solver_candidate_perceptual_error(const ColorSolverCandidateSet &can return axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db; } -ColorSolverNearestResult nearest_color_solver_candidates_perceptual_linear(const ColorSolverCandidateSet &candidates, +template +ColorSolverNearestResult nearest_color_solver_candidates_perceptual_linear(const CandidateSet &candidates, const std::array &target_oklab, const std::array &axis_weights) { @@ -254,7 +261,8 @@ ColorSolverNearestResult nearest_color_solver_candidates_perceptual_linear(const return result; } -void query_color_solver_perceptual_kd_tree(const ColorSolverCandidateSet &candidates, +template +void query_color_solver_perceptual_kd_tree(const CandidateSet &candidates, const std::array &target_oklab, const std::array &axis_weights, int node_idx, @@ -287,7 +295,8 @@ void query_color_solver_perceptual_kd_tree(const ColorSolverCandidateSet &candid query_color_solver_perceptual_kd_tree(candidates, target_oklab, axis_weights, far_node, result); } -ColorSolverNearestResult nearest_color_solver_candidates_perceptual(const ColorSolverCandidateSet &candidates, +template +ColorSolverNearestResult nearest_color_solver_candidates_perceptual(const CandidateSet &candidates, const std::array &target_rgb) { ColorSolverNearestResult result; @@ -304,6 +313,48 @@ ColorSolverNearestResult nearest_color_solver_candidates_perceptual(const ColorS return result; } +std::array mix_ordered_stack_with_buffers(const std::vector> &colors_with_background, + std::vector &weights, + const std::vector &surface_to_deep, + const std::vector &layer_opacities) +{ + if (colors_with_background.empty() || surface_to_deep.empty()) + return colors_with_background.empty() ? std::array{ { 0.f, 0.f, 0.f } } : colors_with_background.back(); + + const size_t component_count = colors_with_background.size() - 1; + weights.assign(colors_with_background.size(), 0.f); + float transmission = 1.f; + for (uint16_t component_idx : surface_to_deep) { + if (size_t(component_idx) >= component_count) + continue; + const float opacity = + size_t(component_idx) < layer_opacities.size() && std::isfinite(layer_opacities[size_t(component_idx)]) ? + std::clamp(layer_opacities[size_t(component_idx)], 1e-4f, 0.9999f) : + 0.5f; + weights[size_t(component_idx)] += transmission * opacity; + transmission *= 1.f - opacity; + if (transmission <= 1e-5f) + break; + } + weights.back() = std::max(0.f, transmission); + return pigment_painter::mix_srgb(colors_with_background, weights); +} + +size_t ordered_stack_candidate_count(size_t component_count, int stack_depth, size_t candidate_limit) +{ + if (component_count == 0 || stack_depth <= 0) + return 0; + size_t count = 1; + for (int idx = 0; idx < stack_depth; ++idx) { + if (candidate_limit > 0 && count > candidate_limit / component_count) + return 0; + if (count > std::numeric_limits::max() / component_count) + return 0; + count *= component_count; + } + return candidate_limit > 0 && count > candidate_limit ? 0 : count; +} + } // namespace ColorSolverMixModel color_solver_mix_model_from_index(int model) @@ -362,6 +413,22 @@ std::array mix_color_solver_components(const std::vector mix_color_solver_ordered_stack(const std::vector> &component_colors, + const std::vector &surface_to_deep, + const std::vector &layer_opacities, + const std::array &background_rgb, + ColorSolverMixModel mix_model) +{ + (void) mix_model; + if (component_colors.empty() || surface_to_deep.empty()) + return background_rgb; + + std::vector> colors = component_colors; + colors.emplace_back(background_rgb); + std::vector weights; + return mix_ordered_stack_with_buffers(colors, weights, surface_to_deep, layer_opacities); +} + std::array color_solver_oklab_from_srgb(const std::array &rgb) { return oklab_from_srgb(rgb); @@ -480,4 +547,145 @@ std::vector solve_color_solver_weights_for_target(const ColorSolverCandid return weights; } +std::string color_solver_ordered_stack_candidate_cache_key(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) +{ + std::ostringstream key; + key << component_colors.size(); + key << "|mx" << mix_model_index(mix_model); + key << "|sd" << stack_depth; + key << "|lim" << candidate_limit; + key << "|bg" + << int(std::lround(clamp01(background_rgb[0]) * 65535.f)) << ',' + << int(std::lround(clamp01(background_rgb[1]) * 65535.f)) << ',' + << int(std::lround(clamp01(background_rgb[2]) * 65535.f)); + for (const std::array &color : component_colors) { + key << '|' + << int(std::lround(clamp01(color[0]) * 65535.f)) << ',' + << int(std::lround(clamp01(color[1]) * 65535.f)) << ',' + << int(std::lround(clamp01(color[2]) * 65535.f)); + } + key << "|op"; + for (size_t idx = 0; idx < component_colors.size(); ++idx) { + const float opacity = + idx < layer_opacities.size() && std::isfinite(layer_opacities[idx]) ? + std::clamp(layer_opacities[idx], 1e-4f, 0.9999f) : + 0.5f; + key << ',' << int(std::lround(opacity * 1000000.f)); + } + return key.str(); +} + +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) +{ + (void) mix_model; + ColorSolverOrderedStackCandidateSet candidates; + if (component_colors.empty() || stack_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) + 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)); + + 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); + + 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()); + 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; +} + +const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates( + ColorSolverOrderedStackCandidateCache &cache, + 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) +{ + const std::string key = + color_solver_ordered_stack_candidate_cache_key(component_colors, + layer_opacities, + background_rgb, + mix_model, + stack_depth, + candidate_limit); + auto it = cache.find(key); + if (it != cache.end()) + return it->second; + return cache.emplace( + key, + build_color_solver_ordered_stack_candidates(component_colors, + layer_opacities, + background_rgb, + mix_model, + stack_depth, + candidate_limit)).first->second; +} + +std::vector solve_color_solver_ordered_stack_for_target( + const ColorSolverOrderedStackCandidateSet &candidates, + const std::array &target_rgb, + ColorSolverMode solver_mode) +{ + if (candidates.empty()) + return {}; + + const size_t candidate_count = candidates.rgbs.size() / 3; + ColorSolverNearestResult nearest = + solver_mode == ColorSolverMode::V2 ? + nearest_color_solver_candidates_perceptual(candidates, target_rgb) : + nearest_color_solver_candidates(candidates, target_rgb); + if (nearest.best_idx >= candidate_count && solver_mode == ColorSolverMode::V2) + nearest = nearest_color_solver_candidates(candidates, target_rgb); + if (nearest.best_idx >= candidate_count) + return {}; + + const size_t stack_begin = nearest.best_idx * size_t(candidates.stack_depth); + if (stack_begin + size_t(candidates.stack_depth) > candidates.stacks.size()) + return {}; + return std::vector(candidates.stacks.begin() + stack_begin, + candidates.stacks.begin() + stack_begin + candidates.stack_depth); +} + } // namespace Slic3r diff --git a/src/libslic3r/ColorSolver.hpp b/src/libslic3r/ColorSolver.hpp index 02ffd36a171..eb76c9eb146 100644 --- a/src/libslic3r/ColorSolver.hpp +++ b/src/libslic3r/ColorSolver.hpp @@ -69,6 +69,28 @@ struct ColorSolverCandidateSet { using ColorSolverCandidateCache = std::map; +struct ColorSolverOrderedStackCandidateSet { + using KdNode = ColorSolverCandidateSet::KdNode; + + size_t component_count { 0 }; + int stack_depth { 0 }; + std::vector rgbs; + std::vector perceptual_coords; + std::vector stacks; + std::vector kd_nodes; + std::vector perceptual_kd_nodes; + int kd_root { -1 }; + int perceptual_kd_root { -1 }; + + bool empty() const + { + return component_count == 0 || stack_depth <= 0 || rgbs.empty() || rgbs.size() % 3 != 0 || + stacks.size() != (rgbs.size() / 3) * size_t(stack_depth); + } +}; + +using ColorSolverOrderedStackCandidateCache = std::map; + ColorSolverMixModel color_solver_mix_model_from_index(int model); ColorSolverLookupMode color_solver_lookup_mode_from_index(int mode); ColorSolverMode color_solver_mode_from_index(int mode); @@ -82,6 +104,11 @@ std::array mix_color_solver_components(const std::vector mix_color_solver_components(const std::vector> &component_colors, const std::vector &weights, ColorSolverMixModel mix_model); +std::array mix_color_solver_ordered_stack(const std::vector> &component_colors, + const std::vector &surface_to_deep, + const std::vector &layer_opacities, + const std::array &background_rgb, + ColorSolverMixModel mix_model); std::array color_solver_oklab_from_srgb(const std::array &rgb); std::string color_solver_candidate_cache_key(const std::vector> &component_colors, @@ -98,6 +125,31 @@ std::vector solve_color_solver_weights_for_target(const ColorSolverCandid const std::array &target_rgb, ColorSolverLookupMode lookup_mode, ColorSolverMode solver_mode); +std::string color_solver_ordered_stack_candidate_cache_key(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); +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); +const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates( + ColorSolverOrderedStackCandidateCache &cache, + 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); +std::vector solve_color_solver_ordered_stack_for_target( + const ColorSolverOrderedStackCandidateSet &candidates, + const std::array &target_rgb, + ColorSolverMode solver_mode); } // namespace Slic3r diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index b1fd47796e5..8e87fb2d277 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -504,6 +504,7 @@ struct TopSurfaceImageRegionPlan { bool contoning_polygonize_color_regions_enabled = false; int contoning_polygonize_resolution = TextureMappingZone::DefaultTopSurfaceContoningPolygonizeResolution; bool contoning_surface_anchored_stacks_enabled = false; + bool contoning_td_adjustment_enabled = TextureMappingZone::DefaultTopSurfaceContoningTdAdjustmentEnabled; int contoning_flat_surface_infill_mode = TextureMappingZone::SlicerDefaultTopSurfaceContoningFlatSurfaceInfillMode; }; @@ -1166,6 +1167,7 @@ struct TopSurfaceImageContoningStackPlanKey { bool blue_noise { false }; bool polygonize { false }; int polygonize_resolution { 1 }; + bool td_adjustment { false }; bool operator<(const TopSurfaceImageContoningStackPlanKey &rhs) const { @@ -1192,7 +1194,8 @@ struct TopSurfaceImageContoningStackPlanKey { supersampled, blue_noise, polygonize, - polygonize_resolution) < + polygonize_resolution, + td_adjustment) < std::tie(rhs.source_layer, rhs.source_layer_id, rhs.target_layer, @@ -1216,7 +1219,8 @@ struct TopSurfaceImageContoningStackPlanKey { rhs.supersampled, rhs.blue_noise, rhs.polygonize, - rhs.polygonize_resolution); + rhs.polygonize_resolution, + rhs.td_adjustment); } }; @@ -1320,29 +1324,6 @@ static float top_surface_image_contoning_oklab_error(const std::array return dl * dl + 4.f * da * da + 4.f * db * db; } -static std::optional> top_surface_image_contoning_stack_rgb( - const std::vector &bottom_to_top, - const PrintConfig &print_config) -{ - if (bottom_to_top.empty()) - return std::nullopt; - std::vector> colors; - std::vector weights; - colors.reserve(bottom_to_top.size()); - weights.reserve(bottom_to_top.size()); - const float weight = 1.f / float(bottom_to_top.size()); - for (unsigned int component_id : bottom_to_top) { - if (component_id == 0 || component_id > print_config.filament_colour.values.size()) - return std::nullopt; - ColorRGB color; - if (!decode_color(print_config.filament_colour.get_at(size_t(component_id - 1)), color)) - return std::nullopt; - colors.push_back({ color.r(), color.g(), color.b() }); - weights.emplace_back(weight); - } - return mix_color_solver_components(colors, weights, ColorSolverMixModel::PigmentPainter); -} - static float top_surface_image_contoning_sample_pitch_mm(const TopSurfaceImageRegionPlan &plan, const BoundingBox &bbox) { @@ -1704,6 +1685,7 @@ static std::vector top_surface_image_conto const ExPolygons &area, float min_feature_mm, bool polygonize_color_regions, + bool lower_surface, const ThrowIfCanceled *throw_if_canceled) { std::vector regions; @@ -1729,7 +1711,9 @@ static std::vector top_surface_image_conto continue; const int pattern_depth = depth % int(bottom_to_top.size()); const unsigned int component_id = - bottom_to_top[size_t(int(bottom_to_top.size()) - 1 - pattern_depth)]; + lower_surface ? + bottom_to_top[size_t(pattern_depth)] : + bottom_to_top[size_t(int(bottom_to_top.size()) - 1 - pattern_depth)]; if (component_id == 0 || component_id > max_component_id) continue; component_grid[idx] = int(component_id); @@ -1969,15 +1953,15 @@ static std::optional top_surface_image_cont const std::array &rgb, int solve_layers, const TextureMappingContoningSolver &solver, - const PrintConfig &print_config, + bool lower_surface, std::vector &labels, std::map, int> &label_by_stack) { - TextureMappingContoningStack stack = solver.solve(rgb, solve_layers); + TextureMappingContoningStack stack = solver.solve(rgb, solve_layers, lower_surface); if (stack.bottom_to_top.empty()) return std::nullopt; std::optional> stack_rgb = - top_surface_image_contoning_stack_rgb(stack.bottom_to_top, print_config); + solver.stack_rgb(stack.bottom_to_top, lower_surface); if (!stack_rgb) return std::nullopt; auto label_it = label_by_stack.find(stack.bottom_to_top); @@ -2124,7 +2108,13 @@ static std::vector top_surface_image_conto auto solve_cell = [&](int row, int col, const std::array &target_rgb, int solve_layers) { std::optional solved = - top_surface_image_contoning_solve_label(target_rgb, solve_layers, solver, print_config, labels, label_by_stack); + top_surface_image_contoning_solve_label(target_rgb, + solve_layers, + solver, + source_surface == TopSurfaceImageSourceSurface::Bottom && + plan.contoning_td_adjustment_enabled, + labels, + label_by_stack); if (!solved) return std::optional(); grid[size_t(row * cols + col)] = solved->label; @@ -2247,6 +2237,8 @@ static std::vector top_surface_image_conto area, plan.contoning_min_feature_mm, plan.contoning_polygonize_color_regions_enabled, + source_surface == TopSurfaceImageSourceSurface::Bottom && + plan.contoning_td_adjustment_enabled, throw_if_canceled); } @@ -2306,7 +2298,13 @@ static std::shared_ptr top_surface_imag 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, solver, print_config, out->labels, label_by_stack); + top_surface_image_contoning_solve_label(target_rgb, + solve_layers, + solver, + source_surface == TopSurfaceImageSourceSurface::Bottom && + plan.contoning_td_adjustment_enabled, + out->labels, + label_by_stack); if (!solved) return std::optional(); TopSurfaceImageContoningStackPlanCell &cell = out->cells[size_t(row * cols + col)]; @@ -2470,6 +2468,7 @@ static TopSurfaceImageContoningStackPlanKey top_surface_image_contoning_stack_pl key.polygonize_resolution = plan.contoning_polygonize_color_regions_enabled ? TextureMappingZone::normalize_top_surface_contoning_polygonize_resolution(plan.contoning_polygonize_resolution) : 1; + key.td_adjustment = plan.contoning_td_adjustment_enabled; return key; } @@ -2511,6 +2510,7 @@ static std::vector top_surface_image_conto const TopSurfaceImageContoningStackPlan &stack_plan, const ExPolygons &area, int depth, + TopSurfaceImageSourceSurface source_surface, const ThrowIfCanceled *throw_if_canceled) { std::vector regions; @@ -2550,6 +2550,8 @@ static std::vector top_surface_image_conto area, plan.contoning_min_feature_mm, plan.contoning_polygonize_color_regions_enabled, + source_surface == TopSurfaceImageSourceSurface::Bottom && + plan.contoning_td_adjustment_enabled, throw_if_canceled); } @@ -2889,7 +2891,12 @@ static void top_surface_image_append_contoning_slices(TopSurfaceImageRegionPlan stack_area_extensions_ptr, throw_if_canceled); const std::vector stack_regions = - top_surface_image_contoning_vector_regions_from_stack_plan(plan, *stack_plan, vector_area, depth, throw_if_canceled); + top_surface_image_contoning_vector_regions_from_stack_plan(plan, + *stack_plan, + vector_area, + depth, + source_surface, + throw_if_canceled); append_regions(stack_regions, true, include_perimeter_regions); } } else if (plan.contoning_blue_noise_error_diffusion_enabled) { @@ -3122,7 +3129,8 @@ static std::vector top_surface_image_region_plans( TextureMappingZone::normalize_top_surface_contoning_polygonize_resolution(zone->top_surface_contoning_polygonize_resolution); plan.contoning_surface_anchored_stacks_enabled = zone->effective_top_surface_contoning_surface_anchored_stacks_enabled(); - const TextureMappingContoningSolver contoning_solver(*zone, print_config, components); + plan.contoning_td_adjustment_enabled = zone->top_surface_contoning_td_adjustment_enabled; + const TextureMappingContoningSolver contoning_solver(*zone, print_config, components, float(layer.height)); const int stack_depth = plan.contoning ? plan.contoning_stack_layers : diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 0ed6113ef94..fe477c3e34f 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -528,7 +528,10 @@ static std::optional perimeter_texture_make_reco if (zone.top_surface_contoning_perimeters_active()) { sampler.contoning = true; sampler.contoning_solver = - TextureMappingContoningSolver(zone, print_config, sampler.image_context->component_ids); + TextureMappingContoningSolver(zone, + print_config, + sampler.image_context->component_ids, + layer != nullptr ? float(layer->height) : 0.f); sampler.contoning_stack_layers = std::clamp(zone.top_surface_contoning_stack_layers, TextureMappingZone::MinTopSurfaceContoningStackLayers, diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index 09cd1cc709f..1a7211492dd 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -1148,6 +1148,7 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const TextureMappingZone::normalize_top_surface_contoning_polygonize_resolution(top_surface_contoning_polygonize_resolution) == TextureMappingZone::normalize_top_surface_contoning_polygonize_resolution(rhs.top_surface_contoning_polygonize_resolution) && effective_top_surface_contoning_surface_anchored_stacks_enabled() == rhs.effective_top_surface_contoning_surface_anchored_stacks_enabled() && + top_surface_contoning_td_adjustment_enabled == rhs.top_surface_contoning_td_adjustment_enabled && 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 && @@ -1548,6 +1549,8 @@ std::string TextureMappingManager::serialize_entries() TextureMappingZone::normalize_top_surface_contoning_polygonize_resolution(zone.top_surface_contoning_polygonize_resolution); texture["top_surface_contoning_surface_anchored_stacks_enabled"] = zone.effective_top_surface_contoning_surface_anchored_stacks_enabled(); + texture["top_surface_contoning_td_adjustment_enabled"] = + zone.top_surface_contoning_td_adjustment_enabled; 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; @@ -1844,6 +1847,9 @@ void TextureMappingManager::load_entries(const std::string &serialized, zone.top_surface_contoning_surface_anchored_stacks_enabled = texture.value("top_surface_contoning_surface_anchored_stacks_enabled", TextureMappingZone::DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled); + zone.top_surface_contoning_td_adjustment_enabled = + texture.value("top_surface_contoning_td_adjustment_enabled", + TextureMappingZone::DefaultTopSurfaceContoningTdAdjustmentEnabled); zone.apply_top_surface_contoning_experimental_defaults(); zone.compact_offset_mode = texture.value("compact_offset_mode", TextureMappingZone::DefaultCompactOffsetMode); zone.use_legacy_fixed_color_mode = diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp index cbed9af301a..a1cba4cd1e8 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -204,6 +204,7 @@ struct TextureMappingZone static constexpr bool DefaultTopSurfaceContoningPolygonizeColorRegionsEnabled = true; static constexpr int DefaultTopSurfaceContoningPolygonizeResolution = 4; static constexpr bool DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled = false; + static constexpr bool DefaultTopSurfaceContoningTdAdjustmentEnabled = true; static constexpr bool DefaultCompactOffsetMode = true; static constexpr bool DefaultUseLegacyFixedColorMode = false; static constexpr bool DefaultHighSpeedImageTextureSampling = true; @@ -362,6 +363,7 @@ struct TextureMappingZone bool top_surface_contoning_polygonize_color_regions_enabled = DefaultTopSurfaceContoningPolygonizeColorRegionsEnabled; int top_surface_contoning_polygonize_resolution = DefaultTopSurfaceContoningPolygonizeResolution; bool top_surface_contoning_surface_anchored_stacks_enabled = DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled; + bool top_surface_contoning_td_adjustment_enabled = DefaultTopSurfaceContoningTdAdjustmentEnabled; bool compact_offset_mode = DefaultCompactOffsetMode; bool use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode; bool high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling; @@ -553,6 +555,7 @@ struct TextureMappingZone top_surface_contoning_polygonize_color_regions_enabled = DefaultTopSurfaceContoningPolygonizeColorRegionsEnabled; top_surface_contoning_polygonize_resolution = DefaultTopSurfaceContoningPolygonizeResolution; top_surface_contoning_surface_anchored_stacks_enabled = DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled; + top_surface_contoning_td_adjustment_enabled = DefaultTopSurfaceContoningTdAdjustmentEnabled; compact_offset_mode = DefaultCompactOffsetMode; use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode; high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling; diff --git a/src/libslic3r/TextureMappingContoning.cpp b/src/libslic3r/TextureMappingContoning.cpp index e8e6ed688bd..04d88ed9634 100644 --- a/src/libslic3r/TextureMappingContoning.cpp +++ b/src/libslic3r/TextureMappingContoning.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -17,6 +18,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; float clamp01(float value) { @@ -81,9 +83,16 @@ bool is_black_color_filament(const PrintConfig &config, unsigned int component_i return max_channel <= 0.20f && luminance <= 0.12f && max_channel - min_channel <= 0.08f; } +float estimated_transmission_distance_mm(const PrintConfig &config, unsigned int component_id) +{ + const float luminance = std::clamp(filament_luminance(config, component_id), 0.f, 1.f); + return std::clamp(0.1f + 2.9f * std::pow(luminance, 1.35f), 0.12f, 3.f); +} + std::vector effective_transmission_distances_mm(const TextureMappingZone &zone, const PrintConfig &config, - const std::vector &component_ids) + const std::vector &component_ids, + bool estimate_missing) { std::vector out(component_ids.size(), 0.f); for (size_t idx = 0; idx < component_ids.size(); ++idx) { @@ -93,11 +102,20 @@ std::vector effective_transmission_distances_mm(const TextureMappingZone } else if (black_role_component(zone.filament_color_mode, idx, component_ids.size()) || is_black_color_filament(config, component_ids[idx])) { out[idx] = INFERRED_BLACK_TD_MM; + } else if (estimate_missing) { + out[idx] = estimated_transmission_distance_mm(config, component_ids[idx]); } } return out; } +float layer_opacity_from_td(float td_mm, float layer_height_mm) +{ + const float safe_td = std::clamp(td_mm, 0.01f, 50.f); + const float safe_layer_height = std::clamp(layer_height_mm, 0.01f, 2.f); + return std::clamp(1.f - std::pow(0.05f, safe_layer_height / safe_td), 1e-4f, 0.9999f); +} + float effective_transmission_distance_for_component(const std::vector &component_ids, const std::vector &effective_tds, unsigned int component_id) @@ -263,7 +281,8 @@ std::vector texture_mapping_contoning_components_bottom_to_top( return component_ids; const std::vector td_component_ids = component_ids; - const std::vector effective_tds = effective_transmission_distances_mm(zone, config, td_component_ids); + const std::vector effective_tds = + effective_transmission_distances_mm(zone, config, td_component_ids, zone.top_surface_contoning_td_adjustment_enabled); const bool has_complete_td = std::all_of(effective_tds.begin(), effective_tds.end(), [](float td) { return std::isfinite(td) && td > 0.f; }); if (has_complete_td) { @@ -327,8 +346,14 @@ bool texture_mapping_contoning_normal_eligible(float normal_z, float threshold_d TextureMappingContoningSolver::TextureMappingContoningSolver(const TextureMappingZone &zone, const PrintConfig &config, - std::vector component_ids) + std::vector component_ids, + float layer_height_mm) { + m_td_adjustment_enabled = zone.top_surface_contoning_td_adjustment_enabled; + m_layer_height_mm = std::isfinite(layer_height_mm) && layer_height_mm > 0.f ? layer_height_mm : 0.2f; + if (!std::isfinite(m_layer_height_mm) || m_layer_height_mm <= 0.f) + m_layer_height_mm = 0.2f; + 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()); @@ -342,7 +367,13 @@ TextureMappingContoningSolver::TextureMappingContoningSolver(const TextureMappin 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_effective_transmission_distances_mm = effective_transmission_distances_mm(zone, config, m_component_ids); + std::vector background_weights(m_component_colors.size(), 1.f / float(m_component_colors.size())); + m_background_rgb = mix_color_solver_components(m_component_colors, background_weights, ColorSolverMixModel::PigmentPainter); + m_effective_transmission_distances_mm = + effective_transmission_distances_mm(zone, config, m_component_ids, m_td_adjustment_enabled); + m_component_layer_opacity.reserve(m_effective_transmission_distances_mm.size()); + for (float td_mm : m_effective_transmission_distances_mm) + m_component_layer_opacity.emplace_back(layer_opacity_from_td(td_mm > 0.f ? td_mm : 3.f, m_layer_height_mm)); m_components_bottom_to_top = texture_mapping_contoning_components_bottom_to_top(zone, config, m_component_ids); } @@ -381,6 +412,63 @@ TextureMappingContoningSolver::candidates_for_depth(int stack_layers) const return m_candidates_by_depth.emplace(depth, std::move(candidates)).first->second; } +std::optional TextureMappingContoningSolver::component_index(unsigned int component_id) const +{ + const auto it = std::find(m_component_ids.begin(), m_component_ids.end(), component_id); + if (it == m_component_ids.end()) + return std::nullopt; + return size_t(it - m_component_ids.begin()); +} + +std::optional> TextureMappingContoningSolver::stack_rgb( + const std::vector &bottom_to_top, + bool lower_surface) const +{ + if (bottom_to_top.empty() || !valid()) + return std::nullopt; + + if (!m_td_adjustment_enabled) { + std::vector> colors; + std::vector weights; + colors.reserve(bottom_to_top.size()); + weights.reserve(bottom_to_top.size()); + const float weight = 1.f / float(bottom_to_top.size()); + for (unsigned int component_id : bottom_to_top) { + const std::optional idx = component_index(component_id); + if (!idx || *idx >= m_component_colors.size()) + return std::nullopt; + colors.emplace_back(m_component_colors[*idx]); + weights.emplace_back(weight); + } + return mix_color_solver_components(colors, weights, ColorSolverMixModel::PigmentPainter); + } + + std::vector surface_to_deep; + surface_to_deep.reserve(bottom_to_top.size()); + 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())) + return false; + surface_to_deep.emplace_back(uint16_t(*idx)); + return true; + }; + if (lower_surface) { + for (unsigned int component_id : bottom_to_top) + if (!append_component(component_id)) + return std::nullopt; + } else { + for (auto it = bottom_to_top.rbegin(); it != bottom_to_top.rend(); ++it) + if (!append_component(*it)) + return std::nullopt; + } + + return mix_color_solver_ordered_stack(m_component_colors, + surface_to_deep, + m_component_layer_opacity, + m_background_rgb, + ColorSolverMixModel::PigmentPainter); +} + void TextureMappingContoningSolver::arrange_stack_for_light_path(std::vector &bottom_to_top, const std::array &target_rgb) const { @@ -446,7 +534,9 @@ void TextureMappingContoningSolver::arrange_stack_for_light_path(std::vector &target_rgb, int stack_layers) const +TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::array &target_rgb, + int stack_layers, + bool lower_surface) const { TextureMappingContoningStack out; if (!valid()) @@ -455,6 +545,39 @@ TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::arr const int depth = std::clamp(stack_layers, TextureMappingZone::MinTopSurfaceContoningStackLayers, TextureMappingZone::MaxTopSurfaceContoningStackLayers); + if (m_td_adjustment_enabled) { + const ColorSolverOrderedStackCandidateSet *ordered_candidates = nullptr; + { + std::lock_guard lock(*m_ordered_candidate_cache_mutex); + ordered_candidates = + &color_solver_ordered_stack_candidates(*m_ordered_candidate_cache, + m_component_colors, + m_component_layer_opacity, + m_background_rgb, + ColorSolverMixModel::PigmentPainter, + depth, + MAX_TD_ORDERED_CONTONING_CANDIDATES); + } + const std::vector surface_to_deep = + solve_color_solver_ordered_stack_for_target(*ordered_candidates, target_rgb, ColorSolverMode::V2); + if (!surface_to_deep.empty()) { + out.bottom_to_top.reserve(surface_to_deep.size()); + auto append_component = [this, &out](uint16_t component_idx) { + if (size_t(component_idx) < m_component_ids.size()) + out.bottom_to_top.emplace_back(m_component_ids[size_t(component_idx)]); + }; + if (lower_surface) { + for (uint16_t component_idx : surface_to_deep) + append_component(component_idx); + } else { + for (auto it = surface_to_deep.rbegin(); it != surface_to_deep.rend(); ++it) + append_component(*it); + } + if (!out.bottom_to_top.empty()) + return out; + } + } + const std::vector &candidates = candidates_for_depth(depth); if (candidates.empty()) return out; @@ -492,18 +615,23 @@ TextureMappingContoningStack TextureMappingContoningSolver::solve(const std::arr if (int(out.bottom_to_top.size()) > depth) out.bottom_to_top.resize(size_t(depth)); arrange_stack_for_light_path(out.bottom_to_top, target_rgb); + if (lower_surface && m_td_adjustment_enabled) + std::reverse(out.bottom_to_top.begin(), out.bottom_to_top.end()); return out; } unsigned int TextureMappingContoningSolver::component_for_depth(const std::array &target_rgb, int stack_layers, - int depth_from_top) const + int depth_from_top, + bool lower_surface) const { - TextureMappingContoningStack stack = solve(target_rgb, stack_layers); + TextureMappingContoningStack stack = solve(target_rgb, stack_layers, lower_surface); 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); + const int idx = lower_surface ? + std::clamp(depth_from_top, 0, depth - 1) : + std::clamp(depth - 1 - depth_from_top, 0, depth - 1); return stack.bottom_to_top[size_t(idx)]; } diff --git a/src/libslic3r/TextureMappingContoning.hpp b/src/libslic3r/TextureMappingContoning.hpp index f54f307f7c7..983e152f458 100644 --- a/src/libslic3r/TextureMappingContoning.hpp +++ b/src/libslic3r/TextureMappingContoning.hpp @@ -3,10 +3,14 @@ #ifndef slic3r_TextureMappingContoning_hpp_ #define slic3r_TextureMappingContoning_hpp_ +#include "ColorSolver.hpp" #include "TextureMapping.hpp" #include +#include #include +#include +#include #include #include @@ -24,14 +28,16 @@ public: TextureMappingContoningSolver() = default; TextureMappingContoningSolver(const TextureMappingZone &zone, const PrintConfig &config, - std::vector component_ids); + std::vector component_ids, + float layer_height_mm = 0.f); bool valid() const { return !m_component_ids.empty() && m_component_ids.size() == m_component_colors.size(); } 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) const; - unsigned int component_for_depth(const std::array &target_rgb, int stack_layers, int depth_from_top) const; + TextureMappingContoningStack solve(const std::array &target_rgb, int stack_layers, bool lower_surface = false) 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; private: struct Candidate { @@ -44,13 +50,20 @@ private: const std::vector& candidates_for_depth(int stack_layers) const; void arrange_stack_for_light_path(std::vector &bottom_to_top, const std::array &target_rgb) const; + std::optional component_index(unsigned int component_id) const; std::vector m_component_ids; std::vector m_components_bottom_to_top; std::vector> m_component_colors; + std::array m_background_rgb { { 0.f, 0.f, 0.f } }; std::vector m_component_luminance; std::vector m_effective_transmission_distances_mm; + std::vector m_component_layer_opacity; + float m_layer_height_mm { 0.2f }; + bool m_td_adjustment_enabled { false }; mutable std::map> m_candidates_by_depth; + mutable std::shared_ptr m_ordered_candidate_cache { std::make_shared() }; + mutable std::shared_ptr m_ordered_candidate_cache_mutex { std::make_shared() }; }; std::vector texture_mapping_contoning_components_bottom_to_top(const TextureMappingZone &zone, diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp index f231ed41e14..579508f3767 100644 --- a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp +++ b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp @@ -45,8 +45,10 @@ constexpr size_t k_temporary_simulated_texture_preview_min_source_pixels = k_tem 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 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; struct TexturePreviewMixCandidate { @@ -90,9 +92,13 @@ struct TexturePreviewSimulationSettings int generic_solver_mix_model = TextureMappingZone::DefaultGenericSolverMixModel; bool contoning_flat_surface_quantization = false; bool contoning_flat_surface_pattern_blend = false; + bool contoning_flat_surface_td_adjustment = false; int contoning_flat_surface_pattern_filaments = TextureMappingZone::DefaultTopSurfaceContoningPatternFilaments; bool simulate_top_surface_lod = false; float top_surface_lod_pitch_mm = 0.f; + float contoning_flat_surface_layer_height_mm = 0.2f; + std::array contoning_flat_surface_background_rgb { { 0.f, 0.f, 0.f } }; + std::vector contoning_flat_surface_layer_opacities; float minimum_visibility_offset_factor = 0.f; std::vector component_ids; std::vector> component_colors; @@ -521,6 +527,82 @@ float contoning_flat_surface_lod_pitch_for_texture_preview(const TextureMappingZ return std::clamp(pitch, 0.25f, std::max(0.25f, min_feature)); } +float texture_preview_layer_height_mm() +{ + return std::clamp(texture_preview_config_float("layer_height", 0.2f), 0.01f, 2.f); +} + +float texture_preview_filament_luminance(const std::array &color) +{ + return 0.2126f * clamp01(color[0]) + 0.7152f * clamp01(color[1]) + 0.0722f * clamp01(color[2]); +} + +bool texture_preview_black_role_component(int filament_color_mode, size_t component_idx, size_t component_count) +{ + switch (std::clamp(filament_color_mode, + int(TextureMappingZone::FilamentColorAny), + int(TextureMappingZone::FilamentColorRGBKW))) { + case int(TextureMappingZone::FilamentColorCMYK): + case int(TextureMappingZone::FilamentColorRGBK): + return component_count == 4 && component_idx == 3; + case int(TextureMappingZone::FilamentColorBW): + return component_count == 2 && component_idx == 0; + case int(TextureMappingZone::FilamentColorCMYKW): + case int(TextureMappingZone::FilamentColorRGBKW): + return component_count == 5 && component_idx == 3; + default: + return false; + } +} + +bool texture_preview_black_color_filament(const std::array &color) +{ + const float r = clamp01(color[0]); + const float g = clamp01(color[1]); + const float b = clamp01(color[2]); + const float max_channel = std::max({ r, g, b }); + const float min_channel = std::min({ r, g, b }); + const float luminance = texture_preview_filament_luminance(color); + return max_channel <= 0.20f && luminance <= 0.12f && max_channel - min_channel <= 0.08f; +} + +float texture_preview_estimated_transmission_distance_mm(const std::array &color) +{ + return std::clamp(0.1f + 2.9f * std::pow(texture_preview_filament_luminance(color), 1.35f), 0.12f, 3.f); +} + +float texture_preview_layer_opacity_from_td(float td_mm, float layer_height_mm) +{ + const float safe_td = std::clamp(td_mm, 0.01f, 50.f); + const float safe_layer_height = std::clamp(layer_height_mm, 0.01f, 2.f); + return std::clamp(1.f - std::pow(0.05f, safe_layer_height / safe_td), 1e-4f, 0.9999f); +} + +std::vector contoning_flat_surface_preview_layer_opacities( + const TextureMappingZone &zone, + const TexturePreviewSimulationSettings &settings) +{ + std::vector out; + out.reserve(settings.component_ids.size()); + for (size_t idx = 0; idx < settings.component_ids.size() && idx < settings.component_colors.size(); ++idx) { + const unsigned int component_id = settings.component_ids[idx]; + float td_mm = 0.f; + if (component_id > 0 && size_t(component_id - 1) < zone.filament_transmission_distances_mm.size()) { + const float explicit_td = zone.filament_transmission_distances_mm[size_t(component_id - 1)]; + if (std::isfinite(explicit_td) && explicit_td > 0.f) + td_mm = explicit_td; + } + if (td_mm <= 0.f && + (texture_preview_black_role_component(zone.filament_color_mode, idx, settings.component_ids.size()) || + texture_preview_black_color_filament(settings.component_colors[idx]))) + td_mm = k_contoning_preview_inferred_black_td_mm; + if (td_mm <= 0.f) + td_mm = texture_preview_estimated_transmission_distance_mm(settings.component_colors[idx]); + out.emplace_back(texture_preview_layer_opacity_from_td(td_mm, settings.contoning_flat_surface_layer_height_mm)); + } + return out; +} + std::array raw_offset_visibility_factors_for_texture_preview(const TextureMappingZone &zone) { const float base_outer_width_mm = @@ -2401,12 +2483,23 @@ std::array fallback_contoning_flat_surface_rgb_for_texture_preview( std::array contoning_flat_surface_rgb_for_texture_preview( const TexturePreviewSimulationSettings &settings, const std::array &sample_rgba, + const ColorSolverOrderedStackCandidateSet &ordered_candidates, const std::vector &candidates, const std::vector &nodes, int root, int total_units) { const std::array target_rgb = texture_preview_target_rgb(settings, sample_rgba); + 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()) + return mix_color_solver_ordered_stack(settings.component_colors, + surface_to_deep, + settings.contoning_flat_surface_layer_opacities, + settings.contoning_flat_surface_background_rgb, + ColorSolverMixModel::PigmentPainter); + } if (!candidates.empty()) { const TexturePreviewMixNearestResult nearest = nearest_contoning_flat_surface_mix_candidate(candidates, nodes, root, target_rgb); @@ -2544,6 +2637,7 @@ std::optional texture_preview_simulation_setti int(TextureMappingZone::GenericSolverLegacy), int(TextureMappingZone::GenericSolverV2)); settings.generic_solver_mix_model = TextureMappingZone::DefaultGenericSolverMixModel; + settings.contoning_flat_surface_layer_height_mm = texture_preview_layer_height_mm(); if (zone->top_surface_image_printing_enabled) { const int stack_layers = std::clamp(zone->top_surface_contoning_stack_layers, @@ -2554,6 +2648,8 @@ std::optional texture_preview_simulation_setti TextureMappingZone::MinTopSurfaceContoningPatternFilaments, TextureMappingZone::MaxTopSurfaceContoningPatternFilaments); settings.contoning_flat_surface_pattern_filaments = std::max(1, std::min(stack_layers, pattern_filaments)); + settings.contoning_flat_surface_td_adjustment = + zone->top_surface_contoning_active() && zone->top_surface_contoning_td_adjustment_enabled; } settings.minimum_visibility_offset_factor = zone->minimum_visibility_offset_enabled ? std::clamp((std::isfinite(zone->minimum_visibility_offset_pct) ? @@ -2597,6 +2693,16 @@ std::optional texture_preview_simulation_setti settings.component_minimum_offset_factors.emplace_back(std::clamp(safe_minimum_offset_pct / 100.f, 0.f, 1.f)); } + if (settings.contoning_flat_surface_td_adjustment && !settings.component_colors.empty()) { + std::vector background_weights(settings.component_colors.size(), 1.f / float(settings.component_colors.size())); + settings.contoning_flat_surface_background_rgb = + mix_color_solver_components(settings.component_colors, background_weights, ColorSolverMixModel::PigmentPainter); + settings.contoning_flat_surface_layer_opacities = + contoning_flat_surface_preview_layer_opacities(*zone, settings); + if (settings.contoning_flat_surface_layer_opacities.size() != settings.component_colors.size()) + settings.contoning_flat_surface_td_adjustment = false; + } + const std::array raw_offset_visibility_factors = raw_offset_visibility_factors_for_texture_preview(*zone); settings.raw_offset_base_visibility_factor = raw_offset_visibility_factors[0]; settings.raw_offset_visibility_range_factor = raw_offset_visibility_factors[1]; @@ -2632,12 +2738,18 @@ size_t texture_preview_simulation_signature(const ModelVolume &model_volume, mix(std::hash{}(settings.generic_solver_mix_model)); mix(std::hash{}(settings.contoning_flat_surface_quantization ? 1 : 0)); mix(std::hash{}(settings.contoning_flat_surface_pattern_blend ? 1 : 0)); + mix(std::hash{}(settings.contoning_flat_surface_td_adjustment ? 1 : 0)); if (settings.contoning_flat_surface_quantization) { mix(std::hash{}(settings.contoning_flat_surface_pattern_filaments)); mix(std::hash{}(settings.simulate_top_surface_lod ? 1 : 0)); if (settings.simulate_top_surface_lod) mix(std::hash{}(int(std::lround(settings.top_surface_lod_pitch_mm * 100000.f)))); } + if (settings.contoning_flat_surface_td_adjustment) { + mix(std::hash{}(int(std::lround(settings.contoning_flat_surface_layer_height_mm * 100000.f)))); + for (const float opacity : settings.contoning_flat_surface_layer_opacities) + mix(std::hash{}(int(std::lround(opacity * 1000000.f)))); + } mix(std::hash{}(int(std::lround(settings.contrast_pct * 100.f)))); mix(std::hash{}(int(std::lround(settings.tone_gamma * 1000.f)))); mix(std::hash{}(int(std::lround(settings.minimum_visibility_offset_factor * 100000.f)))); @@ -2709,8 +2821,21 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && !settings.component_colors.empty() && contoning_flat_surface_pattern_filaments > 0; + const bool use_contoning_flat_surface_ordered_quantization = + use_contoning_flat_surface_quantization && + settings.contoning_flat_surface_td_adjustment && + settings.contoning_flat_surface_layer_opacities.size() == settings.component_colors.size(); + const ColorSolverOrderedStackCandidateSet contoning_flat_surface_ordered_candidates = + use_contoning_flat_surface_ordered_quantization ? + build_color_solver_ordered_stack_candidates(settings.component_colors, + settings.contoning_flat_surface_layer_opacities, + settings.contoning_flat_surface_background_rgb, + ColorSolverMixModel::PigmentPainter, + contoning_flat_surface_pattern_filaments, + k_contoning_flat_surface_preview_max_ordered_candidates) : + ColorSolverOrderedStackCandidateSet{}; const std::vector contoning_flat_surface_candidates = - use_contoning_flat_surface_quantization ? + use_contoning_flat_surface_quantization && contoning_flat_surface_ordered_candidates.empty() ? build_contoning_flat_surface_mix_candidates(settings.component_colors, contoning_flat_surface_pattern_filaments) : std::vector{}; std::vector contoning_flat_surface_nodes; @@ -2928,6 +3053,7 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig forced_simulated_rgb = contoning_flat_surface_rgb_for_texture_preview(settings, sample_rgba, + contoning_flat_surface_ordered_candidates, contoning_flat_surface_candidates, contoning_flat_surface_nodes, contoning_flat_surface_root, @@ -5513,6 +5639,7 @@ static size_t texture_preview_settings_signature_impl(size_t num_physical, signature_mix(std::hash{}(zone.top_surface_contoning_stack_layers)); signature_mix(std::hash{}(zone.top_surface_contoning_pattern_filaments)); signature_mix(std::hash{}(zone.top_surface_contoning_color_lower_surfaces ? 1 : 0)); + signature_mix(std::hash{}(zone.top_surface_contoning_td_adjustment_enabled ? 1 : 0)); signature_mix(std::hash{}(zone.nonlinear_offset_adjustment ? 1 : 0)); signature_mix(std::hash{}(zone.compact_offset_mode ? 1 : 0)); signature_mix(std::hash{}(zone.use_legacy_fixed_color_mode ? 1 : 0)); @@ -5533,6 +5660,8 @@ static size_t texture_preview_settings_signature_impl(size_t num_physical, signature_mix_float(strength_pct, 100.f); for (const float minimum_offset_pct : zone.filament_minimum_offsets_pct) signature_mix_float(minimum_offset_pct, 100.f); + for (const float td_mm : zone.filament_transmission_distances_mm) + signature_mix_float(td_mm, 1000.f); } return signature; } @@ -5663,6 +5792,7 @@ static void texture_preview_mix_zone_baked_model_settings(size_t &signature, signature_mix(std::hash{}(zone.top_surface_contoning_stack_layers)); signature_mix(std::hash{}(zone.top_surface_contoning_pattern_filaments)); signature_mix(std::hash{}(zone.top_surface_contoning_color_lower_surfaces ? 1 : 0)); + signature_mix(std::hash{}(zone.top_surface_contoning_td_adjustment_enabled ? 1 : 0)); signature_mix(std::hash{}(zone.nonlinear_offset_adjustment ? 1 : 0)); signature_mix(std::hash{}(zone.compact_offset_mode ? 1 : 0)); signature_mix(std::hash{}(zone.use_legacy_fixed_color_mode ? 1 : 0)); @@ -5687,6 +5817,8 @@ static void texture_preview_mix_zone_baked_model_settings(size_t &signature, signature_mix_float(strength_pct, 100.f); for (const float minimum_offset_pct : zone.filament_minimum_offsets_pct) signature_mix_float(minimum_offset_pct, 100.f); + for (const float td_mm : zone.filament_transmission_distances_mm) + signature_mix_float(td_mm, 1000.f); if (zone.is_surface_gradient()) { signature_mix_float(texture_preview_config_float("texture_mapping_outer_wall_gradient_global_strength", 100.f), 100.f); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index fe689e02320..37194bb5f7f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1984,6 +1984,7 @@ public: bool top_surface_contoning_polygonize_color_regions_enabled, int top_surface_contoning_polygonize_resolution, bool top_surface_contoning_surface_anchored_stacks_enabled, + bool top_surface_contoning_td_adjustment_enabled, const TextureMappingManager &texture_mapping_zones, const TextureMappingGlobalSettings &global_settings, const TextureMappingPrimeTowerImage &prime_tower_image, @@ -2199,7 +2200,7 @@ public: auto *filament_calibration_note = new wxStaticText(filament_page, wxID_ANY, - _L("NOTE: Filament/TD calibration is not currently used in top-surface coloring"), + _L("NOTE: TD calibration is used by overhang modulation and Contoning TD adjustment"), wxDefaultPosition, wxSize(FromDIP(390), -1)); filament_calibration_note->Wrap(FromDIP(390)); @@ -2662,6 +2663,15 @@ public: 0, wxEXPAND | wxTOP | wxBOTTOM, gap / 2); + m_top_surface_contoning_td_adjustment_checkbox = + new wxCheckBox(m_top_surface_contoning_checkboxes_panel, wxID_ANY, _L("TD adjustment")); + m_top_surface_contoning_td_adjustment_checkbox->SetValue(top_surface_contoning_td_adjustment_enabled); + m_top_surface_contoning_td_adjustment_checkbox->SetMinSize( + wxSize(-1, std::max(m_top_surface_contoning_td_adjustment_checkbox->GetBestSize().GetHeight(), FromDIP(24)))); + contoning_checkboxes_root->Add(m_top_surface_contoning_td_adjustment_checkbox, + 0, + wxEXPAND | wxTOP | wxBOTTOM, + gap / 2); m_top_surface_contoning_only_one_perimeter_around_shell_infill_checkbox = new wxCheckBox(m_top_surface_contoning_checkboxes_panel, wxID_ANY, _L("Only one perimeter around shell infill")); m_top_surface_contoning_only_one_perimeter_around_shell_infill_checkbox->SetValue( @@ -3267,6 +3277,12 @@ public: TextureMappingZone::DefaultTopSurfaceContoningSurfaceAnchoredStacksEnabled : m_top_surface_contoning_surface_anchored_stacks_checkbox->GetValue(); } + bool top_surface_contoning_td_adjustment_enabled() const + { + return m_top_surface_contoning_td_adjustment_checkbox == nullptr ? + TextureMappingZone::DefaultTopSurfaceContoningTdAdjustmentEnabled : + m_top_surface_contoning_td_adjustment_checkbox->GetValue(); + } bool minimum_visibility_offset_enabled() const { return m_minimum_visibility_offset_checkbox && m_minimum_visibility_offset_checkbox->GetValue(); @@ -3723,6 +3739,10 @@ private: 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_td_adjustment_checkbox != nullptr) { + m_top_surface_contoning_td_adjustment_checkbox->Show(contoning); + m_top_surface_contoning_td_adjustment_checkbox->Enable(contoning); + } if (m_top_surface_contoning_only_one_perimeter_around_shell_infill_checkbox != nullptr) { m_top_surface_contoning_only_one_perimeter_around_shell_infill_checkbox->Show(contoning); m_top_surface_contoning_only_one_perimeter_around_shell_infill_checkbox->Enable(contoning); @@ -3856,6 +3876,7 @@ private: wxPanel *m_top_surface_contoning_polygonize_resolution_panel {nullptr}; wxChoice *m_top_surface_contoning_polygonize_resolution_choice {nullptr}; wxCheckBox *m_top_surface_contoning_surface_anchored_stacks_checkbox {nullptr}; + wxCheckBox *m_top_surface_contoning_td_adjustment_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}; @@ -8880,6 +8901,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.top_surface_contoning_polygonize_color_regions_enabled, updated.top_surface_contoning_polygonize_resolution, updated.top_surface_contoning_surface_anchored_stacks_enabled, + updated.top_surface_contoning_td_adjustment_enabled, bundle->texture_mapping_zones, bundle->texture_mapping_global_settings, wxGetApp().model().texture_mapping_prime_tower_image, @@ -8955,6 +8977,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) dlg.top_surface_contoning_polygonize_resolution(); updated.top_surface_contoning_surface_anchored_stacks_enabled = dlg.top_surface_contoning_surface_anchored_stacks_enabled(); + updated.top_surface_contoning_td_adjustment_enabled = + dlg.top_surface_contoning_td_adjustment_enabled(); if (updated.top_surface_image_printing_enabled && updated.top_surface_image_printing_method == int(TextureMappingZone::TopSurfaceImageContoning)) { updated.modulation_mode = int(TextureMappingZone::ModulationPerimeterPathV2);