diff --git a/resources/shaders/110/painted_texture_preview.fs b/resources/shaders/110/painted_texture_preview.fs index 0a6c4137864..05fba9b677c 100644 --- a/resources/shaders/110/painted_texture_preview.fs +++ b/resources/shaders/110/painted_texture_preview.fs @@ -3,6 +3,7 @@ const vec3 ZERO = vec3(0.0, 0.0, 0.0); const float UV_EDGE_EPSILON = 0.000001; const float INVALID_TEXTURE_CHECKER_SCALE = 0.2; +const float CONTONING_FLAT_SURFACE_NORMAL_Z = 0.999; struct PrintVolumeDetection { @@ -13,8 +14,11 @@ struct PrintVolumeDetection uniform vec4 uniform_color; uniform sampler2D uniform_texture; +uniform sampler2D contoning_flat_surface_texture; uniform float texture_preview_mix; uniform bool invalid_texture_mapping; +uniform bool contoning_flat_surface_texture_enabled; +uniform bool contoning_flat_surface_include_bottom; uniform bool color_match_preview_active; uniform vec3 color_match_target_oklab; uniform float color_match_tolerance_sq; @@ -78,6 +82,12 @@ void main() vec4 color = uniform_color; vec4 texture_color = texture2D(uniform_texture, texture_preview_coord(tex_coord)); + if (contoning_flat_surface_texture_enabled) { + float normal_z = normalize(world_normal).z; + if (normal_z >= CONTONING_FLAT_SURFACE_NORMAL_Z || + (contoning_flat_surface_include_bottom && normal_z <= -CONTONING_FLAT_SURFACE_NORMAL_Z)) + texture_color = texture2D(contoning_flat_surface_texture, texture_preview_coord(tex_coord)); + } float mix_factor = clamp(texture_preview_mix, 0.0, 1.0); if (color_match_preview_active) { float source_alpha = clamp(texture_color.a, 0.0, 1.0); diff --git a/resources/shaders/140/painted_texture_preview.fs b/resources/shaders/140/painted_texture_preview.fs index c51a5595945..b3378819c60 100644 --- a/resources/shaders/140/painted_texture_preview.fs +++ b/resources/shaders/140/painted_texture_preview.fs @@ -3,6 +3,7 @@ const vec3 ZERO = vec3(0.0, 0.0, 0.0); const float UV_EDGE_EPSILON = 0.000001; const float INVALID_TEXTURE_CHECKER_SCALE = 0.2; +const float CONTONING_FLAT_SURFACE_NORMAL_Z = 0.999; struct PrintVolumeDetection { @@ -13,8 +14,11 @@ struct PrintVolumeDetection uniform vec4 uniform_color; uniform sampler2D uniform_texture; +uniform sampler2D contoning_flat_surface_texture; uniform float texture_preview_mix; uniform bool invalid_texture_mapping; +uniform bool contoning_flat_surface_texture_enabled; +uniform bool contoning_flat_surface_include_bottom; uniform bool color_match_preview_active; uniform vec3 color_match_target_oklab; uniform float color_match_tolerance_sq; @@ -80,6 +84,12 @@ void main() vec4 color = uniform_color; vec4 texture_color = texture(uniform_texture, texture_preview_coord(tex_coord)); + if (contoning_flat_surface_texture_enabled) { + float normal_z = normalize(world_normal).z; + if (normal_z >= CONTONING_FLAT_SURFACE_NORMAL_Z || + (contoning_flat_surface_include_bottom && normal_z <= -CONTONING_FLAT_SURFACE_NORMAL_Z)) + texture_color = texture(contoning_flat_surface_texture, texture_preview_coord(tex_coord)); + } float mix_factor = clamp(texture_preview_mix, 0.0, 1.0); if (color_match_preview_active) { float source_alpha = clamp(texture_color.a, 0.0, 1.0); diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 1aa6e931296..4f165d270ed 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -3760,6 +3760,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool this->project_config.apply_only(config, s_project_options); if (const auto *color_opt = this->project_config.option("filament_colour", false); color_opt != nullptr) this->texture_mapping_zones.load_entries(this->project_config.opt_string("texture_mapping_definitions"), color_opt->values); + this->texture_mapping_global_settings.load(this->project_config.opt_string("texture_mapping_global_settings")); break; } diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index a5ea6c623e1..2c07dcfec19 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -262,6 +262,7 @@ static void remove_texture_mapping_preview_options(nlohmann::json &root) texture_it->erase("preview_opacity_pct"); texture_it->erase("simulate_preview_colors"); texture_it->erase("limit_preview_resolution"); + texture_it->erase("simulate_top_surface_lod"); } } @@ -286,16 +287,67 @@ static bool texture_mapping_definitions_equal_for_gcode(const ConfigOption *lhs, return lhs_json == rhs_json; } +static bool texture_mapping_global_settings_json_for_gcode(const ConfigOption *opt, nlohmann::json &out) +{ + out = nlohmann::json::object(); + if (opt == nullptr) + return true; + if (opt->type() != coString) + return false; + const std::string &value = static_cast(opt)->value; + if (value.empty()) + return true; + try { + out = nlohmann::json::parse(value); + } catch (const nlohmann::json::exception &) { + return false; + } + if (!out.is_object()) + return false; + out.erase("schema"); + auto preview_it = out.find("preview_options"); + if (preview_it != out.end()) { + if (preview_it->is_object()) { + preview_it->erase("preview_opacity_pct"); + preview_it->erase("simulate_preview_colors"); + preview_it->erase("limit_preview_resolution"); + preview_it->erase("simulate_top_surface_lod"); + if (preview_it->empty()) + out.erase(preview_it); + } + } + out.erase("preview_opacity_pct"); + out.erase("simulate_preview_colors"); + out.erase("limit_preview_resolution"); + out.erase("simulate_top_surface_lod"); + return true; +} + +static bool texture_mapping_global_settings_equal_for_gcode(const ConfigOption *lhs, const ConfigOption *rhs) +{ + if (config_options_equal(lhs, rhs)) + return true; + nlohmann::json lhs_json; + nlohmann::json rhs_json; + return texture_mapping_global_settings_json_for_gcode(lhs, lhs_json) && + texture_mapping_global_settings_json_for_gcode(rhs, rhs_json) && + lhs_json == rhs_json; +} + static void remove_texture_mapping_preview_only_diff(t_config_option_keys &diff, const ConfigBase ¤t_config, const DynamicPrintConfig &new_full_config) { auto it = std::find(diff.begin(), diff.end(), "texture_mapping_definitions"); - if (it == diff.end()) - return; - if (texture_mapping_definitions_equal_for_gcode(current_config.option("texture_mapping_definitions"), + if (it != diff.end() && + texture_mapping_definitions_equal_for_gcode(current_config.option("texture_mapping_definitions"), new_full_config.option("texture_mapping_definitions"))) diff.erase(it); + it = std::find(diff.begin(), diff.end(), "texture_mapping_global_settings"); + if (it != diff.end() && + texture_mapping_global_settings_equal_for_gcode(current_config.option("texture_mapping_global_settings"), + new_full_config.option("texture_mapping_global_settings"))) + diff.erase(it); } // Collect changes to print config, account for overrides of extruder retract values by filament presets. @@ -325,6 +377,8 @@ static t_config_option_keys print_config_diffs( } else if (*opt_new != *opt_old) { if (opt_key == "texture_mapping_definitions" && texture_mapping_definitions_equal_for_gcode(opt_old, opt_new)) continue; + if (opt_key == "texture_mapping_global_settings" && texture_mapping_global_settings_equal_for_gcode(opt_old, opt_new)) + continue; //BBS: add plate_index logic for wipe_tower_x/wipe_tower_y if (!opt_key.compare("wipe_tower_x") || !opt_key.compare("wipe_tower_y")) { const ConfigOptionFloats* option_new = dynamic_cast(opt_new); @@ -358,6 +412,8 @@ static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig &cu if (opt_old == nullptr || *opt_new != *opt_old) { if (opt_key == "texture_mapping_definitions" && texture_mapping_definitions_equal_for_gcode(opt_old, opt_new)) continue; + if (opt_key == "texture_mapping_global_settings" && texture_mapping_global_settings_equal_for_gcode(opt_old, opt_new)) + continue; //BBS: add plate_index logic for wipe_tower_x/wipe_tower_y if (opt_old && (!opt_key.compare("wipe_tower_x") || !opt_key.compare("wipe_tower_y"))) { const ConfigOptionFloats* option_new = dynamic_cast(opt_new); diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index 9d107a11ef4..f47d26c808c 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -916,7 +916,8 @@ static std::string normalized_prime_tower_color_mode_name(std::string name) std::string TextureMappingGlobalSettings::serialize() const { const std::string normalized_mode = normalize_color_mode_name(prime_tower_color_mode); - if (!enabled && + const bool prime_tower_defaults = + !enabled && std::abs((std::isfinite(angle_offset_deg) ? angle_offset_deg : 0.f)) <= 1e-6f && !preserve_aspect_ratio && normalized_mode == "auto" && @@ -928,26 +929,44 @@ std::string TextureMappingGlobalSettings::serialize() const image_file_back.empty() && image_name_back.empty() && image_width_back == 0 && - image_height_back == 0) + image_height_back == 0; + const bool preview_defaults = + std::abs((std::isfinite(preview_opacity_pct) ? preview_opacity_pct : TextureMappingZone::DefaultPreviewOpacityPct) - + TextureMappingZone::DefaultPreviewOpacityPct) <= 1e-6f && + preview_simulate_colors == TextureMappingZone::DefaultPreviewSimulateColors && + preview_limit_resolution == TextureMappingZone::DefaultPreviewLimitResolution && + preview_simulate_top_surface_lod == TextureMappingZone::DefaultPreviewSimulateTopSurfaceLod; + if (prime_tower_defaults && preview_defaults) return {}; nlohmann::json root; root["schema"] = 1; - nlohmann::json prime_tower_texture_mapping; - prime_tower_texture_mapping["enabled"] = enabled; - prime_tower_texture_mapping["angle_offset_deg"] = std::clamp(std::isfinite(angle_offset_deg) ? angle_offset_deg : 0.f, 0.f, 360.f); - prime_tower_texture_mapping["preserve_aspect_ratio"] = preserve_aspect_ratio; - prime_tower_texture_mapping["prime_tower_color_mode"] = normalized_mode; - prime_tower_texture_mapping["settings_zone_uid"] = prime_tower_settings_zone_uid; - prime_tower_texture_mapping["image_file"] = image_file; - prime_tower_texture_mapping["image_name"] = image_name; - prime_tower_texture_mapping["image_width"] = image_width; - prime_tower_texture_mapping["image_height"] = image_height; - prime_tower_texture_mapping["image_file_back"] = image_file_back; - prime_tower_texture_mapping["image_name_back"] = image_name_back; - prime_tower_texture_mapping["image_width_back"] = image_width_back; - prime_tower_texture_mapping["image_height_back"] = image_height_back; - root["prime_tower_texture_mapping"] = std::move(prime_tower_texture_mapping); + if (!preview_defaults || !prime_tower_defaults) { + nlohmann::json preview_options; + preview_options["preview_opacity_pct"] = + std::clamp(std::isfinite(preview_opacity_pct) ? preview_opacity_pct : TextureMappingZone::DefaultPreviewOpacityPct, 0.f, 100.f); + preview_options["simulate_preview_colors"] = preview_simulate_colors; + preview_options["limit_preview_resolution"] = preview_limit_resolution; + preview_options["simulate_top_surface_lod"] = preview_simulate_top_surface_lod; + root["preview_options"] = std::move(preview_options); + } + if (!prime_tower_defaults) { + nlohmann::json prime_tower_texture_mapping; + prime_tower_texture_mapping["enabled"] = enabled; + prime_tower_texture_mapping["angle_offset_deg"] = std::clamp(std::isfinite(angle_offset_deg) ? angle_offset_deg : 0.f, 0.f, 360.f); + prime_tower_texture_mapping["preserve_aspect_ratio"] = preserve_aspect_ratio; + prime_tower_texture_mapping["prime_tower_color_mode"] = normalized_mode; + prime_tower_texture_mapping["settings_zone_uid"] = prime_tower_settings_zone_uid; + prime_tower_texture_mapping["image_file"] = image_file; + prime_tower_texture_mapping["image_name"] = image_name; + prime_tower_texture_mapping["image_width"] = image_width; + prime_tower_texture_mapping["image_height"] = image_height; + prime_tower_texture_mapping["image_file_back"] = image_file_back; + prime_tower_texture_mapping["image_name_back"] = image_name_back; + prime_tower_texture_mapping["image_width_back"] = image_width_back; + prime_tower_texture_mapping["image_height_back"] = image_height_back; + root["prime_tower_texture_mapping"] = std::move(prime_tower_texture_mapping); + } return root.dump(); } @@ -967,6 +986,25 @@ void TextureMappingGlobalSettings::load(const std::string &serialized) if (!root.is_object()) return; + const auto preview_options_it = root.find("preview_options"); + if (preview_options_it != root.end() && preview_options_it->is_object()) { + const auto opacity_it = preview_options_it->find("preview_opacity_pct"); + if (opacity_it != preview_options_it->end() && opacity_it->is_number()) + preview_opacity_pct = std::clamp(opacity_it->get(), 0.f, 100.f); + const auto simulate_it = preview_options_it->find("simulate_preview_colors"); + if (simulate_it != preview_options_it->end() && simulate_it->is_boolean()) + preview_simulate_colors = simulate_it->get(); + const auto limit_it = preview_options_it->find("limit_preview_resolution"); + if (limit_it != preview_options_it->end() && limit_it->is_boolean()) + preview_limit_resolution = limit_it->get(); + const auto top_surface_lod_it = preview_options_it->find("simulate_top_surface_lod"); + if (top_surface_lod_it != preview_options_it->end() && top_surface_lod_it->is_boolean()) + preview_simulate_top_surface_lod = top_surface_lod_it->get(); + } + const auto root_simulate_it = root.find("simulate_preview_colors"); + if (root_simulate_it != root.end() && root_simulate_it->is_boolean()) + preview_simulate_colors = root_simulate_it->get(); + const auto prime_tower_texture_mapping_it = root.find("prime_tower_texture_mapping"); if (prime_tower_texture_mapping_it == root.end() || !prime_tower_texture_mapping_it->is_object()) return; @@ -1122,9 +1160,6 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const high_resolution_sampling == rhs.high_resolution_sampling && std::abs(tone_gamma - rhs.tone_gamma) <= eps && transmission_distance_calibration_mode == rhs.transmission_distance_calibration_mode && - std::abs(preview_opacity_pct - rhs.preview_opacity_pct) <= eps && - preview_simulate_colors == rhs.preview_simulate_colors && - preview_limit_resolution == rhs.preview_limit_resolution && auto_adjust_filament_selection == rhs.auto_adjust_filament_selection && floats_equal(filament_strengths_pct, rhs.filament_strengths_pct) && floats_equal(filament_minimum_offsets_pct, rhs.filament_minimum_offsets_pct) && @@ -1527,10 +1562,6 @@ std::string TextureMappingManager::serialize_entries() texture["tone_gamma"] = normalize_tone_gamma(zone.tone_gamma); texture["transmission_distance_calibration"] = transmission_distance_calibration_mode_name(zone.transmission_distance_calibration_mode); - texture["preview_opacity_pct"] = - std::clamp(finite_or(zone.preview_opacity_pct, TextureMappingZone::DefaultPreviewOpacityPct), 0.f, 100.f); - texture["simulate_preview_colors"] = zone.preview_simulate_colors; - texture["limit_preview_resolution"] = zone.preview_limit_resolution; texture["auto_adjust_filaments"] = zone.auto_adjust_filament_selection; texture["strength_pct"] = floats_to_json(normalized_strengths); texture["minimum_offset_pct"] = floats_to_json(normalized_min_offsets); @@ -1832,10 +1863,6 @@ 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); - zone.preview_opacity_pct = - std::clamp(texture.value("preview_opacity_pct", TextureMappingZone::DefaultPreviewOpacityPct), 0.f, 100.f); - zone.preview_simulate_colors = texture.value("simulate_preview_colors", TextureMappingZone::DefaultPreviewSimulateColors); - zone.preview_limit_resolution = texture.value("limit_preview_resolution", true); 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 8535af0cf12..5d5cf141799 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -219,6 +219,7 @@ struct TextureMappingZone static constexpr int DefaultTransmissionDistanceCalibrationMode = int(TDCalibrationAbsolute); static constexpr bool DefaultPreviewSimulateColors = false; static constexpr bool DefaultPreviewLimitResolution = true; + static constexpr bool DefaultPreviewSimulateTopSurfaceLod = true; static constexpr bool DefaultAutoAdjustFilamentSelection = true; static constexpr int default_modulation_mode_for_surface_pattern(int surface_pattern) @@ -329,9 +330,6 @@ struct TextureMappingZone bool high_resolution_sampling = DefaultHighResolutionSampling; float tone_gamma = DefaultToneGamma; int transmission_distance_calibration_mode = DefaultTransmissionDistanceCalibrationMode; - float preview_opacity_pct = DefaultPreviewOpacityPct; - bool preview_simulate_colors = DefaultPreviewSimulateColors; - bool preview_limit_resolution = DefaultPreviewLimitResolution; bool auto_adjust_filament_selection = DefaultAutoAdjustFilamentSelection; std::vector filament_strengths_pct; std::vector filament_minimum_offsets_pct; @@ -454,9 +452,6 @@ struct TextureMappingZone high_resolution_sampling = DefaultHighResolutionSampling; tone_gamma = DefaultToneGamma; transmission_distance_calibration_mode = DefaultTransmissionDistanceCalibrationMode; - preview_opacity_pct = DefaultPreviewOpacityPct; - preview_simulate_colors = DefaultPreviewSimulateColors; - preview_limit_resolution = DefaultPreviewLimitResolution; auto_adjust_filament_selection = DefaultAutoAdjustFilamentSelection; linear_gradient_mode = DefaultLinearGradientMode; linear_gradient_radius_mm = DefaultLinearGradientRadiusMm; @@ -519,6 +514,10 @@ struct TextureMappingGlobalSettings bool enabled = false; float angle_offset_deg = 0.f; bool preserve_aspect_ratio = false; + float preview_opacity_pct = TextureMappingZone::DefaultPreviewOpacityPct; + bool preview_simulate_colors = TextureMappingZone::DefaultPreviewSimulateColors; + bool preview_limit_resolution = TextureMappingZone::DefaultPreviewLimitResolution; + bool preview_simulate_top_surface_lod = TextureMappingZone::DefaultPreviewSimulateTopSurfaceLod; std::string prime_tower_color_mode = "auto"; uint64_t prime_tower_settings_zone_uid = 0; std::string image_file; diff --git a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp index a3f2989c542..c2ae409b74d 100644 --- a/src/slic3r/GUI/MMUPaintedTexturePreview.cpp +++ b/src/slic3r/GUI/MMUPaintedTexturePreview.cpp @@ -44,6 +44,8 @@ constexpr size_t k_temporary_simulated_texture_preview_max_pixels = 1024ull * 10 constexpr size_t k_temporary_simulated_texture_preview_min_source_pixels = k_temporary_simulated_texture_preview_max_pixels * 3 / 2; 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 double k_contoning_top_surface_preview_lod_max_samples = 350000.0; constexpr const char *TEXTURE_MAPPING_BACKGROUND_COLOR_CONFIG_KEY = "texture_mapping_background_color"; struct TexturePreviewMixCandidate @@ -86,6 +88,10 @@ struct TexturePreviewSimulationSettings int generic_solver_lookup_mode = int(TextureMappingZone::GenericSolverClosestMix); int generic_solver_mode = int(TextureMappingZone::GenericSolverV2); int generic_solver_mix_model = TextureMappingZone::DefaultGenericSolverMixModel; + bool contoning_flat_surface_quantization = false; + int contoning_flat_surface_pattern_filaments = TextureMappingZone::DefaultTopSurfaceContoningPatternFilaments; + bool simulate_top_surface_lod = false; + float top_surface_lod_pitch_mm = 0.f; float minimum_visibility_offset_factor = 0.f; std::vector component_ids; std::vector> component_colors; @@ -257,7 +263,7 @@ std::array unwrap_triangle_uvs(const Vec2f &uv0, const Vec2f &uv1, con return out; } -float estimated_texture_preview_mm_per_pixel(const ModelVolume &model_volume) +float estimated_texture_preview_mm_per_pixel(const ModelVolume &model_volume, const Transform3d *world_matrix = nullptr) { const unsigned int width = model_volume.imported_texture_width; const unsigned int height = model_volume.imported_texture_height; @@ -286,9 +292,15 @@ float estimated_texture_preview_mm_per_pixel(const ModelVolume &model_volume) size_t(indices[2]) >= its.vertices.size()) continue; - const Vec3d p0 = its.vertices[size_t(indices[0])].cast(); - const Vec3d p1 = its.vertices[size_t(indices[1])].cast(); - const Vec3d p2 = its.vertices[size_t(indices[2])].cast(); + const Vec3d p0 = world_matrix != nullptr ? + ((*world_matrix) * its.vertices[size_t(indices[0])].cast()) : + its.vertices[size_t(indices[0])].cast(); + const Vec3d p1 = world_matrix != nullptr ? + ((*world_matrix) * its.vertices[size_t(indices[1])].cast()) : + its.vertices[size_t(indices[1])].cast(); + const Vec3d p2 = world_matrix != nullptr ? + ((*world_matrix) * its.vertices[size_t(indices[2])].cast()) : + its.vertices[size_t(indices[2])].cast(); const double tri_area_mm2 = 0.5 * (p1 - p0).cross(p2 - p0).norm(); if (!std::isfinite(tri_area_mm2) || tri_area_mm2 <= double(k_epsilon)) continue; @@ -457,6 +469,57 @@ float texture_preview_config_float(const char *key, float fallback) return fallback; } +float texture_preview_config_nozzle_mm(const DynamicPrintConfig &config, const std::vector &component_ids) +{ + float nozzle = 0.4f; + if (const ConfigOptionFloats *opt = config.option("nozzle_diameter")) { + for (const unsigned int component_id : component_ids) { + const size_t idx = component_id > 0 ? size_t(component_id - 1) : size_t(0); + if (idx < opt->values.size() && std::isfinite(opt->values[idx]) && opt->values[idx] > 0.0) + nozzle = std::max(nozzle, float(opt->values[idx])); + } + if (!opt->values.empty() && std::isfinite(opt->values.front()) && opt->values.front() > 0.0) + nozzle = std::max(nozzle, float(opt->values.front())); + } + return nozzle; +} + +float texture_preview_config_line_width_mm(const DynamicPrintConfig &config, + const char *key, + float ratio_over, + float fallback) +{ + const ConfigOptionFloatOrPercent *opt = config.option(key); + if (opt == nullptr || !std::isfinite(opt->value) || std::abs(opt->value) <= k_epsilon) + return fallback; + + const float value = opt->percent ? ratio_over * float(opt->value) / 100.f : float(opt->value); + return std::isfinite(value) && value > 0.f ? value : fallback; +} + +float contoning_flat_surface_lod_pitch_for_texture_preview(const TextureMappingZone &zone, + const std::vector &component_ids) +{ + if (GUI::wxGetApp().preset_bundle == nullptr) + return 0.f; + + const DynamicPrintConfig config = GUI::wxGetApp().preset_bundle->full_config(); + const float nozzle = texture_preview_config_nozzle_mm(config, component_ids); + const float line_width = texture_preview_config_line_width_mm(config, "line_width", nozzle, nozzle); + const float external_width = texture_preview_config_line_width_mm(config, "outer_wall_line_width", nozzle, line_width); + const float configured = + std::clamp(zone.top_surface_contoning_min_feature_mm, + TextureMappingZone::MinTopSurfaceContoningMinFeatureMm, + TextureMappingZone::MaxTopSurfaceContoningMinFeatureMm); + const float min_feature = configured > 0.f ? + configured : + std::max({ 2.0f, 4.f * nozzle, 3.f * external_width }); + float pitch = std::clamp(external_width, + 0.25f, + std::max(0.25f, min_feature * 0.5f)); + return std::clamp(pitch, 0.25f, std::max(0.25f, min_feature)); +} + std::array raw_offset_visibility_factors_for_texture_preview(const TextureMappingZone &zone) { const float base_outer_width_mm = @@ -693,11 +756,34 @@ std::array simulated_texture_preview_size(unsigned int width, unsigned int max_edge = 0, size_t max_pixels = 0) { - if (max_edge > 0 && max_pixels > 0) - return limited_simulated_texture_preview_size(width, height, max_edge, max_pixels); - return settings.limit_texture_resolution ? + std::array size = max_edge > 0 && max_pixels > 0 ? + limited_simulated_texture_preview_size(width, height, max_edge, max_pixels) : + settings.limit_texture_resolution ? limited_simulated_texture_preview_size(width, height) : std::array{ width, height }; + + if (settings.contoning_flat_surface_quantization && + settings.simulate_top_surface_lod && + std::isfinite(settings.texture_preview_mm_per_pixel) && + std::isfinite(settings.top_surface_lod_pitch_mm) && + settings.texture_preview_mm_per_pixel > 0.f && + settings.top_surface_lod_pitch_mm > k_epsilon) { + const double scale = double(settings.texture_preview_mm_per_pixel) / double(settings.top_surface_lod_pitch_mm); + if (std::isfinite(scale) && scale > 0.0 && scale < 1.0) { + unsigned int lod_width = std::max(1u, unsigned(std::ceil(double(width) * scale))); + unsigned int lod_height = std::max(1u, unsigned(std::ceil(double(height) * scale))); + const double lod_pixels = double(lod_width) * double(lod_height); + if (lod_pixels > k_contoning_top_surface_preview_lod_max_samples) { + const double sample_cap_scale = std::sqrt(k_contoning_top_surface_preview_lod_max_samples / lod_pixels); + lod_width = std::max(1u, unsigned(std::floor(double(lod_width) * sample_cap_scale))); + lod_height = std::max(1u, unsigned(std::floor(double(lod_height) * sample_cap_scale))); + } + size[0] = std::min(size[0], lod_width); + size[1] = std::min(size[1], lod_height); + } + } + + return size; } bool simulated_texture_preview_needs_temporary_result(unsigned int width, @@ -1030,12 +1116,50 @@ bool is_gradient_zone(const TextureMappingZone &zone) return zone.enabled && !zone.deleted && zone.is_surface_gradient(); } +const TextureMappingGlobalSettings *texture_mapping_global_preview_settings() +{ + return GUI::wxGetApp().preset_bundle != nullptr ? + &GUI::wxGetApp().preset_bundle->texture_mapping_global_settings : + nullptr; +} + +float texture_preview_opacity_factor() +{ + const TextureMappingGlobalSettings *settings = texture_mapping_global_preview_settings(); + const float opacity_pct = settings != nullptr ? + settings->preview_opacity_pct : + TextureMappingZone::DefaultPreviewOpacityPct; + return std::clamp(opacity_pct, 0.f, 100.f) / 100.f; +} + +bool texture_preview_simulate_colors() +{ + const TextureMappingGlobalSettings *settings = texture_mapping_global_preview_settings(); + return settings != nullptr ? + settings->preview_simulate_colors : + TextureMappingZone::DefaultPreviewSimulateColors; +} + +bool texture_preview_limit_resolution() +{ + const TextureMappingGlobalSettings *settings = texture_mapping_global_preview_settings(); + return settings != nullptr ? + settings->preview_limit_resolution : + TextureMappingZone::DefaultPreviewLimitResolution; +} + +bool texture_preview_simulate_top_surface_lod() +{ + const TextureMappingGlobalSettings *settings = texture_mapping_global_preview_settings(); + return settings != nullptr && settings->preview_simulate_top_surface_lod; +} + float texture_preview_mix_for_filament(unsigned int filament_id, size_t num_physical, const TextureMappingManager *texture_mgr) { const TextureMappingZone *zone = zone_for_filament(filament_id, num_physical, texture_mgr); if (zone == nullptr || (!is_image_zone(*zone) && !is_gradient_zone(*zone))) return 0.f; - return std::clamp(zone->preview_opacity_pct, 0.f, 100.f) / 100.f; + return texture_preview_opacity_factor(); } bool texture_preview_settings_invalid_for_filament(unsigned int filament_id, size_t num_physical, const TextureMappingManager *texture_mgr) @@ -1524,6 +1648,81 @@ std::vector best_component_mix_weights_for_target(const std::vector(n - k + idx) / static_cast(idx); + if (count > static_cast(limit)) + return 0; + } + return size_t(std::max(1.0L, std::round(count))); +} + +std::vector build_contoning_flat_surface_mix_candidates( + const std::vector> &component_colors, + int total_units) +{ + const size_t candidate_count = + contoning_flat_surface_preview_candidate_count(component_colors.size(), + total_units, + k_contoning_flat_surface_preview_max_candidates); + if (component_colors.empty() || total_units <= 0 || candidate_count == 0) + return {}; + + const size_t component_count = component_colors.size(); + std::vector units(component_count, 0); + std::vector weights(component_count, 0.f); + std::vector candidates; + candidates.reserve(candidate_count); + + std::function recurse = [&](size_t idx, int remaining_units) { + if (idx + 1 == component_count) { + units[idx] = remaining_units; + for (size_t weight_idx = 0; weight_idx < component_count; ++weight_idx) + weights[weight_idx] = float(units[weight_idx]) / float(std::max(1, total_units)); + + TexturePreviewMixCandidate candidate; + candidate.rgb = mix_color_solver_components(component_colors, weights, ColorSolverMixModel::PigmentPainter); + candidate.perceptual = oklab_from_srgb(candidate.rgb); + candidates.emplace_back(std::move(candidate)); + return; + } + + for (int unit = 0; unit <= remaining_units; ++unit) { + units[idx] = unit; + recurse(idx + 1, remaining_units - unit); + } + }; + recurse(0, total_units); + return candidates; +} + +TexturePreviewMixNearestResult nearest_contoning_flat_surface_mix_candidate( + const std::vector &candidates, + const std::vector &nodes, + int root, + const std::array &target_rgb) +{ + TexturePreviewMixNearestResult result; + if (candidates.empty()) + return result; + + const std::array target_oklab = oklab_from_srgb(target_rgb); + const std::array axis_weights = { 1.f, 4.f, 4.f }; + if (root >= 0 && !nodes.empty()) + query_texture_preview_mix_candidate_perceptual_kd_tree(candidates, nodes, target_oklab, axis_weights, root, result); + if (result.best_idx >= candidates.size()) + result = nearest_texture_preview_mix_candidates_perceptual_linear(candidates, target_oklab, axis_weights); + return result; +} + float apply_texture_tone_gamma(float channel, float tone_gamma) { const float safe_channel = clamp01(channel); @@ -1654,8 +1853,8 @@ std::vector binary_dither_candidates_for_te return candidates; } -std::array texture_preview_target_oklab(const TexturePreviewSimulationSettings &settings, - const std::array &sample_rgba) +std::array texture_preview_target_rgb(const TexturePreviewSimulationSettings &settings, + const std::array &sample_rgba) { std::array target = { clamp01(sample_rgba[0]), @@ -1667,8 +1866,13 @@ std::array texture_preview_target_oklab(const TexturePreviewSimulation target[1] = apply_texture_tone_gamma(target[1], settings.tone_gamma); target[2] = apply_texture_tone_gamma(target[2], settings.tone_gamma); } - target = apply_texture_contrast_to_rgb(target, std::clamp(settings.contrast_pct, 25.f, 300.f) / 100.f); - return oklab_from_srgb(target); + return apply_texture_contrast_to_rgb(target, std::clamp(settings.contrast_pct, 25.f, 300.f) / 100.f); +} + +std::array texture_preview_target_oklab(const TexturePreviewSimulationSettings &settings, + const std::array &sample_rgba) +{ + return oklab_from_srgb(texture_preview_target_rgb(settings, sample_rgba)); } struct TexturePreviewBinaryDitherNearestResult { @@ -2118,6 +2322,96 @@ std::vector component_weights_for_texture_preview(const TexturePreviewSim return desired; } +std::array fallback_contoning_flat_surface_rgb_for_texture_preview( + const TexturePreviewSimulationSettings &settings, + const std::array &sample_rgba, + int total_units) +{ + const size_t component_count = settings.component_colors.size(); + if (component_count == 0 || total_units <= 0) + return { sample_rgba[0], sample_rgba[1], sample_rgba[2] }; + + TexturePreviewSimulationSettings local_settings = settings; + local_settings.dithering_enabled = false; + local_settings.compact_offset_mode = false; + local_settings.contoning_flat_surface_quantization = false; + std::vector continuous_weights = component_weights_for_texture_preview(local_settings, sample_rgba, false); + continuous_weights.resize(component_count, 0.f); + + float total_weight = 0.f; + for (float &weight : continuous_weights) { + weight = clamp01(weight); + total_weight += weight; + } + + if (total_weight <= k_epsilon) { + const std::array target_rgb = texture_preview_target_rgb(settings, sample_rgba); + const std::array target_oklab = oklab_from_srgb(target_rgb); + const std::array axis_weights = { 1.f, 4.f, 4.f }; + size_t best_idx = 0; + float best_error = std::numeric_limits::max(); + for (size_t idx = 0; idx < settings.component_colors.size(); ++idx) { + const std::array component_oklab = oklab_from_srgb(settings.component_colors[idx]); + const float dl = component_oklab[0] - target_oklab[0]; + const float da = component_oklab[1] - target_oklab[1]; + const float db = component_oklab[2] - target_oklab[2]; + const float error = axis_weights[0] * dl * dl + axis_weights[1] * da * da + axis_weights[2] * db * db; + if (error < best_error) { + best_error = error; + best_idx = idx; + } + } + std::vector one_hot(component_count, 0.f); + one_hot[best_idx] = 1.f; + return mix_color_solver_components(settings.component_colors, one_hot, ColorSolverMixModel::PigmentPainter); + } + + struct QuantizedWeight { + size_t idx { 0 }; + float remainder { 0.f }; + }; + + std::vector counts(component_count, 0); + std::vector remainders; + remainders.reserve(component_count); + int assigned = 0; + for (size_t idx = 0; idx < component_count; ++idx) { + const float exact = continuous_weights[idx] * float(total_units) / total_weight; + const int count = std::clamp(int(std::floor(exact)), 0, total_units); + counts[idx] = count; + assigned += count; + remainders.push_back({ idx, exact - float(count) }); + } + std::sort(remainders.begin(), remainders.end(), [](const QuantizedWeight &lhs, const QuantizedWeight &rhs) { + return lhs.remainder > rhs.remainder; + }); + for (int remaining = total_units - assigned, idx = 0; remaining > 0 && !remainders.empty(); --remaining, ++idx) + ++counts[remainders[size_t(idx) % remainders.size()].idx]; + + std::vector quantized_weights(component_count, 0.f); + for (size_t idx = 0; idx < component_count; ++idx) + quantized_weights[idx] = float(counts[idx]) / float(total_units); + return mix_color_solver_components(settings.component_colors, quantized_weights, ColorSolverMixModel::PigmentPainter); +} + +std::array contoning_flat_surface_rgb_for_texture_preview( + const TexturePreviewSimulationSettings &settings, + const std::array &sample_rgba, + 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 (!candidates.empty()) { + const TexturePreviewMixNearestResult nearest = + nearest_contoning_flat_surface_mix_candidate(candidates, nodes, root, target_rgb); + if (nearest.best_idx < candidates.size()) + return candidates[nearest.best_idx].rgb; + } + return fallback_contoning_flat_surface_rgb_for_texture_preview(settings, sample_rgba, total_units); +} + std::vector raw_offset_print_width_weights_for_texture_preview(const TexturePreviewSimulationSettings &settings, const std::vector &raw_weights) { @@ -2205,7 +2499,7 @@ std::optional texture_preview_simulation_setti const std::vector &physical_colors) { const TextureMappingZone *zone = zone_for_filament(filament_id, num_physical, texture_mgr); - if (zone == nullptr || !is_image_zone(*zone) || !zone->preview_simulate_colors) + if (zone == nullptr || !is_image_zone(*zone) || !texture_preview_simulate_colors()) return std::nullopt; TexturePreviewSimulationSettings settings; @@ -2216,7 +2510,7 @@ std::optional texture_preview_simulation_setti int(TextureMappingZone::FilamentColorAny), int(TextureMappingZone::FilamentColorRGBKW)); settings.force_sequential_filaments = zone->force_sequential_filaments; - settings.limit_texture_resolution = zone->preview_limit_resolution; + settings.limit_texture_resolution = texture_preview_limit_resolution(); settings.dithering_enabled = zone->dithering_enabled && settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues); @@ -2246,6 +2540,17 @@ std::optional texture_preview_simulation_setti int(TextureMappingZone::GenericSolverLegacy), int(TextureMappingZone::GenericSolverV2)); settings.generic_solver_mix_model = TextureMappingZone::DefaultGenericSolverMixModel; + if (zone->top_surface_contoning_active()) { + const int stack_layers = + std::clamp(zone->top_surface_contoning_stack_layers, + TextureMappingZone::MinTopSurfaceContoningStackLayers, + TextureMappingZone::MaxTopSurfaceContoningStackLayers); + const int pattern_filaments = + std::clamp(zone->top_surface_contoning_pattern_filaments, + TextureMappingZone::MinTopSurfaceContoningPatternFilaments, + TextureMappingZone::MaxTopSurfaceContoningPatternFilaments); + settings.contoning_flat_surface_pattern_filaments = std::max(1, std::min(stack_layers, pattern_filaments)); + } settings.minimum_visibility_offset_factor = zone->minimum_visibility_offset_enabled ? std::clamp((std::isfinite(zone->minimum_visibility_offset_pct) ? zone->minimum_visibility_offset_pct : @@ -2256,6 +2561,9 @@ std::optional texture_preview_simulation_setti settings.component_ids = TextureMappingManager::effective_texture_component_ids(*zone, num_physical, physical_colors); if (settings.component_ids.empty()) return std::nullopt; + settings.simulate_top_surface_lod = + texture_preview_simulate_top_surface_lod() && + zone->top_surface_contoning_active(); const bool raw_values_mode = settings.mapping_mode == int(TextureMappingZone::TextureMappingRawValues); settings.component_colors.reserve(settings.component_ids.size()); @@ -2318,6 +2626,13 @@ size_t texture_preview_simulation_signature(const ModelVolume &model_volume, mix(std::hash{}(settings.generic_solver_lookup_mode)); mix(std::hash{}(settings.generic_solver_mode)); mix(std::hash{}(settings.generic_solver_mix_model)); + mix(std::hash{}(settings.contoning_flat_surface_quantization ? 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)))); + } 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)))); @@ -2363,15 +2678,35 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig return result; prepare_texture_preview_simulation_settings(settings); + const int contoning_flat_surface_pattern_filaments = + std::clamp(settings.contoning_flat_surface_pattern_filaments, + TextureMappingZone::MinTopSurfaceContoningPatternFilaments, + TextureMappingZone::MaxTopSurfaceContoningPatternFilaments); + const bool use_contoning_flat_surface_quantization = + settings.contoning_flat_surface_quantization && + settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && + !settings.component_colors.empty() && + contoning_flat_surface_pattern_filaments > 0; + const std::vector contoning_flat_surface_candidates = + use_contoning_flat_surface_quantization ? + build_contoning_flat_surface_mix_candidates(settings.component_colors, contoning_flat_surface_pattern_filaments) : + std::vector{}; + std::vector contoning_flat_surface_nodes; + const int contoning_flat_surface_root = contoning_flat_surface_candidates.empty() ? + -1 : + build_generic_mix_candidate_kd_tree(contoning_flat_surface_candidates, contoning_flat_surface_nodes, true); const bool use_raw_offsets = + !use_contoning_flat_surface_quantization && source_raw_component_channels.size() == settings.component_colors.size() && source_raw_offsets.size() >= size_t(width) * size_t(height) * size_t(source_raw_channels); const bool use_binary_dithering = + !use_contoning_flat_surface_quantization && settings.dithering_enabled && settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && !is_halftone_dithering_method_for_texture_preview(settings.dithering_method) && !use_raw_offsets; const bool use_halftone_dithering = + !use_contoning_flat_surface_quantization && settings.dithering_enabled && settings.mapping_mode != int(TextureMappingZone::TextureMappingRawValues) && is_halftone_dithering_method_for_texture_preview(settings.dithering_method) && @@ -2404,15 +2739,57 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig return key; }; - auto component_coverage_at_source = [&](double src_x, double src_y, size_t component_idx) { + const double source_step_x = double(width) / double(std::max(1u, result.width)); + const double source_step_y = double(height) / double(std::max(1u, result.height)); + const bool use_contoning_flat_surface_lod_supersampling = + use_contoning_flat_surface_quantization && + settings.simulate_top_surface_lod && + (source_step_x > 1.05 || source_step_y > 1.05); + + auto sample_blended_source_color_at_source = [&](double sample_src_x, double sample_src_y) { const std::array source_rgba_sample = - sample_texture_preview_rgba_bilinear_at_source(source_rgba, width, height, src_x, src_y); - const ColorRGBA blended_source_color = - composite_texture_mapping_color_over_background_for_preview(ColorRGBA(float(source_rgba_sample[0]) / 255.f, - float(source_rgba_sample[1]) / 255.f, - float(source_rgba_sample[2]) / 255.f, - float(source_rgba_sample[3]) / 255.f), - background_color); + sample_texture_preview_rgba_bilinear_at_source(source_rgba, width, height, sample_src_x, sample_src_y); + return composite_texture_mapping_color_over_background_for_preview(ColorRGBA(float(source_rgba_sample[0]) / 255.f, + float(source_rgba_sample[1]) / 255.f, + float(source_rgba_sample[2]) / 255.f, + float(source_rgba_sample[3]) / 255.f), + background_color); + }; + + auto sample_blended_source_color_for_texel = [&](double src_x, double src_y) { + if (!use_contoning_flat_surface_lod_supersampling) + return sample_blended_source_color_at_source(src_x, src_y); + + const double radius_x = source_step_x * 0.35; + const double radius_y = source_step_y * 0.35; + const std::array, 9> offsets = { + std::array{ 0.0, 0.0 }, + std::array{ -radius_x, -radius_y }, + std::array{ 0.0, -radius_y }, + std::array{ radius_x, -radius_y }, + std::array{ -radius_x, 0.0 }, + std::array{ radius_x, 0.0 }, + std::array{ -radius_x, radius_y }, + std::array{ 0.0, radius_y }, + std::array{ radius_x, radius_y } + }; + float r = 0.f; + float g = 0.f; + float b = 0.f; + float a = 0.f; + for (const std::array &offset : offsets) { + const ColorRGBA color = sample_blended_source_color_at_source(src_x + offset[0], src_y + offset[1]); + r += color.r(); + g += color.g(); + b += color.b(); + a += color.a(); + } + const float inv_count = 1.f / float(offsets.size()); + return ColorRGBA(r * inv_count, g * inv_count, b * inv_count, a * inv_count); + }; + + auto component_coverage_at_source = [&](double src_x, double src_y, size_t component_idx) { + const ColorRGBA blended_source_color = sample_blended_source_color_at_source(src_x, src_y); const std::array sampled_rgba = { blended_source_color.r(), blended_source_color.g(), @@ -2481,14 +2858,7 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig for (unsigned int x = 0; x < result.width; ++x) { const double src_x = (double(x) + 0.5) * double(width) / double(std::max(1u, result.width)) - 0.5; const double src_y = (double(y) + 0.5) * double(height) / double(std::max(1u, result.height)) - 0.5; - const std::array source_rgba_sample = - sample_texture_preview_rgba_bilinear_at_source(source_rgba, width, height, src_x, src_y); - const ColorRGBA blended_source_color = - composite_texture_mapping_color_over_background_for_preview(ColorRGBA(float(source_rgba_sample[0]) / 255.f, - float(source_rgba_sample[1]) / 255.f, - float(source_rgba_sample[2]) / 255.f, - float(source_rgba_sample[3]) / 255.f), - background_color); + const ColorRGBA blended_source_color = sample_blended_source_color_for_texel(src_x, src_y); const std::array source_rgb = { to_u8(blended_source_color.r()), to_u8(blended_source_color.g()), @@ -2517,6 +2887,7 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig 1.f }; std::vector component_weights; + std::optional> forced_simulated_rgb; if (use_raw_offsets) { const std::vector raw_sample = sample_texture_preview_raw_offsets_bilinear(source_raw_offsets, @@ -2531,7 +2902,15 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig if (component_weights.size() == settings.component_colors.size()) component_weights = raw_offset_print_width_weights_for_texture_preview(settings, component_weights); } else { - if (use_halftone_dithering) { + if (use_contoning_flat_surface_quantization) { + forced_simulated_rgb = + contoning_flat_surface_rgb_for_texture_preview(settings, + sample_rgba, + contoning_flat_surface_candidates, + contoning_flat_surface_nodes, + contoning_flat_surface_root, + contoning_flat_surface_pattern_filaments); + } else if (use_halftone_dithering) { const std::vector continuous_coverage = component_weights_for_texture_preview(settings, sample_rgba, false); uint32_t mask = 0; @@ -2613,13 +2992,15 @@ TexturePreviewSimulationResult build_simulated_texture_preview_result(size_t sig activity = 1.f; } - const std::array simulated_rgb = activity > k_epsilon ? + const std::array simulated_rgb = forced_simulated_rgb.has_value() ? + *forced_simulated_rgb : + (activity > k_epsilon ? mix_color_solver_components(settings.component_colors, component_weights, use_halftone_dithering ? ColorSolverMixModel::PigmentPainter : color_solver_mix_model_from_index(settings.generic_solver_mix_model)) : - std::array{ sample_rgba[0], sample_rgba[1], sample_rgba[2] }; + std::array{ sample_rgba[0], sample_rgba[1], sample_rgba[2] }); const std::array out_rgba = { to_u8(simulated_rgb[0]), @@ -2908,10 +3289,16 @@ bool texture_preview_simulation_has_temporary_pending_impl() return false; } -size_t texture_preview_simulation_cache_key(const ModelVolume &model_volume, unsigned int filament_id) +size_t texture_preview_simulation_cache_key(const ModelVolume &model_volume, + unsigned int filament_id, + unsigned int variant = 0, + float texture_preview_mm_per_pixel = 0.f) { size_t key = reinterpret_cast(&model_volume); key ^= std::hash{}(filament_id) + 0x9e3779b97f4a7c15ull + (key << 6) + (key >> 2); + key ^= std::hash{}(variant) + 0x9e3779b97f4a7c15ull + (key << 6) + (key >> 2); + if (std::isfinite(texture_preview_mm_per_pixel) && texture_preview_mm_per_pixel > 0.f) + key ^= std::hash{}(int(std::lround(texture_preview_mm_per_pixel * 100000.f))) + 0x9e3779b97f4a7c15ull + (key << 6) + (key >> 2); return key; } @@ -2957,23 +3344,40 @@ void consume_temporary_simulated_texture_preview_result(TexturePreviewSimulation } const GUI::GLTexture *simulated_texture_preview_texture_for_filament(const ModelVolume &model_volume, + const Transform3d &world_matrix, unsigned int filament_id, size_t num_physical, const TextureMappingManager *texture_mgr, size_t source_texture_signature, - const GUI::GLTexture &fallback_texture) + const GUI::GLTexture &fallback_texture, + bool contoning_flat_surface_quantization = false, + bool *simulated_ready = nullptr) { + if (simulated_ready != nullptr) + *simulated_ready = false; + const TextureMappingZone *zone = zone_for_filament(filament_id, num_physical, texture_mgr); + if (contoning_flat_surface_quantization && + (zone == nullptr || !is_image_zone(*zone) || !texture_preview_simulate_colors() || !zone->top_surface_contoning_active())) + return nullptr; + const std::vector physical_colors = physical_filament_colors_for_texture_preview(num_physical); std::optional settings = texture_preview_simulation_settings_for_filament(filament_id, num_physical, texture_mgr, physical_colors); if (!settings.has_value()) - return &fallback_texture; - settings->texture_preview_mm_per_pixel = estimated_texture_preview_mm_per_pixel(model_volume); + return contoning_flat_surface_quantization ? nullptr : &fallback_texture; + settings->contoning_flat_surface_quantization = contoning_flat_surface_quantization; + settings->texture_preview_mm_per_pixel = estimated_texture_preview_mm_per_pixel(model_volume, &world_matrix); + if (settings->contoning_flat_surface_quantization && settings->simulate_top_surface_lod) + settings->top_surface_lod_pitch_mm = + contoning_flat_surface_lod_pitch_for_texture_preview(*zone, settings->component_ids); const ColorRGBA background_color = texture_mapping_background_color_for_preview(model_volume); const size_t simulation_signature = texture_preview_simulation_signature(model_volume, source_texture_signature, *settings); auto &cache = texture_preview_simulation_cache(); - const size_t cache_key = texture_preview_simulation_cache_key(model_volume, filament_id); + const size_t cache_key = texture_preview_simulation_cache_key(model_volume, + filament_id, + contoning_flat_surface_quantization ? 1u : 0u, + settings->texture_preview_mm_per_pixel); std::shared_ptr &entry_ref = cache[cache_key]; if (entry_ref == nullptr) entry_ref = std::make_shared(); @@ -2997,8 +3401,11 @@ const GUI::GLTexture *simulated_texture_preview_texture_for_filament(const Model entry.pending_job_state.reset(); } - if (entry.texture != nullptr && entry.uploaded_signature == simulation_signature && entry.texture->get_id() != 0) + if (entry.texture != nullptr && entry.uploaded_signature == simulation_signature && entry.texture->get_id() != 0) { + if (simulated_ready != nullptr) + *simulated_ready = true; return entry.texture.get(); + } if (!entry.pending_future.valid()) { entry.pending_signature = simulation_signature; @@ -3064,10 +3471,13 @@ const GUI::GLTexture *simulated_texture_preview_texture_for_filament(const Model if (temporary_signature != 0 && entry.texture != nullptr && entry.uploaded_signature == temporary_signature && - entry.texture->get_id() != 0) + entry.texture->get_id() != 0) { + if (simulated_ready != nullptr) + *simulated_ready = true; return entry.texture.get(); + } - return &fallback_texture; + return contoning_flat_surface_quantization ? nullptr : &fallback_texture; } } // namespace @@ -4405,7 +4815,7 @@ std::optional surface_gradient_preview_settings_ std::clamp(zone.offset_fade_mode, int(TextureMappingZone::OffsetFadeNone), int(TextureMappingZone::OffsetFadeOutInReversed)); - settings.limit_texture_resolution = zone.preview_limit_resolution; + settings.limit_texture_resolution = texture_preview_limit_resolution(); const float base_outer_width_mm = std::max(0.05f, surface_gradient_preview_config_float("texture_mapping_outer_wall_gradient_max_line_width", 0.95f)); const float min_outer_width_mm = std::clamp(surface_gradient_preview_config_float("texture_mapping_outer_wall_gradient_min_line_width", 0.32f), 0.05f, @@ -5033,6 +5443,10 @@ static size_t texture_preview_settings_signature_impl(size_t num_physical, signature_mix_float(texture_preview_config_float("texture_mapping_outer_wall_gradient_global_strength", 100.f), 100.f); signature_mix_float(texture_preview_config_float("texture_mapping_outer_wall_gradient_max_line_width", 0.95f), 1000.f); signature_mix_float(texture_preview_config_float("texture_mapping_outer_wall_gradient_min_line_width", 0.32f), 1000.f); + signature_mix_float(texture_preview_opacity_factor(), 10000.f); + signature_mix(std::hash{}(texture_preview_simulate_colors() ? 1 : 0)); + signature_mix(std::hash{}(texture_preview_limit_resolution() ? 1 : 0)); + signature_mix(std::hash{}(texture_preview_simulate_top_surface_lod() ? 1 : 0)); if (texture_mgr == nullptr) return signature; @@ -5066,6 +5480,11 @@ static size_t texture_preview_settings_signature_impl(size_t num_physical, signature_mix(std::hash{}(zone.texture_mapping_mode)); signature_mix(std::hash{}(zone.filament_color_mode)); signature_mix(std::hash{}(zone.force_sequential_filaments ? 1 : 0)); + signature_mix(std::hash{}(zone.top_surface_image_printing_enabled ? 1 : 0)); + signature_mix(std::hash{}(zone.top_surface_image_printing_method)); + 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.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)); @@ -5080,9 +5499,6 @@ static size_t texture_preview_settings_signature_impl(size_t num_physical, signature_mix(std::hash{}(zone.generic_solver_lookup_mode)); signature_mix(std::hash{}(zone.generic_solver_mode)); signature_mix(std::hash{}(TextureMappingZone::DefaultGenericSolverMixModel)); - signature_mix(std::hash{}(zone.preview_simulate_colors ? 1 : 0)); - signature_mix(std::hash{}(zone.preview_limit_resolution ? 1 : 0)); - signature_mix_float(zone.preview_opacity_pct, 100.f); signature_mix_float(zone.contrast_pct, 100.f); signature_mix_float(zone.tone_gamma); for (const float strength_pct : zone.filament_strengths_pct) @@ -5123,7 +5539,7 @@ static bool active_texture_preview_zone_ids_contains(const std::vectortop_surface_contoning_color_lower_surfaces; + return true; +} + static bool texture_preview_gradient_zone_uses_model(const TextureMappingZone &zone, size_t num_physical) { if (!zone.enabled || zone.deleted || !zone.is_surface_gradient()) @@ -5176,6 +5615,11 @@ static void texture_preview_mix_zone_baked_model_settings(size_t &signature, signature_mix(std::hash{}(zone.texture_mapping_mode)); signature_mix(std::hash{}(zone.filament_color_mode)); signature_mix(std::hash{}(zone.force_sequential_filaments ? 1 : 0)); + signature_mix(std::hash{}(zone.top_surface_image_printing_enabled ? 1 : 0)); + signature_mix(std::hash{}(zone.top_surface_image_printing_method)); + 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.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)); @@ -5190,8 +5634,6 @@ static void texture_preview_mix_zone_baked_model_settings(size_t &signature, signature_mix(std::hash{}(zone.generic_solver_lookup_mode)); signature_mix(std::hash{}(zone.generic_solver_mode)); signature_mix(std::hash{}(TextureMappingZone::DefaultGenericSolverMixModel)); - signature_mix(std::hash{}(zone.preview_simulate_colors ? 1 : 0)); - signature_mix(std::hash{}(zone.preview_limit_resolution ? 1 : 0)); signature_mix_float(zone.contrast_pct, 100.f); signature_mix_float(zone.tone_gamma); signature_mix(std::hash{}(zone.linear_gradient_mode)); @@ -5286,7 +5728,7 @@ size_t texture_preview_model_settings_signature(size_t num_physical, const bool halftone_model = texture_preview_zone_uses_halftone_model(zone); const bool simulated_vertex_color_model = image_zone && - zone.preview_simulate_colors && + texture_preview_simulate_colors() && (has_texture_mapping_color_preview_data || (!has_texture_preview_data && has_vertex_color_preview_data)); signature_mix(std::hash{}(halftone_model ? 1 : 0)); signature_mix(std::hash{}(simulated_vertex_color_model ? 1 : 0)); @@ -5339,9 +5781,13 @@ void render_model_texture_preview_models( set_color_match_preview_uniforms(*shader, color_match); glsafe(::glActiveTexture(GL_TEXTURE0)); shader->set_uniform("uniform_texture", 0); + shader->set_uniform("contoning_flat_surface_texture", 1); + shader->set_uniform("contoning_flat_surface_texture_enabled", false); + shader->set_uniform("contoning_flat_surface_include_bottom", false); const size_t texture_signature = model_volume_texture_preview_signature(model_volume); GLuint bound_texture_id = 0; + GLuint bound_contoning_flat_texture_id = 0; const bool color_match_active = color_match != nullptr && color_match->active; for (size_t idx = 0; idx < models.size(); ++idx) { if (color_match_active && !color_match_preview_allows_filament(color_match, filament_ids[idx])) @@ -5361,6 +5807,7 @@ void render_model_texture_preview_models( const GUI::GLTexture *preview_texture = force_original_texture ? &texture : simulated_texture_preview_texture_for_filament(model_volume, + model_matrix, filament_ids[idx], num_physical, texture_mgr, @@ -5370,16 +5817,50 @@ void render_model_texture_preview_models( continue; if (preview_texture->get_id() != bound_texture_id) { + glsafe(::glActiveTexture(GL_TEXTURE0)); glsafe(::glBindTexture(GL_TEXTURE_2D, preview_texture->get_id())); bound_texture_id = preview_texture->get_id(); } + bool include_contoning_bottom = false; + bool contoning_flat_ready = false; + const bool use_contoning_flat_texture = + !color_match_active && + !force_original_texture && + texture_preview_contoning_flat_surface_texture_for_filament(filament_ids[idx], + num_physical, + texture_mgr, + include_contoning_bottom); + const GUI::GLTexture *contoning_flat_texture = use_contoning_flat_texture ? + simulated_texture_preview_texture_for_filament(model_volume, + model_matrix, + filament_ids[idx], + num_physical, + texture_mgr, + texture_signature, + texture, + true, + &contoning_flat_ready) : + nullptr; + const bool contoning_flat_enabled = + contoning_flat_ready && contoning_flat_texture != nullptr && contoning_flat_texture->get_id() != 0; + if (contoning_flat_enabled && contoning_flat_texture->get_id() != bound_contoning_flat_texture_id) { + glsafe(::glActiveTexture(GL_TEXTURE1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, contoning_flat_texture->get_id())); + bound_contoning_flat_texture_id = contoning_flat_texture->get_id(); + glsafe(::glActiveTexture(GL_TEXTURE0)); + } + shader->set_uniform("contoning_flat_surface_texture_enabled", contoning_flat_enabled); + shader->set_uniform("contoning_flat_surface_include_bottom", contoning_flat_enabled && include_contoning_bottom); shader->set_uniform("texture_preview_mix", color_match_active ? 1.f : mix); shader->set_uniform("invalid_texture_mapping", invalid); models[idx].set_color(colors[idx]); models[idx].render(); } + glsafe(::glActiveTexture(GL_TEXTURE1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + glsafe(::glActiveTexture(GL_TEXTURE0)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); shader->stop_using(); restore_render_state(render_state); @@ -5426,6 +5907,7 @@ void render_model_texture_preview_model( const GUI::GLTexture *preview_texture = force_original_texture ? &texture : simulated_texture_preview_texture_for_filament(model_volume, + model_matrix, filament_id, num_physical, texture_mgr, @@ -5450,6 +5932,36 @@ void render_model_texture_preview_model( glsafe(::glActiveTexture(GL_TEXTURE0)); glsafe(::glBindTexture(GL_TEXTURE_2D, preview_texture->get_id())); shader->set_uniform("uniform_texture", 0); + shader->set_uniform("contoning_flat_surface_texture", 1); + bool include_contoning_bottom = false; + bool contoning_flat_ready = false; + const bool use_contoning_flat_texture = + !color_match_active && + !force_original_texture && + texture_preview_contoning_flat_surface_texture_for_filament(filament_id, + num_physical, + texture_mgr, + include_contoning_bottom); + const GUI::GLTexture *contoning_flat_texture = use_contoning_flat_texture ? + simulated_texture_preview_texture_for_filament(model_volume, + model_matrix, + filament_id, + num_physical, + texture_mgr, + texture_signature, + texture, + true, + &contoning_flat_ready) : + nullptr; + const bool contoning_flat_enabled = + contoning_flat_ready && contoning_flat_texture != nullptr && contoning_flat_texture->get_id() != 0; + if (contoning_flat_enabled) { + glsafe(::glActiveTexture(GL_TEXTURE1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, contoning_flat_texture->get_id())); + glsafe(::glActiveTexture(GL_TEXTURE0)); + } + shader->set_uniform("contoning_flat_surface_texture_enabled", contoning_flat_enabled); + shader->set_uniform("contoning_flat_surface_include_bottom", contoning_flat_enabled && include_contoning_bottom); shader->set_uniform("texture_preview_mix", color_match_active ? 1.f : mix); shader->set_uniform("invalid_texture_mapping", invalid); model.set_color(color); @@ -5458,6 +5970,9 @@ void render_model_texture_preview_model( else model.render(render_range); + glsafe(::glActiveTexture(GL_TEXTURE1)); + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); + glsafe(::glActiveTexture(GL_TEXTURE0)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); shader->stop_using(); restore_render_state(render_state); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ecfb58e4a41..8da77392512 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -621,7 +621,9 @@ static bool assign_imported_texture_mapping_zone(Model &model) return false; project_config.option("texture_mapping_definitions", true); + project_config.option("texture_mapping_global_settings", true); bundle->texture_mapping_zones.load_entries(project_config.opt_string("texture_mapping_definitions"), color_opt->values); + bundle->texture_mapping_global_settings.load(project_config.opt_string("texture_mapping_global_settings")); const unsigned int zone_id = bundle->texture_mapping_zones.ensure_image_texture_zone(color_opt->values.size(), color_opt->values); if (zone_id == 0) return false; @@ -636,6 +638,15 @@ static bool assign_imported_texture_mapping_zone(Model &model) opt->value = serialized; else print_config.set_key_value("texture_mapping_definitions", new ConfigOptionString(serialized)); + const std::string global_serialized = bundle->texture_mapping_global_settings.serialize(); + if (ConfigOptionString *opt = project_config.option("texture_mapping_global_settings")) + opt->value = global_serialized; + else + project_config.set_key_value("texture_mapping_global_settings", new ConfigOptionString(global_serialized)); + if (ConfigOptionString *opt = print_config.option("texture_mapping_global_settings")) + opt->value = global_serialized; + else + print_config.set_key_value("texture_mapping_global_settings", new ConfigOptionString(global_serialized)); for (ModelObject *object : model.objects) { if (!model_object_has_imported_texture_mapping_data(object)) @@ -760,6 +771,7 @@ static void load_texture_mapping_definitions(PresetBundle &bundle, const std::st set_texture_mapping_definitions(bundle, serialized); const ConfigOptionStrings *color_opt = bundle.project_config.option("filament_colour", false); bundle.texture_mapping_zones.load_entries(serialized, color_opt != nullptr ? color_opt->values : std::vector()); + set_texture_mapping_definitions(bundle, bundle.texture_mapping_zones.serialize_entries()); } static void sync_model_texture_mapping_definitions(Model &model, const std::string &serialized) @@ -1867,10 +1879,8 @@ public: const std::vector &component_transmission_distances_mm, int transmission_distance_calibration_mode, float tone_gamma, - float preview_opacity_pct, bool force_sequential_filaments, bool auto_adjust_filament_selection, - bool preview_limit_resolution, bool reduce_outer_surface_texture, bool seam_hiding, bool nonlinear_offset_adjustment, @@ -2125,13 +2135,28 @@ public: m_force_sequential_filaments_checkbox->SetValue(force_sequential_filaments); filament_root->Add(m_force_sequential_filaments_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); + filament_root->AddStretchSpacer(1); + auto *filament_calibration_note = + new wxStaticText(filament_page, + wxID_ANY, + _L("NOTE: Filament/TD calibration is not currently used in top-surface coloring"), + wxDefaultPosition, + wxSize(FromDIP(390), -1)); + filament_calibration_note->Wrap(FromDIP(390)); + filament_root->Add(filament_calibration_note, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); + filament_page->Bind(wxEVT_SIZE, [this, filament_page, filament_calibration_note, gap](wxSizeEvent &evt) { + const int wrap_width = std::max(FromDIP(120), filament_page->GetClientSize().GetWidth() - gap * 2); + filament_calibration_note->Wrap(wrap_width); + evt.Skip(); + }); + auto *preview_box = new wxStaticBoxSizer(wxVERTICAL, preview_page, _L("3D Preview")); auto *preview_opacity_row = new wxBoxSizer(wxHORIZONTAL); preview_opacity_row->Add(new wxStaticText(preview_page, wxID_ANY, _L("Texture opacity")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); - const int opacity = std::clamp(int(std::lround(preview_opacity_pct)), 0, 100); + const int opacity = std::clamp(int(std::lround(m_global_settings.preview_opacity_pct)), 0, 100); m_preview_opacity_slider = new wxSlider(preview_page, wxID_ANY, opacity, 0, 100, wxDefaultPosition, wxSize(FromDIP(180), -1), wxSL_HORIZONTAL | wxSL_AUTOTICKS); m_preview_opacity_spin = new wxSpinCtrl(preview_page, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(70), -1), wxSP_ARROW_KEYS | wxALIGN_RIGHT, 0, 100, opacity); @@ -2151,8 +2176,11 @@ public: m_auto_adjust_filament_selection_checkbox->SetValue(auto_adjust_filament_selection); preview_box->Add(m_auto_adjust_filament_selection_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); m_preview_limit_resolution_checkbox = new wxCheckBox(preview_page, wxID_ANY, _L("Limit color simulation texture resolution")); - m_preview_limit_resolution_checkbox->SetValue(preview_limit_resolution); + m_preview_limit_resolution_checkbox->SetValue(m_global_settings.preview_limit_resolution); preview_box->Add(m_preview_limit_resolution_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); + m_preview_top_surface_lod_checkbox = new wxCheckBox(preview_page, wxID_ANY, _L("Simulate level of detail for top-surface coloring")); + m_preview_top_surface_lod_checkbox->SetValue(m_global_settings.preview_simulate_top_surface_lod); + preview_box->Add(m_preview_top_surface_lod_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); auto *minimum_visibility_offset_row = new wxBoxSizer(wxHORIZONTAL); m_minimum_visibility_offset_checkbox = new wxCheckBox(preview_page, wxID_ANY, _L("Minimum visibility offset")); m_minimum_visibility_offset_checkbox->SetValue(minimum_visibility_offset_enabled); @@ -2872,6 +2900,7 @@ public: bool force_sequential_filaments() const { return m_force_sequential_filaments_checkbox && m_force_sequential_filaments_checkbox->GetValue(); } bool auto_adjust_filament_selection() const { return m_auto_adjust_filament_selection_checkbox == nullptr || m_auto_adjust_filament_selection_checkbox->GetValue(); } bool preview_limit_resolution() const { return m_preview_limit_resolution_checkbox == nullptr || m_preview_limit_resolution_checkbox->GetValue(); } + bool preview_simulate_top_surface_lod() const { return m_preview_top_surface_lod_checkbox != nullptr && m_preview_top_surface_lod_checkbox->GetValue(); } bool reduce_outer_surface_texture() const { return false; } bool seam_hiding() const { return m_seam_hiding_checkbox && m_seam_hiding_checkbox->GetValue(); } bool nonlinear_offset_adjustment() const { return m_nonlinear_offset_adjustment_checkbox && m_nonlinear_offset_adjustment_checkbox->GetValue(); } @@ -3131,6 +3160,9 @@ public: TextureMappingGlobalSettings global_settings() const { TextureMappingGlobalSettings settings = m_global_settings; + settings.preview_opacity_pct = preview_opacity_pct(); + settings.preview_limit_resolution = preview_limit_resolution(); + settings.preview_simulate_top_surface_lod = preview_simulate_top_surface_lod(); settings.enabled = m_prime_tower_mapping_enabled_checkbox != nullptr && m_prime_tower_mapping_enabled_checkbox->GetValue(); settings.preserve_aspect_ratio = m_prime_tower_preserve_aspect_ratio_checkbox != nullptr && m_prime_tower_preserve_aspect_ratio_checkbox->GetValue(); @@ -3612,6 +3644,7 @@ private: wxCheckBox *m_force_sequential_filaments_checkbox {nullptr}; wxCheckBox *m_auto_adjust_filament_selection_checkbox {nullptr}; wxCheckBox *m_preview_limit_resolution_checkbox {nullptr}; + wxCheckBox *m_preview_top_surface_lod_checkbox {nullptr}; wxCheckBox *m_reduce_outer_surface_texture_checkbox {nullptr}; wxCheckBox *m_seam_hiding_checkbox {nullptr}; wxCheckBox *m_nonlinear_offset_adjustment_checkbox {nullptr}; @@ -5633,6 +5666,15 @@ Sidebar::Sidebar(Plater *parent) opt->value = serialized; else bundle->project_config.set_key_value("texture_mapping_definitions", new ConfigOptionString(serialized)); + const std::string global_serialized = bundle->texture_mapping_global_settings.serialize(); + if (ConfigOptionString *opt = print_cfg->option("texture_mapping_global_settings")) + opt->value = global_serialized; + else + print_cfg->set_key_value("texture_mapping_global_settings", new ConfigOptionString(global_serialized)); + if (ConfigOptionString *opt = bundle->project_config.option("texture_mapping_global_settings")) + opt->value = global_serialized; + else + bundle->project_config.set_key_value("texture_mapping_global_settings", new ConfigOptionString(global_serialized)); if (auto *print_tab = wxGetApp().get_tab(Preset::TYPE_PRINT)) print_tab->update_dirty(); if (wxGetApp().plater() != nullptr) @@ -5654,6 +5696,7 @@ Sidebar::Sidebar(Plater *parent) bundle->project_config.opt_string("texture_mapping_definitions") : std::string(); bundle->texture_mapping_zones.load_entries(serialized, colors); + bundle->texture_mapping_global_settings.load(bundle->project_config.opt_string("texture_mapping_global_settings")); bundle->texture_mapping_zones.add_zone(colors.size(), colors, colors.size() < 2 ? int(TextureMappingZone::LinearGradient) : int(TextureMappingZone::ImageTexture)); @@ -7549,13 +7592,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) auto is_preview_only_texture_row_change = [](const TextureMappingZone &before, const TextureMappingZone &after) { TextureMappingZone before_gcode = before; TextureMappingZone after_gcode = after; - before_gcode.preview_opacity_pct = TextureMappingZone::DefaultPreviewOpacityPct; - before_gcode.preview_simulate_colors = TextureMappingZone::DefaultPreviewSimulateColors; - before_gcode.preview_limit_resolution = TextureMappingZone::DefaultPreviewLimitResolution; before_gcode.show_linear_gradient_direction_arrow = false; - after_gcode.preview_opacity_pct = TextureMappingZone::DefaultPreviewOpacityPct; - after_gcode.preview_simulate_colors = TextureMappingZone::DefaultPreviewSimulateColors; - after_gcode.preview_limit_resolution = TextureMappingZone::DefaultPreviewLimitResolution; after_gcode.show_linear_gradient_direction_arrow = false; return before != after && before_gcode == after_gcode; }; @@ -7634,6 +7671,19 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) notify_change(affects_scene); }; + auto persist_preview_simulate_colors = [this, bundle, set_config_string, notify_change, refresh_texture_mapping_preview](bool value) { + if (bundle == nullptr) + return; + const bool changed = bundle->texture_mapping_global_settings.preview_simulate_colors != value; + bundle->texture_mapping_global_settings.preview_simulate_colors = value; + set_config_string("texture_mapping_global_settings", bundle->texture_mapping_global_settings.serialize()); + if (changed) { + notify_change(false); + refresh_texture_mapping_preview(); + CallAfter([this]() { update_texture_mapping_panel(false); }); + } + }; + for (const size_t zone_index : visible_zone_indices) { if (zone_index >= zones.size()) continue; @@ -7994,7 +8044,10 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) editor_sizer->Add(contrast_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); ::CheckBox *preview_colors_chk = nullptr; - auto *preview_colors_row = make_sidebar_checkbox_row(editor, preview_colors_chk, _L("Preview Result Colors"), entry.preview_simulate_colors); + auto *preview_colors_row = make_sidebar_checkbox_row(editor, + preview_colors_chk, + _L("Preview Result Colors"), + bundle->texture_mapping_global_settings.preview_simulate_colors); editor_sizer->Add(preview_colors_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); auto *button_row = new wxBoxSizer(wxHORIZONTAL); @@ -8008,7 +8061,7 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) auto apply_controls = [zone_index, mgr_ptr, num_physical, filament_checks, surface_choice, linear_gradient_stops_bar, linear_gradient_mode_choice, linear_gradient_radius_spin, linear_gradient_radius_percent_chk, - surface_pattern_from_selection, linear_gradient_mode_from_selection, mode_choice, preview_colors_chk, + surface_pattern_from_selection, linear_gradient_mode_from_selection, mode_choice, show_direction_arrow_chk, contrast_spin, apply_zone]() { if (mgr_ptr == nullptr) return; @@ -8058,8 +8111,6 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.filament_color_mode = mode_choice != nullptr ? texture_mapping_color_mode_from_selection(mode_choice->GetSelection(), updated.filament_color_mode) : updated.filament_color_mode; - updated.preview_simulate_colors = - updated.is_image_texture() && preview_colors_chk != nullptr && preview_colors_chk->GetValue(); updated.show_linear_gradient_direction_arrow = show_direction_arrow_chk != nullptr && show_direction_arrow_chk->GetValue(); updated.contrast_pct = contrast_spin != nullptr ? std::clamp(float(contrast_spin->GetValue()), 25.f, 300.f) : updated.contrast_pct; updated.high_resolution_sampling = true; @@ -8174,7 +8225,6 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) linear_gradient_mode_from_selection, mode_choice, surface_choice, - preview_colors_chk, contrast_spin, set_sidebar_checkbox_value, surface_pattern_from_selection, @@ -8235,8 +8285,6 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.filament_color_mode = mode_choice != nullptr ? texture_mapping_color_mode_from_selection(mode_choice->GetSelection(), updated.filament_color_mode) : updated.filament_color_mode; - updated.preview_simulate_colors = - updated.is_image_texture() && preview_colors_chk != nullptr && preview_colors_chk->GetValue(); updated.contrast_pct = contrast_spin != nullptr ? std::clamp(float(contrast_spin->GetValue()), 25.f, 300.f) : updated.contrast_pct; updated.high_resolution_sampling = true; @@ -8264,8 +8312,9 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) apply_zone(std::move(updated)); }; mode_choice->Bind(wxEVT_COMBOBOX, on_mode_choice); - preview_colors_chk->Bind(wxEVT_TOGGLEBUTTON, [apply_controls](wxCommandEvent &evt) { - apply_controls(); + preview_colors_chk->Bind(wxEVT_TOGGLEBUTTON, [preview_colors_chk, persist_preview_simulate_colors](wxCommandEvent &evt) { + if (preview_colors_chk != nullptr) + persist_preview_simulate_colors(preview_colors_chk->GetValue()); evt.Skip(); }); if (show_direction_arrow_chk != nullptr) @@ -8621,10 +8670,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) transmission_distances, updated.transmission_distance_calibration_mode, updated.tone_gamma, - updated.preview_opacity_pct, updated.force_sequential_filaments, updated.auto_adjust_filament_selection, - updated.preview_limit_resolution, updated.reduce_outer_surface_texture, updated.seam_hiding, updated.nonlinear_offset_adjustment, @@ -8682,7 +8729,6 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.texture_mapping_mode = dlg.texture_mapping_mode(); updated.tone_gamma = dlg.tone_gamma(); updated.transmission_distance_calibration_mode = dlg.transmission_distance_calibration_mode(); - updated.preview_opacity_pct = dlg.preview_opacity_pct(); updated.force_sequential_filaments = dlg.force_sequential_filaments(); if (updated.force_sequential_filaments && ids.size() >= 2) { updated.component_ids = encode_texture_mapping_component_ids(ids); @@ -8690,7 +8736,6 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.component_b = ids[1]; } updated.auto_adjust_filament_selection = dlg.auto_adjust_filament_selection(); - updated.preview_limit_resolution = dlg.preview_limit_resolution(); updated.reduce_outer_surface_texture = dlg.reduce_outer_surface_texture(); updated.seam_hiding = dlg.seam_hiding(); updated.nonlinear_offset_adjustment = dlg.nonlinear_offset_adjustment(); @@ -8752,8 +8797,18 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.generic_solver_mode = dlg.generic_solver_mode(); updated.generic_solver_mix_model = dlg.generic_solver_mix_model(); const TextureMappingGlobalSettings dlg_global_settings = dlg.global_settings(); + auto prime_tower_only_settings = [](TextureMappingGlobalSettings settings) { + settings.preview_opacity_pct = TextureMappingZone::DefaultPreviewOpacityPct; + settings.preview_simulate_colors = TextureMappingZone::DefaultPreviewSimulateColors; + settings.preview_limit_resolution = TextureMappingZone::DefaultPreviewLimitResolution; + settings.preview_simulate_top_surface_lod = TextureMappingZone::DefaultPreviewSimulateTopSurfaceLod; + return settings.serialize(); + }; const bool global_settings_changed = dlg_global_settings.serialize() != bundle->texture_mapping_global_settings.serialize(); + const bool prime_tower_settings_changed = + prime_tower_only_settings(dlg_global_settings) != + prime_tower_only_settings(bundle->texture_mapping_global_settings); if (global_settings_changed) bundle->texture_mapping_global_settings = dlg_global_settings; Model &model = wxGetApp().model(); @@ -8792,10 +8847,10 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) std::abs(updated.filament_transmission_distances_mm.back()) <= 1e-6f) updated.filament_transmission_distances_mm.pop_back(); const bool affects_scene = apply_zone(std::move(updated)); - const bool prime_tower_preview_changed = global_settings_changed || prime_tower_images_changed; - if (prime_tower_preview_changed) - notify_change(true); - if (affects_scene || prime_tower_preview_changed) + const bool prime_tower_preview_changed = prime_tower_settings_changed || prime_tower_images_changed; + if (global_settings_changed || prime_tower_images_changed) + notify_change(prime_tower_preview_changed); + if (affects_scene || global_settings_changed || prime_tower_images_changed) refresh_texture_mapping_preview(); CallAfter([this]() { update_texture_mapping_panel(false); }); };