diff --git a/deps_src/CMakeLists.txt b/deps_src/CMakeLists.txt index bc6a457ad0a..3976a7d569e 100644 --- a/deps_src/CMakeLists.txt +++ b/deps_src/CMakeLists.txt @@ -31,6 +31,7 @@ add_subdirectory(miniz) add_subdirectory(minilzo) add_subdirectory(pigment-painter) add_subdirectory(prusa-fdm-mixer) +add_subdirectory(colorsolver) add_subdirectory(qhull) add_subdirectory(qoi) add_subdirectory(semver) # Semver static library diff --git a/deps_src/colorsolver/CMakeLists.txt b/deps_src/colorsolver/CMakeLists.txt new file mode 100644 index 00000000000..0379fce8d63 --- /dev/null +++ b/deps_src/colorsolver/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.13) +project(colorsolver) + +add_library(colorsolver STATIC + ColorSolver.cpp + ColorSolver.hpp +) + +target_include_directories(colorsolver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_features(colorsolver PUBLIC cxx_std_17) +target_link_libraries(colorsolver PUBLIC pigment_painter prusa_fdm_mixer) diff --git a/src/libslic3r/ColorSolver.cpp b/deps_src/colorsolver/ColorSolver.cpp similarity index 99% rename from src/libslic3r/ColorSolver.cpp rename to deps_src/colorsolver/ColorSolver.cpp index 90cafaf128b..a3da9fe6da1 100644 --- a/src/libslic3r/ColorSolver.cpp +++ b/deps_src/colorsolver/ColorSolver.cpp @@ -1637,6 +1637,11 @@ ColorSolverCandidateSet build_color_solver_candidates(const std::vector> &component_colors, ColorSolverMixModel mix_model, diff --git a/src/libslic3r/ColorSolver.hpp b/deps_src/colorsolver/ColorSolver.hpp similarity index 99% rename from src/libslic3r/ColorSolver.hpp rename to deps_src/colorsolver/ColorSolver.hpp index 71261544494..b0fb8d3cd3b 100644 --- a/src/libslic3r/ColorSolver.hpp +++ b/deps_src/colorsolver/ColorSolver.hpp @@ -169,6 +169,7 @@ std::string color_solver_candidate_cache_key(const std::vector> &component_colors, ColorSolverMixModel mix_model, int total_units = 0); +void build_color_solver_candidate_kd_trees(ColorSolverCandidateSet &candidates); const ColorSolverCandidateSet &color_solver_candidates(ColorSolverCandidateCache &cache, const std::vector> &component_colors, ColorSolverMixModel mix_model, diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index d5c33d625ea..ec79bb4c121 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -148,8 +148,6 @@ set(lisbslic3r_sources Feature/Interlocking/VoxelUtils.cpp Feature/Interlocking/VoxelUtils.hpp FileParserError.hpp - ColorSolver.cpp - ColorSolver.hpp Fill/Fill3DHoneycomb.cpp Fill/Fill3DHoneycomb.hpp Fill/FillAdaptive.cpp @@ -608,8 +606,7 @@ target_link_libraries(libslic3r libnest2d miniz opencv_world - pigment_painter - prusa_fdm_mixer + colorsolver PRIVATE ${CMAKE_DL_LIBS} ${EXPAT_LIBRARIES} diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 2d7480cdabe..d8508129322 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -16,7 +16,7 @@ #endif #include "../ClipperUtils.hpp" #include "../Color.hpp" -#include "../ColorSolver.hpp" +#include "ColorSolver.hpp" #include "../Geometry.hpp" #include "../Layer.hpp" #include "../MarchingSquares.hpp" @@ -12418,8 +12418,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillParams fallback_params = params; fallback_params.flow = fallback_flow; fallback_params.pattern = ipRectilinear; - fallback_params.no_edge_overlap = - !surface_fill.params.texture_mapping_top_surface_contoning_partition_color_regions; + fallback_params.no_edge_overlap = true; + fallback_params.edge_overlap_width_factor = 0.5f; ExtrusionEntitiesPtr hybrid_interior_entities; if (!hybrid_boundary_skin && collection && !collection->empty()) { apply_top_surface_image_collection_metadata(*collection, surface_fill.params, std::nullopt, throw_if_canceled_ptr); diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index d60fdb7b80c..b202d59cdd1 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -98,6 +98,7 @@ struct FillParams bool dont_sort{ false }; // do not sort the lines, just simply connect them bool can_reverse{true}; bool no_edge_overlap{false}; + float edge_overlap_width_factor{1.0f}; float horiz_move{0.0}; //move infill to get cross zag pattern bool symmetric_infill_y_axis{false}; diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 3d4994e73d4..d7735cbfe60 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -2768,7 +2768,9 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa assert(params.density > 0.0001f && params.density <= 1.f); coord_t line_spacing = coord_t(scale_(this->spacing) / params.density); const coordf_t edge_width = std::max(this->spacing, coordf_t(params.flow.width())); - const coordf_t edge_overlap_width = std::min(0.075 * edge_width, coordf_t(0.05)); + const coordf_t edge_overlap_width = + std::clamp(params.edge_overlap_width_factor, coordf_t(0.0), coordf_t(1.0)) * + std::min(0.075 * edge_width, coordf_t(0.05)); const coordf_t outer_clearance = params.no_edge_overlap ? std::max(0.5 * edge_width - edge_overlap_width, coordf_t(0.0)) : (0.5f - INFILL_OVERLAP_OVER_SPACING) * this->spacing; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 42c074bd911..7dff61f818c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6122,8 +6122,10 @@ static TransmissionDistanceCalibrationContextForGCode transmission_distance_cali TransmissionDistanceCalibrationContextForGCode context; context.mode = std::clamp(zone.transmission_distance_calibration_mode, int(TextureMappingZone::TDCalibrationNone), - int(TextureMappingZone::TDCalibrationNeighbor)); - if (context.mode == int(TextureMappingZone::TDCalibrationNone) || component_ids.empty()) + int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)); + if (context.mode == int(TextureMappingZone::TDCalibrationNone) || + context.mode == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample) || + component_ids.empty()) return context; std::vector explicit_tds(component_ids.size(), 0.f); @@ -8272,6 +8274,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( float halftone_dot_size_mm, const std::vector &component_strength_factors, const std::vector &component_minimum_offset_factors, + const GCodeGenericMixCandidateSet *calibrated_side_candidates, std::map *generic_mix_candidate_cache, std::map *uv_texture_triangle_cache, float texture_contrast_pct, @@ -8800,8 +8803,11 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( int(TextureMappingZone::DitheringClosest), int(TextureMappingZone::DitheringHalftoneV2)); std::vector binary_dither_masks; + const bool has_calibrated_side_candidates = + calibrated_side_candidates != nullptr && !calibrated_side_candidates->empty(); const bool can_binary_dither = dithering_enabled && + !has_calibrated_side_candidates && !raw_values_mode && !is_halftone_dithering_method_for_gcode(clamped_binary_dither_method) && component_count > 0 && @@ -8958,7 +8964,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( const size_t sample_count = samples.size(); GCodeGenericMixCandidateSet local_generic_mix_candidates; const GCodeGenericMixCandidateSet *generic_mix_candidates = nullptr; - if (!raw_values_mode) { + if (!raw_values_mode && !has_calibrated_side_candidates) { std::vector optimized_probe; if (!use_fixed_color_generic_solver) { const std::array probe_target { 0.f, 0.f, 0.f }; @@ -9038,6 +9044,14 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( for (size_t channel_idx = 0; channel_idx < channel_count; ++channel_idx) desired[channel_idx] = clamp01f_for_gcode(channels[channel_idx]); mapped_component_count = channel_count; + } else if (has_calibrated_side_candidates) { + std::vector calibrated = + best_component_mix_weights_for_target_for_gcode(*calibrated_side_candidates, + target, + int(TextureMappingZone::GenericSolverClosestMix), + effective_solver_mode); + if (calibrated.size() == component_count) + desired = std::move(calibrated); } else { std::vector optimized; if (!use_fixed_color_generic_solver) @@ -9061,7 +9075,7 @@ static VertexColorOverhangWeightField build_vertex_color_weight_field_for_gcode( } } - if (!has_binary_dither && !has_raw_component_weights && std::abs(contrast_factor - 1.f) > 1e-5f) + if (!has_binary_dither && !has_raw_component_weights && !has_calibrated_side_candidates && std::abs(contrast_factor - 1.f) > 1e-5f) apply_texture_contrast_to_mapped_components_for_gcode(desired, contrast_factor, mapped_component_count); for (size_t component_idx = 0; component_idx < component_count; ++component_idx) { @@ -9550,9 +9564,18 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const std::clamp(zone->tone_gamma, 0.5f, 3.f); const bool high_resolution_texture_sampling = zone->high_resolution_sampling || dithering_enabled; const bool high_speed_image_texture_sampling = zone->high_speed_image_texture_sampling; - const std::vector component_strength_factors = overhang_component_strength_factors_for_gcode(*zone, component_ids); - const std::vector component_minimum_offset_factors = - overhang_component_minimum_offset_factors_for_gcode(*zone, component_ids); + const bool calibrated_side_mode = + vertex_color_match_mode && + !raw_texture_mapping_mode && + zone->transmission_distance_calibration_mode == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample); + std::vector component_strength_factors; + component_strength_factors.reserve(component_ids.size()); + std::vector component_minimum_offset_factors; + component_minimum_offset_factors.reserve(component_ids.size()); + for (const unsigned int id : component_ids) { + component_strength_factors.emplace_back(calibrated_side_mode ? 1.f : overhang_filament_strength_factor_for_gcode(*zone, id)); + component_minimum_offset_factors.emplace_back(calibrated_side_mode ? 0.f : overhang_filament_minimum_offset_factor_for_gcode(*zone, id)); + } std::vector> component_colors; component_colors.reserve(component_ids.size()); @@ -9579,6 +9602,26 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const (missing_component_color || component_colors.size() != component_ids.size() || component_colors.empty())) return std::nullopt; + std::vector component_transmission_distances_mm; + component_transmission_distances_mm.reserve(component_ids.size()); + for (const unsigned int id : component_ids) { + const size_t idx = id > 0 ? size_t(id - 1) : size_t(-1); + const float value = idx < zone->filament_transmission_distances_mm.size() ? + zone->filament_transmission_distances_mm[idx] : + 0.f; + component_transmission_distances_mm.emplace_back(std::isfinite(value) && value > 0.f ? std::clamp(value, 0.01f, 50.f) : 0.f); + } + std::optional calibrated_side_candidates; + const GCodeGenericMixCandidateSet *calibrated_side_candidates_ptr = nullptr; + if (calibrated_side_mode) { + calibrated_side_candidates = + texture_mapping_side_surface_color_calibrated_candidates(*zone, + component_colors, + component_transmission_distances_mm); + if (calibrated_side_candidates && !calibrated_side_candidates->empty()) + calibrated_side_candidates_ptr = &*calibrated_side_candidates; + } + const TransmissionDistanceCalibrationContextForGCode td_calibration_context = transmission_distance_calibration_context_for_gcode(*zone, component_ids, @@ -9613,6 +9656,8 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const component_key_stream << "|tg" << int(std::lround(texture_tone_gamma * 100.f)); component_key_stream << "|hr" << (high_resolution_texture_sampling ? 1 : 0); component_key_stream << "|hs" << (high_speed_image_texture_sampling ? 1 : 0); + if (calibrated_side_mode) + component_key_stream << "|sc" << std::hash{}(zone->side_surface_color_calibration_json); const std::string component_key_prefix = component_key_stream.str(); struct SeamLayerTextureState { @@ -9683,6 +9728,7 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const halftone_dot_size_mm, component_strength_factors, component_minimum_offset_factors, + calibrated_side_candidates_ptr, &m_generic_solver_mix_candidate_cache, &m_uv_texture_triangle_cache, texture_contrast_pct, @@ -9712,9 +9758,9 @@ std::optional GCode::texture_mapping_seam_hiding_hint(const active_component_id, active_component_idx, weight_field, - texture_mapping_offset_filament_strength_factor(*zone, active_component_id), - texture_mapping_offset_filament_minimum_offset_factor(*zone, active_component_id), - transmission_distance_width_factor_for_gcode(td_calibration_context, active_component_idx, previous_component_idx), + calibrated_side_mode ? 1.f : texture_mapping_offset_filament_strength_factor(*zone, active_component_id), + calibrated_side_mode ? 0.f : texture_mapping_offset_filament_minimum_offset_factor(*zone, active_component_id), + calibrated_side_mode ? 1.f : transmission_distance_width_factor_for_gcode(td_calibration_context, active_component_idx, previous_component_idx), signed_fade_factor, fade_factor }; diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 93a35695a1d..3e018bb182a 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -15,7 +15,7 @@ #include "libslic3r/Point.hpp" #include "libslic3r/Polygon.hpp" -#include "libslic3r/ColorSolver.hpp" +#include "ColorSolver.hpp" #include "libslic3r/TextureMapping.hpp" #include "libslic3r/Polyline.hpp" #include "libslic3r/TriangleMesh.hpp" diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index e1b1d7b76e6..452f0c2a338 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -1125,20 +1125,31 @@ static std::string transmission_distance_calibration_mode_name(int mode) { switch (clamp_int(mode, int(TextureMappingZone::TDCalibrationNone), - int(TextureMappingZone::TDCalibrationNeighbor))) { + int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample))) { case int(TextureMappingZone::TDCalibrationNone): return "none"; case int(TextureMappingZone::TDCalibrationNeighbor): return "neighbor"; + case int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample): + return "calibrated_nearest_measured_sample"; default: return "absolute"; } } static int transmission_distance_calibration_mode_from_name(std::string name) { - std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return char(std::tolower(c)); }); + std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { + const char lowered = char(std::tolower(c)); + return lowered == '-' || lowered == ' ' ? '_' : lowered; + }); if (name == "none" || name == "off" || name == "disabled") return int(TextureMappingZone::TDCalibrationNone); - if (name == "neighbor" || name == "neighbour") + if (name == "neighbor" || name == "neighbour" || name == "neighbor_td" || name == "neighbour_td") return int(TextureMappingZone::TDCalibrationNeighbor); + if (name == "calibrated_nearest_measured_sample" || + name == "nearest_measured_sample" || + name == "nearest_measured_sample_global" || + name == "measured_sample" || + name == "measured_samples") + return int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample); return int(TextureMappingZone::TDCalibrationAbsolute); } @@ -1151,7 +1162,7 @@ static int transmission_distance_calibration_mode_from_json(const nlohmann::json if (mode_it->is_number_integer() || mode_it->is_number_unsigned()) return clamp_int(mode_it->get(), int(TextureMappingZone::TDCalibrationNone), - int(TextureMappingZone::TDCalibrationNeighbor)); + int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)); if (mode_it->is_boolean()) return mode_it->get() ? int(TextureMappingZone::TDCalibrationAbsolute) : @@ -1334,6 +1345,11 @@ bool TextureMappingColorCalibration::has_mode(int mode) const } } +bool TextureMappingSideSurfaceColorCalibration::has_nearest_measured_sample() const +{ + return !nearest_measured_sample.empty(); +} + namespace { static const nlohmann::json *calibration_model_json(const nlohmann::json &models, const char *base_key) @@ -1348,6 +1364,37 @@ static const nlohmann::json *calibration_model_json(const nlohmann::json &models return it != models.end() ? &*it : nullptr; } +static const nlohmann::json *surface_calibration_json(const nlohmann::json &root, const char *surface_key) +{ + if (!root.is_object()) + return nullptr; + auto surfaces_it = root.find("surface_calibrations"); + if (surfaces_it != root.end() && surfaces_it->is_object()) { + auto surface_it = surfaces_it->find(surface_key); + if (surface_it != surfaces_it->end() && surface_it->is_object()) + return &*surface_it; + } + auto root_surface_it = root.find(surface_key); + if (root_surface_it != root.end() && root_surface_it->is_object()) + return &*root_surface_it; + return nullptr; +} + +static nlohmann::json calibration_models_from_root_or_surface(const nlohmann::json &root, const nlohmann::json *surface) +{ + if (root.contains("weights") && root["weights"].is_object() && !root["weights"].empty()) + return root["weights"]; + if (root.contains("models") && root["models"].is_object() && !root["models"].empty()) + return root["models"]; + if (surface != nullptr) { + if (surface->contains("weights") && (*surface)["weights"].is_object()) + return (*surface)["weights"]; + if (surface->contains("models") && (*surface)["models"].is_object()) + return (*surface)["models"]; + } + return nlohmann::json::object(); +} + static ColorSolverCalibratedStackModel calibration_current_linear_affine_model(const nlohmann::json &raw) { ColorSolverCalibratedStackModel model; @@ -1617,6 +1664,78 @@ static std::vector calibration_filaments return filaments; } +static std::vector side_surface_swatch_weights_from_json(const nlohmann::json &raw, size_t component_count) +{ + std::vector weights = json_float_vector_or_empty(raw, { "raw_offsets", "raw_filament_offsets" }); + if (!weights.empty()) { + if (weights.size() < component_count) + return {}; + weights.resize(component_count); + for (float &value : weights) + value = std::clamp((std::isfinite(value) ? value : 0.f) / 255.f, 0.f, 1.f); + return weights; + } + + weights = json_float_vector_or_empty(raw, { "normalized_weights", "weights", "component_weights" }); + if (weights.size() < component_count) + return {}; + weights.resize(component_count); + for (float &value : weights) + value = std::clamp(std::isfinite(value) ? value : 0.f, 0.f, 1.f); + return weights; +} + +static ColorSolverCandidateSet side_surface_nearest_measured_sample_candidates(const nlohmann::json &surface, + size_t fallback_component_count) +{ + ColorSolverCandidateSet candidates; + if (!surface.is_object()) + return candidates; + auto swatches_it = surface.find("swatches"); + if (swatches_it == surface.end() || !swatches_it->is_array()) + return candidates; + + size_t component_count = fallback_component_count; + if (component_count == 0) { + for (const nlohmann::json &swatch : *swatches_it) { + const std::vector raw_offsets = + json_float_vector_or_empty(swatch, { "raw_offsets", "raw_filament_offsets", "normalized_weights", "weights", "component_weights" }); + if (!raw_offsets.empty()) { + component_count = raw_offsets.size(); + break; + } + } + } + if (component_count == 0) + return candidates; + + candidates.component_count = component_count; + candidates.rgbs.reserve(swatches_it->size() * 3); + candidates.perceptual_coords.reserve(swatches_it->size() * 3); + candidates.weights.reserve(swatches_it->size() * component_count); + for (const nlohmann::json &swatch : *swatches_it) { + std::optional> rgb = calibration_measured_sample_rgb_from_json(swatch); + if (!rgb) + continue; + std::vector weights = side_surface_swatch_weights_from_json(swatch, component_count); + if (weights.size() != component_count) + continue; + const std::array perceptual = color_solver_oklab_from_srgb(*rgb); + candidates.rgbs.emplace_back((*rgb)[0]); + candidates.rgbs.emplace_back((*rgb)[1]); + candidates.rgbs.emplace_back((*rgb)[2]); + candidates.perceptual_coords.emplace_back(perceptual[0]); + candidates.perceptual_coords.emplace_back(perceptual[1]); + candidates.perceptual_coords.emplace_back(perceptual[2]); + candidates.weights.insert(candidates.weights.end(), weights.begin(), weights.end()); + } + + if (candidates.empty()) + return {}; + build_color_solver_candidate_kd_trees(candidates); + return candidates; +} + static const ColorSolverCalibratedStackModel &calibration_model_for_mode(const TextureMappingColorCalibration &calibration, int mode) { @@ -1654,7 +1773,8 @@ static float calibration_color_distance(const std::array &lhs, const s static TextureMappingCalibrationMatch match_calibration_filaments(const TextureMappingColorCalibration &calibration, const std::vector> &component_colors, const std::vector &component_tds_mm, - int filament_color_mode) + int filament_color_mode, + const char *warning_prefix = "Top-surface color calibration") { TextureMappingCalibrationMatch match; match.calibration_index_by_component.assign(component_colors.size(), size_t(-1)); @@ -1742,7 +1862,7 @@ static TextureMappingCalibrationMatch match_calibration_filaments(const TextureM if (!match.complete) notes.emplace_back("the calibration file does not contain enough matching filaments"); if (!notes.empty()) { - match.warning = "Top-surface color calibration mismatch: "; + match.warning = std::string(warning_prefix != nullptr ? warning_prefix : "Calibration") + " mismatch: "; for (size_t idx = 0; idx < notes.size(); ++idx) { if (idx > 0) match.warning += "; "; @@ -1874,6 +1994,41 @@ static std::optional remap_calibration_model( return std::nullopt; } +static std::optional remap_side_surface_candidate_set( + const ColorSolverCandidateSet &source, + const TextureMappingCalibrationMatch &match, + size_t target_component_count) +{ + if (source.empty() || !match.complete || target_component_count == 0) + return std::nullopt; + + ColorSolverCandidateSet out; + out.component_count = target_component_count; + const size_t source_component_count = source.component_count; + const size_t candidate_count = source.rgbs.size() / 3; + if (source_component_count == 0 || + source.weights.size() != candidate_count * source_component_count || + source.perceptual_coords.size() != source.rgbs.size()) + return std::nullopt; + + out.rgbs = source.rgbs; + out.perceptual_coords = source.perceptual_coords; + out.weights.assign(candidate_count * target_component_count, 0.f); + for (size_t candidate_idx = 0; candidate_idx < candidate_count; ++candidate_idx) { + const size_t src_begin = candidate_idx * source_component_count; + const size_t dst_begin = candidate_idx * target_component_count; + for (size_t component_idx = 0; component_idx < target_component_count; ++component_idx) { + const size_t source_idx = match.calibration_index_by_component[component_idx]; + if (source_idx >= source_component_count) + return std::nullopt; + out.weights[dst_begin + component_idx] = source.weights[src_begin + source_idx]; + } + } + + build_color_solver_candidate_kd_trees(out); + return out.empty() ? std::nullopt : std::optional(out); +} + } // namespace std::optional texture_mapping_parse_top_surface_color_calibration( @@ -1893,11 +2048,12 @@ std::optional texture_mapping_parse_top_surface_ } TextureMappingColorCalibration calibration; + const nlohmann::json *top_surface = surface_calibration_json(root, "top_surface"); calibration.display_name = json_string_or_empty(root, { "display_name", "name", "profile_id" }); + if (calibration.display_name.empty() && top_surface != nullptr) + calibration.display_name = json_string_or_empty(*top_surface, { "display_name", "name", "profile_id" }); calibration.filaments = calibration_filaments_from_json(root); - const nlohmann::json models = root.contains("weights") && root["weights"].is_object() ? - root["weights"] : - root.value("models", nlohmann::json::object()); + const nlohmann::json models = calibration_models_from_root_or_surface(root, top_surface); if (const nlohmann::json *raw = calibration_model_json(models, "current_linear_affine")) calibration.current_linear_affine = calibration_current_linear_affine_model(*raw); @@ -1927,6 +2083,46 @@ std::optional texture_mapping_parse_top_surface_ return calibration; } +std::optional texture_mapping_parse_side_surface_color_calibration( + const std::string &json_text, + std::string *error) +{ + if (error != nullptr) + error->clear(); + if (json_text.empty()) + return std::nullopt; + + nlohmann::json root = nlohmann::json::parse(json_text, nullptr, false); + if (root.is_discarded() || !root.is_object()) { + if (error != nullptr) + *error = "The calibration file is not valid JSON."; + return std::nullopt; + } + + const nlohmann::json *side_surface = surface_calibration_json(root, "side_surface"); + if (side_surface == nullptr) { + if (error != nullptr) + *error = "The calibration file does not contain side-surface calibration data."; + return std::nullopt; + } + + TextureMappingSideSurfaceColorCalibration calibration; + calibration.display_name = json_string_or_empty(root, { "display_name", "name", "profile_id" }); + if (calibration.display_name.empty()) + calibration.display_name = json_string_or_empty(*side_surface, { "display_name", "name", "profile_id" }); + calibration.filaments = calibration_filaments_from_json(root); + calibration.nearest_measured_sample = + side_surface_nearest_measured_sample_candidates(*side_surface, calibration.filaments.size()); + + if (!calibration.has_nearest_measured_sample()) { + if (error != nullptr) + *error = "The side-surface calibration does not contain supported measured swatches."; + return std::nullopt; + } + + return calibration; +} + std::vector texture_mapping_top_surface_color_calibration_supported_modes(const std::string &json_text) { std::vector out; @@ -1983,6 +2179,13 @@ std::string texture_mapping_top_surface_color_calibration_display_name(const std return calibration ? calibration->display_name : std::string(); } +std::string texture_mapping_side_surface_color_calibration_display_name(const std::string &json_text) +{ + std::optional calibration = + texture_mapping_parse_side_surface_color_calibration(json_text); + return calibration ? calibration->display_name : std::string(); +} + std::string texture_mapping_top_surface_color_calibration_warning(const TextureMappingZone &zone, const std::vector> &component_colors, const std::vector &component_tds_mm) @@ -2007,6 +2210,30 @@ std::string texture_mapping_top_surface_color_calibration_warning(const TextureM return std::string(); } +std::string texture_mapping_side_surface_color_calibration_warning(const TextureMappingZone &zone, + const std::vector> &component_colors, + const std::vector &component_tds_mm) +{ + if (zone.side_surface_color_calibration_json.empty()) + return std::string(); + std::string error; + std::optional side_calibration = + texture_mapping_parse_side_surface_color_calibration(zone.side_surface_color_calibration_json, &error); + if (!side_calibration) + return error.empty() ? "Side-surface color calibration could not be loaded." : error; + + TextureMappingColorCalibration calibration; + calibration.display_name = side_calibration->display_name; + calibration.filaments = side_calibration->filaments; + const TextureMappingCalibrationMatch match = + match_calibration_filaments(calibration, + component_colors, + component_tds_mm, + zone.filament_color_mode, + "Side-surface color calibration"); + return match.warning; +} + std::optional texture_mapping_top_surface_color_calibrated_model( const TextureMappingZone &zone, int mode, @@ -2035,6 +2262,62 @@ std::optional texture_mapping_top_surface_color return remap_calibration_model(source, mode, match, component_colors.size()); } +std::optional texture_mapping_side_surface_color_calibrated_candidates( + const TextureMappingZone &zone, + const std::vector> &component_colors, + const std::vector &component_tds_mm, + std::string *warning) +{ + if (warning != nullptr) + warning->clear(); + std::string error; + std::optional side_calibration = + texture_mapping_parse_side_surface_color_calibration(zone.side_surface_color_calibration_json, &error); + if (!side_calibration) { + if (warning != nullptr) + *warning = error; + return std::nullopt; + } + + TextureMappingColorCalibration calibration; + calibration.display_name = side_calibration->display_name; + calibration.filaments = side_calibration->filaments; + const TextureMappingCalibrationMatch match = + match_calibration_filaments(calibration, + component_colors, + component_tds_mm, + zone.filament_color_mode, + "Side-surface color calibration"); + if (warning != nullptr) + *warning = match.warning; + return remap_side_surface_candidate_set(side_calibration->nearest_measured_sample, + match, + component_colors.size()); +} + +std::vector texture_mapping_calibration_component_transmission_distances( + const std::vector &filaments, + const std::vector> &component_colors, + const std::vector &component_tds_mm, + int filament_color_mode) +{ + TextureMappingColorCalibration calibration; + calibration.filaments = filaments; + const TextureMappingCalibrationMatch match = + match_calibration_filaments(calibration, component_colors, component_tds_mm, filament_color_mode); + std::vector out(component_colors.size(), 0.f); + if (!match.complete) + return out; + for (size_t component_idx = 0; component_idx < component_colors.size(); ++component_idx) { + const size_t calibration_idx = match.calibration_index_by_component[component_idx]; + if (calibration_idx < filaments.size() && + std::isfinite(filaments[calibration_idx].td_mm) && + filaments[calibration_idx].td_mm > 0.f) + out[component_idx] = std::clamp(filaments[calibration_idx].td_mm, 0.01f, 50.f); + } + return out; +} + bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const { constexpr float eps = 1e-6f; @@ -2154,6 +2437,8 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const effective_top_surface_contoning_beam_search_stack_expansion_enabled() == rhs.effective_top_surface_contoning_beam_search_stack_expansion_enabled() && top_surface_color_calibration_name == rhs.top_surface_color_calibration_name && top_surface_color_calibration_json == rhs.top_surface_color_calibration_json && + side_surface_color_calibration_name == rhs.side_surface_color_calibration_name && + side_surface_color_calibration_json == rhs.side_surface_color_calibration_json && 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 && @@ -2584,6 +2869,12 @@ std::string TextureMappingManager::serialize_entries() texture["top_surface_color_calibration"] = calibration.is_discarded() ? nlohmann::json(zone.top_surface_color_calibration_json) : std::move(calibration); } + if (!zone.side_surface_color_calibration_json.empty()) { + texture["side_surface_color_calibration_name"] = zone.side_surface_color_calibration_name; + nlohmann::json calibration = nlohmann::json::parse(zone.side_surface_color_calibration_json, nullptr, false); + texture["side_surface_color_calibration"] = + calibration.is_discarded() ? nlohmann::json(zone.side_surface_color_calibration_json) : std::move(calibration); + } 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; @@ -2949,6 +3240,19 @@ void TextureMappingManager::load_entries(const std::string &serialized, zone.top_surface_contoning_color_prediction_mode = texture_mapping_top_surface_color_calibration_first_supported_mode(zone.top_surface_color_calibration_json); } + zone.side_surface_color_calibration_name = + texture.value("side_surface_color_calibration_name", std::string()); + zone.side_surface_color_calibration_json.clear(); + auto side_color_calibration_it = texture.find("side_surface_color_calibration"); + if (side_color_calibration_it != texture.end()) { + zone.side_surface_color_calibration_json = + side_color_calibration_it->is_string() ? + side_color_calibration_it->get() : + side_color_calibration_it->dump(); + if (zone.side_surface_color_calibration_name.empty()) + zone.side_surface_color_calibration_name = + texture_mapping_side_surface_color_calibration_display_name(zone.side_surface_color_calibration_json); + } zone.apply_top_surface_contoning_experimental_defaults(); zone.compact_offset_mode = texture.value("compact_offset_mode", TextureMappingZone::DefaultCompactOffsetMode); zone.use_legacy_fixed_color_mode = @@ -2984,6 +3288,11 @@ void TextureMappingManager::load_entries(const std::string &serialized, zone.high_resolution_sampling = true; zone.tone_gamma = normalize_tone_gamma(texture.value("tone_gamma", 1.f)); zone.transmission_distance_calibration_mode = transmission_distance_calibration_mode_from_json(texture); + if (zone.transmission_distance_calibration_mode == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample) && + !texture_mapping_parse_side_surface_color_calibration(zone.side_surface_color_calibration_json)) { + zone.transmission_distance_calibration_mode = + TextureMappingZone::DefaultTransmissionDistanceCalibrationMode; + } zone.auto_adjust_filament_selection = texture.value("auto_adjust_filaments", true); zone.filament_strengths_pct = normalize_strengths(floats_from_json(texture.value("strength_pct", nlohmann::json::array()))); zone.filament_minimum_offsets_pct = diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp index b770c45d1dc..f6b68015f4e 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -154,7 +154,8 @@ struct TextureMappingZone enum TransmissionDistanceCalibrationMode : uint8_t { TDCalibrationNone = 0, TDCalibrationAbsolute = 1, - TDCalibrationNeighbor = 2 + TDCalibrationNeighbor = 2, + TDCalibrationCalibratedNearestMeasuredSample = 3 }; static constexpr int DefaultSurfacePattern = int(ImageTexture); @@ -518,6 +519,8 @@ struct TextureMappingZone bool top_surface_contoning_beam_search_stack_expansion_enabled = DefaultTopSurfaceContoningBeamSearchStackExpansionEnabled; std::string top_surface_color_calibration_name; std::string top_surface_color_calibration_json; + std::string side_surface_color_calibration_name; + std::string side_surface_color_calibration_json; bool compact_offset_mode = DefaultCompactOffsetMode; bool use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode; bool high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling; @@ -823,6 +826,8 @@ struct TextureMappingZone top_surface_contoning_beam_search_stack_expansion_enabled = DefaultTopSurfaceContoningBeamSearchStackExpansionEnabled; top_surface_color_calibration_name.clear(); top_surface_color_calibration_json.clear(); + side_surface_color_calibration_name.clear(); + side_surface_color_calibration_json.clear(); compact_offset_mode = DefaultCompactOffsetMode; use_legacy_fixed_color_mode = DefaultUseLegacyFixedColorMode; high_speed_image_texture_sampling = DefaultHighSpeedImageTextureSampling; @@ -893,21 +898,46 @@ struct TextureMappingColorCalibration { bool has_mode(int mode) const; }; +struct TextureMappingSideSurfaceColorCalibration { + std::string display_name; + std::vector filaments; + ColorSolverCandidateSet nearest_measured_sample; + + bool has_nearest_measured_sample() const; +}; + std::optional texture_mapping_parse_top_surface_color_calibration( const std::string &json_text, std::string *error = nullptr); +std::optional texture_mapping_parse_side_surface_color_calibration( + const std::string &json_text, + std::string *error = nullptr); std::vector texture_mapping_top_surface_color_calibration_supported_modes(const std::string &json_text); int texture_mapping_top_surface_color_calibration_first_supported_mode(const std::string &json_text); std::string texture_mapping_top_surface_color_calibration_display_name(const std::string &json_text); +std::string texture_mapping_side_surface_color_calibration_display_name(const std::string &json_text); std::string texture_mapping_top_surface_color_calibration_warning(const TextureMappingZone &zone, const std::vector> &component_colors, const std::vector &component_tds_mm); +std::string texture_mapping_side_surface_color_calibration_warning(const TextureMappingZone &zone, + const std::vector> &component_colors, + const std::vector &component_tds_mm); std::optional texture_mapping_top_surface_color_calibrated_model( const TextureMappingZone &zone, int mode, const std::vector> &component_colors, const std::vector &component_tds_mm, std::string *warning = nullptr); +std::optional texture_mapping_side_surface_color_calibrated_candidates( + const TextureMappingZone &zone, + const std::vector> &component_colors, + const std::vector &component_tds_mm, + std::string *warning = nullptr); +std::vector texture_mapping_calibration_component_transmission_distances( + const std::vector &filaments, + const std::vector> &component_colors, + const std::vector &component_tds_mm, + int filament_color_mode); struct TextureMappingPrimeTowerImage { diff --git a/src/libslic3r/TextureMappingOffset.cpp b/src/libslic3r/TextureMappingOffset.cpp index aea5e9be5b6..5868f7bac9a 100644 --- a/src/libslic3r/TextureMappingOffset.cpp +++ b/src/libslic3r/TextureMappingOffset.cpp @@ -1630,11 +1630,14 @@ std::vector component_weights_for_sample(const WeightedTextureSample &sam float contrast_factor, float tone_gamma, const std::vector> &component_colors, - const ColorSolverCandidateSet *generic_mix_candidates) + const ColorSolverCandidateSet *generic_mix_candidates, + const ColorSolverCandidateSet *calibrated_side_candidates) { std::vector desired(component_count, 0.f); size_t mapped_component_count = component_count; const bool has_raw_component_weights = sample.raw_component_weights.size() == component_count; + const bool has_calibrated_side_candidates = + calibrated_side_candidates != nullptr && !calibrated_side_candidates->empty(); const int effective_solver_mode = TextureMappingZone::effective_generic_solver_mode(generic_solver_mode); if (has_raw_component_weights) { float raw_activity = 0.f; @@ -1662,6 +1665,14 @@ std::vector component_weights_for_sample(const WeightedTextureSample &sam for (size_t channel_idx = 0; channel_idx < channel_count; ++channel_idx) desired[channel_idx] = clamp01f(channels[channel_idx]); mapped_component_count = channel_count; + } else if (has_calibrated_side_candidates) { + std::vector calibrated = + solve_color_solver_weights_for_target(*calibrated_side_candidates, + target, + ColorSolverLookupMode::ClosestMix, + color_solver_mode_from_index(effective_solver_mode)); + if (calibrated.size() == component_count) + desired = std::move(calibrated); } else { std::vector optimized; if (!use_fixed_color_generic_solver) @@ -1684,7 +1695,7 @@ std::vector component_weights_for_sample(const WeightedTextureSample &sam } } - if (!has_raw_component_weights && std::abs(contrast_factor - 1.f) > 1e-5f) + if (!has_raw_component_weights && !has_calibrated_side_candidates && std::abs(contrast_factor - 1.f) > 1e-5f) apply_texture_contrast_to_components(desired, contrast_factor, mapped_component_count); for (float &v : desired) v = clamp01f(v); @@ -1717,6 +1728,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(const bool high_speed_image_texture_sampling, std::optional texture_sample_pitch_mm_override, std::optional> image_background_rgba_override, + const ColorSolverCandidateSet *calibrated_side_candidates, std::optional raw_top_surface_depth_override) { TextureMappingOffsetWeightField weight_field; @@ -2099,8 +2111,11 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(const int(TextureMappingZone::DitheringClosest), int(TextureMappingZone::DitheringHalftoneV2)); std::vector binary_dither_masks; + const bool has_calibrated_side_candidates = + calibrated_side_candidates != nullptr && !calibrated_side_candidates->empty(); const bool can_binary_dither = dithering_enabled && + !has_calibrated_side_candidates && !raw_values_mode && !is_halftone_dithering_method(clamped_binary_dither_method) && component_count > 0 && @@ -2245,7 +2260,7 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(const use_fixed_color_generic_solver ? fixed_colors : component_colors; ColorSolverCandidateSet candidates; const ColorSolverCandidateSet *candidate_ptr = nullptr; - if (!raw_values_mode) { + if (!raw_values_mode && !has_calibrated_side_candidates) { candidates = build_color_solver_candidates(solver_colors, color_solver_mix_model_from_index(generic_solver_mix_model)); candidate_ptr = candidates.empty() ? nullptr : &candidates; } @@ -2301,7 +2316,8 @@ TextureMappingOffsetWeightField build_texture_mapping_offset_weight_field(const contrast_factor, tone_gamma, component_colors, - candidate_ptr); + candidate_ptr, + calibrated_side_candidates); } for (size_t component_idx = 0; component_idx < component_count; ++component_idx) { const float v = component_idx < desired.size() ? clamp01f(desired[component_idx]) : 0.f; @@ -2669,8 +2685,10 @@ TransmissionDistanceCalibrationContext transmission_distance_calibration_context TransmissionDistanceCalibrationContext context; context.mode = std::clamp(zone.transmission_distance_calibration_mode, int(TextureMappingZone::TDCalibrationNone), - int(TextureMappingZone::TDCalibrationNeighbor)); - if (context.mode == int(TextureMappingZone::TDCalibrationNone) || component_ids.empty()) + int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)); + if (context.mode == int(TextureMappingZone::TDCalibrationNone) || + context.mode == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample) || + component_ids.empty()) return context; std::vector explicit_tds(component_ids.size(), 0.f); @@ -3609,13 +3627,36 @@ std::optional build_texture_mapping_offset_context_ TextureMappingZone::MaxHalftoneDotSizeMm); const bool high_resolution_texture_sampling = zone.high_resolution_sampling || dithering_enabled; const bool compact_offset_mode = halftone_dithering_enabled ? false : zone.compact_offset_mode || dithering_enabled; + const bool calibrated_side_mode = + vertex_color_match_mode && + !raw_texture_mapping_mode && + zone.transmission_distance_calibration_mode == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample); std::vector component_strength_factors; component_strength_factors.reserve(component_ids.size()); std::vector component_minimum_offset_factors; component_minimum_offset_factors.reserve(component_ids.size()); for (const unsigned int id : component_ids) { - component_strength_factors.emplace_back(texture_mapping_offset_filament_strength_factor(zone, id)); - component_minimum_offset_factors.emplace_back(texture_mapping_offset_filament_minimum_offset_factor(zone, id)); + component_strength_factors.emplace_back(calibrated_side_mode ? 1.f : texture_mapping_offset_filament_strength_factor(zone, id)); + component_minimum_offset_factors.emplace_back(calibrated_side_mode ? 0.f : texture_mapping_offset_filament_minimum_offset_factor(zone, id)); + } + std::vector component_transmission_distances_mm; + component_transmission_distances_mm.reserve(component_ids.size()); + for (const unsigned int id : component_ids) { + const size_t idx = id > 0 ? size_t(id - 1) : size_t(-1); + const float value = idx < zone.filament_transmission_distances_mm.size() ? + zone.filament_transmission_distances_mm[idx] : + 0.f; + component_transmission_distances_mm.emplace_back(std::isfinite(value) && value > 0.f ? std::clamp(value, 0.01f, 50.f) : 0.f); + } + std::optional calibrated_side_candidates; + const ColorSolverCandidateSet *calibrated_side_candidates_ptr = nullptr; + if (calibrated_side_mode) { + calibrated_side_candidates = + texture_mapping_side_surface_color_calibrated_candidates(zone, + component_colors, + component_transmission_distances_mm); + if (calibrated_side_candidates && !calibrated_side_candidates->empty()) + calibrated_side_candidates_ptr = &*calibrated_side_candidates; } TextureMappingOffsetWeightField weight_field; @@ -3632,7 +3673,8 @@ std::optional build_texture_mapping_offset_context_ component_minimum_offset_factors, texture_contrast_pct, texture_tone_gamma, sample_z_mm, layer_sample_falloff_mm, high_resolution_texture_sampling, zone.high_speed_image_texture_sampling, texture_sample_pitch_mm_override, - image_background_rgba_override, raw_top_surface_depth_override); + image_background_rgba_override, calibrated_side_candidates_ptr, + raw_top_surface_depth_override); if (weight_field.empty()) return std::nullopt; } @@ -3734,11 +3776,11 @@ std::optional build_texture_mapping_offset_context_ context.active_halftone_angle_deg = halftone_screen_angle_deg(filament_color_mode, active_component_idx); context.active_component_strength_factor = linear_gradient_mode ? 1.f : - texture_mapping_offset_filament_strength_factor(zone, active_component_id); + (calibrated_side_mode ? 1.f : texture_mapping_offset_filament_strength_factor(zone, active_component_id)); context.active_component_minimum_offset_factor = linear_gradient_mode ? 0.f : - texture_mapping_offset_filament_minimum_offset_factor(zone, active_component_id); - context.active_component_td_width_factor = linear_gradient_mode ? + (calibrated_side_mode ? 0.f : texture_mapping_offset_filament_minimum_offset_factor(zone, active_component_id)); + context.active_component_td_width_factor = linear_gradient_mode || calibrated_side_mode ? 1.f : transmission_distance_width_factor(td_calibration_context, active_component_idx, previous_component_idx); context.base_outer_width_mm = base_outer_width_mm; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index c0e80eaf3e7..0cda296ecd9 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -11,7 +11,7 @@ #include "libslic3r/BuildVolume.hpp" #include "libslic3r/Color.hpp" -#include "libslic3r/ColorSolver.hpp" +#include "ColorSolver.hpp" #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" #include "libslic3r/Geometry.hpp" diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 74355bfe290..6bf925d59e3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -21,7 +21,7 @@ #include "libslic3r/Format/bbs_3mf.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/TextureMapping.hpp" -#include "libslic3r/ColorSolver.hpp" +#include "ColorSolver.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/AABBMesh.hpp" #include "slic3r/Utils/UndoRedo.hpp" diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp index af3210dc386..48a78723e95 100644 --- a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp +++ b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp @@ -14,7 +14,7 @@ #include "libslic3r/PresetBundle.hpp" #include "libslic3r/TextureMapping.hpp" #include "libslic3r/TextureMappingContoning.hpp" -#include "libslic3r/ColorSolver.hpp" +#include "ColorSolver.hpp" #include #include diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 72329349b01..335bd6bfabf 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -73,7 +73,7 @@ #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/DRC.hpp" #include "libslic3r/Format/STEP.hpp" -#include "libslic3r/ColorSolver.hpp" +#include "ColorSolver.hpp" #include "libslic3r/Format/AMF.hpp" //#include "libslic3r/Format/3mf.hpp" #include "libslic3r/Format/bbs_3mf.hpp" @@ -2550,6 +2550,8 @@ public: bool top_surface_contoning_variable_layer_height_compensation_enabled, const std::string &top_surface_color_calibration_name, const std::string &top_surface_color_calibration_json, + const std::string &side_surface_color_calibration_name, + const std::string &side_surface_color_calibration_json, std::function&)> apply_filament_colors, const TextureMappingZoneShellUsageSummary &top_surface_contoning_shell_usage, const TextureMappingManager &texture_mapping_zones, @@ -2569,6 +2571,8 @@ public: , m_apply_filament_colors(std::move(apply_filament_colors)) , m_top_surface_color_calibration_name(top_surface_color_calibration_name) , m_top_surface_color_calibration_json(top_surface_color_calibration_json) + , m_side_surface_color_calibration_name(side_surface_color_calibration_name) + , m_side_surface_color_calibration_json(side_surface_color_calibration_json) { (void) reduce_outer_surface_texture; (void) top_surface_contoning_min_feature_mm; @@ -2671,20 +2675,24 @@ public: auto *td_box = new wxStaticBoxSizer(wxVERTICAL, filament_page, _L("Transmission distance")); auto *td_mode_row = new wxBoxSizer(wxHORIZONTAL); - td_mode_row->Add(new wxStaticText(filament_page, wxID_ANY, _L("TD calibration mode")), + td_mode_row->Add(new wxStaticText(filament_page, wxID_ANY, _L("Side calibration mode")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); wxArrayString td_mode_choices; td_mode_choices.Add(_L("None")); - td_mode_choices.Add(_L("Absolute")); - td_mode_choices.Add(_L("Neighbor")); + td_mode_choices.Add(_L("Absolute TD")); + td_mode_choices.Add(_L("Neighbour TD")); + td_mode_choices.Add(_L("Calibrated: nearest measured sample")); m_transmission_distance_calibration_mode_choice = new wxChoice(filament_page, wxID_ANY, wxDefaultPosition, wxDefaultSize, td_mode_choices); m_transmission_distance_calibration_mode_choice->SetSelection( std::clamp(transmission_distance_calibration_mode, int(TextureMappingZone::TDCalibrationNone), - int(TextureMappingZone::TDCalibrationNeighbor))); + int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample))); + m_transmission_distance_calibration_mode_choice->Bind(wxEVT_CHOICE, [this](wxCommandEvent &) { + update_strength_offsets_visibility(true); + }); td_mode_row->Add(m_transmission_distance_calibration_mode_choice, 1, wxALIGN_CENTER_VERTICAL); td_box->Add(td_mode_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); for (size_t i = 0; i < component_ids.size(); ++i) { @@ -2711,6 +2719,36 @@ public: } filament_root->Add(td_box, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); + auto add_filament_calibration_row = [this, filament_page, filament_root, gap](const wxString &title, + wxStaticText **label_out, + std::function load_fn, + std::function clear_fn) { + auto *row = new wxBoxSizer(wxHORIZONTAL); + row->Add(new wxStaticText(filament_page, wxID_ANY, title), + 0, + wxALIGN_CENTER_VERTICAL | wxRIGHT, + gap); + auto *label = new wxStaticText(filament_page, wxID_ANY, wxEmptyString); + row->Add(label, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); + auto *load_button = new wxButton(filament_page, wxID_ANY, _L("Load...")); + auto *clear_button = new wxButton(filament_page, wxID_ANY, _L("Clear")); + row->Add(load_button, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, std::max(FromDIP(2), gap / 2)); + row->Add(clear_button, 0, wxALIGN_CENTER_VERTICAL); + load_button->Bind(wxEVT_BUTTON, [load_fn](wxCommandEvent &) { load_fn(); }); + clear_button->Bind(wxEVT_BUTTON, [clear_fn](wxCommandEvent &) { clear_fn(); }); + if (label_out != nullptr) + *label_out = label; + filament_root->Add(row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); + }; + add_filament_calibration_row(_L("Top Calibration"), + &m_filament_top_surface_color_calibration_label, + [this]() { load_top_surface_color_calibration(); }, + [this]() { clear_top_surface_color_calibration(); }); + add_filament_calibration_row(_L("Side Calibration"), + &m_side_surface_color_calibration_label, + [this]() { load_side_surface_color_calibration(); }, + [this]() { clear_side_surface_color_calibration(); }); + m_strength_offsets_toggle_button = new wxButton(filament_page, wxID_ANY, wxEmptyString); m_strength_offsets_toggle_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { m_strength_offsets_expanded = !m_strength_offsets_expanded; @@ -2763,9 +2801,9 @@ public: } strength_offsets_root->Add(minimum_offsets_box, 0, wxEXPAND | wxBOTTOM, gap); strength_offsets_root->Add(strengths_box, 0, wxEXPAND | wxBOTTOM, gap); - auto *reset_btn = new wxButton(m_strength_offsets_panel, wxID_ANY, _L("Reset strengths and offsets")); - reset_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { reset_strengths_and_offsets(); }); - strength_offsets_root->Add(reset_btn, 0, wxALIGN_RIGHT | wxBOTTOM, gap); + m_strength_offsets_reset_button = new wxButton(m_strength_offsets_panel, wxID_ANY, _L("Reset strengths and offsets")); + m_strength_offsets_reset_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { reset_strengths_and_offsets(); }); + strength_offsets_root->Add(m_strength_offsets_reset_button, 0, wxALIGN_RIGHT | wxBOTTOM, gap); filament_root->Add(m_strength_offsets_panel, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); update_strength_offsets_visibility(false); @@ -3324,6 +3362,7 @@ public: new wxChoice(m_top_surface_contoning_checkboxes_panel, wxID_ANY); rebuild_top_surface_contoning_color_prediction_choices(top_surface_contoning_color_prediction_mode); update_top_surface_color_calibration_label(); + update_side_surface_color_calibration_label(); contoning_td_correction_row->Add(m_top_surface_contoning_td_correction_choice, 1, wxALIGN_CENTER_VERTICAL); contoning_checkboxes_root->Add(contoning_td_correction_row, 0, wxEXPAND | wxTOP | wxBOTTOM, gap / 2); m_top_surface_contoning_variable_layer_height_compensation_checkbox = @@ -3809,7 +3848,7 @@ public: return m_transmission_distance_calibration_mode_choice ? std::clamp(m_transmission_distance_calibration_mode_choice->GetSelection(), int(TextureMappingZone::TDCalibrationNone), - int(TextureMappingZone::TDCalibrationNeighbor)) : + int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)) : TextureMappingZone::DefaultTransmissionDistanceCalibrationMode; } @@ -4156,6 +4195,14 @@ public: { return m_top_surface_color_calibration_json; } + std::string side_surface_color_calibration_name() const + { + return m_side_surface_color_calibration_name; + } + std::string side_surface_color_calibration_json() const + { + return m_side_surface_color_calibration_json; + } bool top_surface_contoning_td_effective_alpha_correction_selected() const { const int effective_mode = TextureMappingZone::effective_top_surface_contoning_color_prediction_mode( @@ -4305,6 +4352,11 @@ public: } private: + enum class CalibrationSurface { + Top, + Side + }; + static std::string prime_tower_color_mode_name(int selection) { switch (selection) { @@ -4482,7 +4534,9 @@ private: void update_top_surface_color_calibration_label() { - if (m_top_surface_color_calibration_label != nullptr) { + auto update_label = [this](wxStaticText *label_widget) { + if (label_widget == nullptr) + return; wxString label = _L("No calibration loaded"); wxString tooltip; if (!m_top_surface_color_calibration_json.empty()) { @@ -4497,9 +4551,11 @@ private: display = display.Left(19) + "..." + display.Right(20); label = display; } - m_top_surface_color_calibration_label->SetLabel(label); - m_top_surface_color_calibration_label->SetToolTip(tooltip); - } + label_widget->SetLabel(label); + label_widget->SetToolTip(tooltip); + }; + update_label(m_top_surface_color_calibration_label); + update_label(m_filament_top_surface_color_calibration_label); if (m_top_surface_color_calibration_warning_text != nullptr) { TextureMappingZone tmp; @@ -4520,6 +4576,59 @@ private: Layout(); } + void update_side_surface_color_calibration_label() + { + if (m_side_surface_color_calibration_label != nullptr) { + wxString label = _L("No calibration loaded"); + wxString tooltip; + if (!m_side_surface_color_calibration_json.empty()) { + std::string name = m_side_surface_color_calibration_name; + if (name.empty()) + name = texture_mapping_side_surface_color_calibration_display_name(m_side_surface_color_calibration_json); + if (name.empty()) + name = "Loaded calibration"; + tooltip = from_u8(name); + wxString display = from_u8(name); + if (display.length() > 42) + display = display.Left(19) + "..." + display.Right(20); + label = display; + } + m_side_surface_color_calibration_label->SetLabel(label); + m_side_surface_color_calibration_label->SetToolTip(tooltip); + } + Layout(); + } + + int preferred_top_surface_color_calibration_mode() const + { + const std::vector modes = + texture_mapping_top_surface_color_calibration_supported_modes(m_top_surface_color_calibration_json); + const int nearest_mode = int(TextureMappingZone::ContoningColorPredictionCalibratedNearestMeasuredSample); + if (std::find(modes.begin(), modes.end(), nearest_mode) != modes.end()) + return nearest_mode; + return texture_mapping_top_surface_color_calibration_first_supported_mode(m_top_surface_color_calibration_json); + } + + bool apply_transmission_distances_from_calibration_filaments( + const std::vector &filaments) + { + if (filaments.empty()) + return false; + const std::vector tds = + texture_mapping_calibration_component_transmission_distances(filaments, + top_surface_color_calibration_component_colors(), + component_transmission_distances_mm(), + m_filament_color_mode); + bool applied = false; + for (size_t idx = 0; idx < tds.size() && idx < m_transmission_distance_spins.size(); ++idx) { + if (tds[idx] > 0.f && m_transmission_distance_spins[idx] != nullptr) { + m_transmission_distance_spins[idx]->SetValue(double(tds[idx])); + applied = true; + } + } + return applied; + } + bool filament_color_lists_differ(const std::vector &lhs, const std::vector &rhs) const { @@ -4677,10 +4786,11 @@ private: return true; } - void load_top_surface_color_calibration() + void load_surface_color_calibration(CalibrationSurface target_surface) { + const bool target_top = target_surface == CalibrationSurface::Top; wxFileDialog dlg(this, - _L("Load top-surface color calibration"), + target_top ? _L("Load top-surface color calibration") : _L("Load side-surface color calibration"), wxEmptyString, wxEmptyString, _L("JSON files (*.json)|*.json"), @@ -4697,23 +4807,78 @@ private: std::ostringstream ss; ss << file.rdbuf(); const std::string json_text = ss.str(); - std::string error; - if (!texture_mapping_parse_top_surface_color_calibration(json_text, &error)) { + std::string top_error; + std::string side_error; + std::optional top_calibration = + texture_mapping_parse_top_surface_color_calibration(json_text, &top_error); + std::optional side_calibration = + texture_mapping_parse_side_surface_color_calibration(json_text, &side_error); + if ((target_top && !top_calibration) || (!target_top && !side_calibration)) { + const std::string &error = target_top ? top_error : side_error; MessageDialog msg(this, - error.empty() ? _L("The selected file is not a supported top-surface color calibration.") : from_u8(error), + error.empty() ? + (target_top ? + _L("The selected file is not a supported top-surface color calibration.") : + _L("The selected file is not a supported side-surface color calibration.")) : + from_u8(error), _L("Invalid calibration"), wxOK | wxICON_WARNING); msg.ShowModal(); return; } - m_top_surface_color_calibration_json = json_text; - m_top_surface_color_calibration_name = - boost::filesystem::path(into_u8(dlg.GetPath())).filename().string(); - const int mode = texture_mapping_top_surface_color_calibration_first_supported_mode(m_top_surface_color_calibration_json); - rebuild_top_surface_contoning_color_prediction_choices(mode); + const std::string filename = boost::filesystem::path(into_u8(dlg.GetPath())).filename().string(); + bool top_changed = false; + bool side_changed = false; + if (target_top) { + m_top_surface_color_calibration_json = json_text; + m_top_surface_color_calibration_name = filename; + rebuild_top_surface_contoning_color_prediction_choices(preferred_top_surface_color_calibration_mode()); + bool td_applied = apply_transmission_distances_from_calibration_filaments(top_calibration->filaments); + top_changed = true; + if (side_calibration && m_side_surface_color_calibration_json.empty()) { + m_side_surface_color_calibration_json = json_text; + m_side_surface_color_calibration_name = filename; + if (m_transmission_distance_calibration_mode_choice != nullptr) + m_transmission_distance_calibration_mode_choice->SetSelection( + int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)); + if (!td_applied) + td_applied = apply_transmission_distances_from_calibration_filaments(side_calibration->filaments); + side_changed = true; + } + } else { + m_side_surface_color_calibration_json = json_text; + m_side_surface_color_calibration_name = filename; + if (m_transmission_distance_calibration_mode_choice != nullptr) + m_transmission_distance_calibration_mode_choice->SetSelection( + int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)); + bool td_applied = apply_transmission_distances_from_calibration_filaments(side_calibration->filaments); + side_changed = true; + if (top_calibration && m_top_surface_color_calibration_json.empty()) { + m_top_surface_color_calibration_json = json_text; + m_top_surface_color_calibration_name = filename; + rebuild_top_surface_contoning_color_prediction_choices(preferred_top_surface_color_calibration_mode()); + if (!td_applied) + td_applied = apply_transmission_distances_from_calibration_filaments(top_calibration->filaments); + top_changed = true; + } + } update_top_surface_color_calibration_label(); - update_top_surface_image_options_visibility(true); + update_side_surface_color_calibration_label(); + if (top_changed) + update_top_surface_image_options_visibility(true); + if (side_changed) + update_strength_offsets_visibility(true); + } + + void load_top_surface_color_calibration() + { + load_surface_color_calibration(CalibrationSurface::Top); + } + + void load_side_surface_color_calibration() + { + load_surface_color_calibration(CalibrationSurface::Side); } void clear_top_surface_color_calibration() @@ -4725,6 +4890,19 @@ private: update_top_surface_image_options_visibility(true); } + void clear_side_surface_color_calibration() + { + m_side_surface_color_calibration_name.clear(); + m_side_surface_color_calibration_json.clear(); + if (m_transmission_distance_calibration_mode_choice != nullptr && + transmission_distance_calibration_mode() == int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample)) { + m_transmission_distance_calibration_mode_choice->SetSelection( + TextureMappingZone::DefaultTransmissionDistanceCalibrationMode); + } + update_side_surface_color_calibration_label(); + update_strength_offsets_visibility(true); + } + void reset_strengths_and_offsets() { for (wxSlider *slider : m_minimum_offset_sliders) @@ -4773,6 +4951,22 @@ private: m_strength_offsets_toggle_button->SetLabel(m_strength_offsets_expanded ? _L("Hide Filament Strengths and Offsets") : _L("Show Filament Strengths and Offsets")); + const bool manual_controls_enabled = + transmission_distance_calibration_mode() != int(TextureMappingZone::TDCalibrationCalibratedNearestMeasuredSample); + for (wxSlider *slider : m_minimum_offset_sliders) + if (slider) + slider->Enable(manual_controls_enabled); + for (wxSpinCtrl *spin : m_minimum_offset_spins) + if (spin) + spin->Enable(manual_controls_enabled); + for (wxSlider *slider : m_strength_sliders) + if (slider) + slider->Enable(manual_controls_enabled); + for (wxSpinCtrl *spin : m_strength_spins) + if (spin) + spin->Enable(manual_controls_enabled); + if (m_strength_offsets_reset_button != nullptr) + m_strength_offsets_reset_button->Enable(manual_controls_enabled); if (m_strength_offsets_panel != nullptr) m_strength_offsets_panel->Show(m_strength_offsets_expanded); if (!fit_dialog) @@ -5531,6 +5725,8 @@ private: wxCheckBox *m_top_surface_contoning_surface_scatter_checkbox {nullptr}; wxChoice *m_top_surface_contoning_td_correction_choice {nullptr}; std::vector m_top_surface_contoning_color_prediction_modes; + wxStaticText *m_filament_top_surface_color_calibration_label {nullptr}; + wxStaticText *m_side_surface_color_calibration_label {nullptr}; wxStaticText *m_top_surface_color_calibration_label {nullptr}; wxStaticText *m_top_surface_color_calibration_warning_text {nullptr}; wxButton *m_set_filament_colors_from_calibration_button {nullptr}; @@ -5560,11 +5756,14 @@ private: std::function&)> m_apply_filament_colors; std::string m_top_surface_color_calibration_name; std::string m_top_surface_color_calibration_json; + std::string m_side_surface_color_calibration_name; + std::string m_side_surface_color_calibration_json; std::vector m_minimum_offset_sliders; std::vector m_minimum_offset_spins; std::vector m_strength_sliders; std::vector m_strength_spins; std::vector m_transmission_distance_spins; + wxButton *m_strength_offsets_reset_button {nullptr}; bool m_strength_offsets_expanded {false}; }; @@ -10650,6 +10849,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.top_surface_contoning_variable_layer_height_compensation_enabled, updated.top_surface_color_calibration_name, updated.top_surface_color_calibration_json, + updated.side_surface_color_calibration_name, + updated.side_surface_color_calibration_json, apply_filament_colors_from_calibration, shell_usage, bundle->texture_mapping_zones, @@ -10762,6 +10963,10 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) dlg.top_surface_color_calibration_name(); updated.top_surface_color_calibration_json = dlg.top_surface_color_calibration_json(); + updated.side_surface_color_calibration_name = + dlg.side_surface_color_calibration_name(); + updated.side_surface_color_calibration_json = + dlg.side_surface_color_calibration_json(); if (updated.top_surface_image_printing_enabled && updated.top_surface_image_printing_method == int(TextureMappingZone::TopSurfaceImageContoning)) { updated.modulation_mode = int(TextureMappingZone::ModulationPerimeterPathV2);