diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index eb87282002c..0656661b56e 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -5930,15 +5930,150 @@ static float overhang_filament_minimum_offset_factor_for_gcode(const TextureMapp return std::clamp(minimum_offset_pct / 100.f, 0.f, 1.f); } +struct TransmissionDistanceCalibrationContextForGCode { + bool enabled { false }; + int mode { int(TextureMappingZone::TDCalibrationNone) }; + std::vector own_width_factors; + std::vector neighbor_opacity_ratios; +}; + +static bool texture_mapping_component_is_black_for_gcode(size_t component_idx, + int filament_color_mode, + const std::vector> &component_colors) +{ + switch (std::clamp(filament_color_mode, int(TextureMappingZone::FilamentColorAny), int(TextureMappingZone::FilamentColorBW))) { + case int(TextureMappingZone::FilamentColorCMYK): + case int(TextureMappingZone::FilamentColorRGBK): + if (component_idx == 3) + return true; + break; + case int(TextureMappingZone::FilamentColorBW): + if (component_idx == 0) + return true; + break; + default: + break; + } + + if (component_idx >= component_colors.size()) + return false; + + const std::array &c = component_colors[component_idx]; + const float max_channel = std::max({ c[0], c[1], c[2] }); + const float luminance = 0.2126f * c[0] + 0.7152f * c[1] + 0.0722f * c[2]; + return max_channel <= 0.18f && luminance <= 0.12f; +} + +static bool overhang_filament_explicit_transmission_distance_for_gcode(const TextureMappingZone &zone, + unsigned int physical_filament_id, + float &td_mm) +{ + if (physical_filament_id == 0) + return false; + + const size_t idx = size_t(physical_filament_id - 1); + if (idx >= zone.filament_transmission_distances_mm.size()) + return false; + + const float value = zone.filament_transmission_distances_mm[idx]; + if (!std::isfinite(value) || value <= 0.f) + return false; + + td_mm = std::clamp(value, 0.01f, 50.f); + return true; +} + +static float transmission_distance_reference_for_gcode(bool is_black) +{ + return is_black ? 0.1f : 3.f; +} + +static float transmission_distance_opacity_for_gcode(float td_mm, float path_extension_mm) +{ + constexpr float surface_scatter = 0.50f; + constexpr float surface_depth_mm = 0.32f; + const float safe_td = std::clamp(td_mm, 0.01f, 50.f); + const float path_mm = std::max(0.f, surface_depth_mm + std::max(0.f, path_extension_mm)); + const float opacity = surface_scatter + (1.f - surface_scatter) * (1.f - std::exp(-path_mm / safe_td)); + return std::clamp(opacity, 1e-4f, 1.f); +} + +static TransmissionDistanceCalibrationContextForGCode transmission_distance_calibration_context_for_gcode( + const TextureMappingZone &zone, + const std::vector &component_ids, + const std::vector> &component_colors, + int filament_color_mode) +{ + 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()) + return context; + + std::vector explicit_tds(component_ids.size(), 0.f); + bool has_active_explicit_td = false; + for (size_t idx = 0; idx < component_ids.size(); ++idx) { + float td_mm = 0.f; + if (overhang_filament_explicit_transmission_distance_for_gcode(zone, component_ids[idx], td_mm)) { + explicit_tds[idx] = td_mm; + has_active_explicit_td = true; + } + } + if (!has_active_explicit_td) + return context; + + const bool neighbor_mode = context.mode == int(TextureMappingZone::TDCalibrationNeighbor); + const float path_extension_mm = neighbor_mode ? 0.16f : 0.12f; + const float own_power = neighbor_mode ? 0.25f : 0.35f; + context.own_width_factors.assign(component_ids.size(), 1.f); + context.neighbor_opacity_ratios.assign(component_ids.size(), 1.f); + context.enabled = true; + + for (size_t idx = 0; idx < component_ids.size(); ++idx) { + const bool is_black = texture_mapping_component_is_black_for_gcode(idx, filament_color_mode, component_colors); + const float reference_td_mm = transmission_distance_reference_for_gcode(is_black); + const float actual_td_mm = explicit_tds[idx] > 0.f ? explicit_tds[idx] : reference_td_mm; + const float actual_opacity = transmission_distance_opacity_for_gcode(actual_td_mm, path_extension_mm); + const float reference_opacity = transmission_distance_opacity_for_gcode(reference_td_mm, path_extension_mm); + context.own_width_factors[idx] = + std::clamp(std::pow(reference_opacity / std::max(actual_opacity, 1e-4f), own_power), 0.25f, 2.f); + context.neighbor_opacity_ratios[idx] = + std::clamp(actual_opacity / std::max(reference_opacity, 1e-4f), 0.25f, 4.f); + } + + return context; +} + +static float transmission_distance_width_factor_for_gcode(const TransmissionDistanceCalibrationContextForGCode &context, + size_t active_component_idx, + size_t previous_component_idx) +{ + if (!context.enabled || active_component_idx >= context.own_width_factors.size()) + return 1.f; + + float factor = context.own_width_factors[active_component_idx]; + if (context.mode == int(TextureMappingZone::TDCalibrationNeighbor) && + previous_component_idx < context.neighbor_opacity_ratios.size()) + factor *= std::pow(context.neighbor_opacity_ratios[previous_component_idx], 0.20f); + + return std::clamp(factor, 0.25f, 2.f); +} + static float variable_width_delta_for_overhang_range_for_gcode(float inset_strength, float max_width_delta_limit_mm, float minimum_offset_factor, - float strength_factor) + float strength_factor, + float transmission_distance_width_factor) { if (!std::isfinite(max_width_delta_limit_mm) || max_width_delta_limit_mm <= 0.f) return 0.f; - const float desired_width_factor = 1.f - std::clamp(inset_strength, 0.f, 1.f); + const float desired_width_factor = + std::clamp((1.f - std::clamp(inset_strength, 0.f, 1.f)) * + std::clamp(transmission_distance_width_factor, 0.f, 2.f), + 0.f, + 1.f); const float min_width_factor = std::clamp(minimum_offset_factor, 0.f, 1.f); const float adjusted_width_factor = min_width_factor + desired_width_factor * std::clamp(strength_factor, 0.f, 1.f) * (1.f - min_width_factor); @@ -6014,6 +6149,7 @@ static float variable_width_delta_for_visibility_range_for_gcode(float inset_str float max_width_delta_limit_mm, float minimum_offset_factor, float strength_factor, + float transmission_distance_width_factor, bool nonlinear_offset_adjustment, float layer_height_mm, float stair_step_mm, @@ -6022,7 +6158,11 @@ static float variable_width_delta_for_visibility_range_for_gcode(float inset_str if (!std::isfinite(max_width_delta_limit_mm) || max_width_delta_limit_mm <= 0.f) return 0.f; - float desired_width_factor = 1.f - std::clamp(inset_strength, 0.f, 1.f); + float desired_width_factor = + std::clamp((1.f - std::clamp(inset_strength, 0.f, 1.f)) * + std::clamp(transmission_distance_width_factor, 0.f, 2.f), + 0.f, + 1.f); if (nonlinear_offset_adjustment) desired_width_factor = nonlinear_visibility_width_factor_for_gcode(desired_width_factor, layer_height_mm, @@ -8110,6 +8250,12 @@ std::optional GCode::texture_mapping_seam_hiding_point(const ExtrusionLoo if (missing_component_color || component_colors.size() != component_ids.size() || component_colors.empty()) return std::nullopt; + const TransmissionDistanceCalibrationContextForGCode td_calibration_context = + transmission_distance_calibration_context_for_gcode(*zone, + component_ids, + component_colors, + texture_filament_color_mode); + std::ostringstream component_key_stream; for (size_t idx = 0; idx < component_ids.size(); ++idx) { if (idx > 0) @@ -8136,6 +8282,7 @@ std::optional GCode::texture_mapping_seam_hiding_point(const ExtrusionLoo const VertexColorOverhangWeightField *weight_field { nullptr }; float active_component_strength_factor { 1.f }; float active_component_minimum_offset_factor { 0.f }; + float active_component_td_width_factor { 1.f }; float signed_fade_factor { 1.f }; float fade_factor { 1.f }; }; @@ -8156,6 +8303,15 @@ std::optional GCode::texture_mapping_seam_hiding_point(const ExtrusionLoo const auto active_component_it = std::find(component_ids.begin(), component_ids.end(), active_component_id); if (active_component_it == component_ids.end()) return std::nullopt; + const size_t active_component_idx = size_t(active_component_it - component_ids.begin()); + size_t previous_component_idx = size_t(-1); + if (layer_index > 0) { + const unsigned int previous_component_id = + texture_mgr.resolve_zone_component(texture_zone_id, num_physical, layer_index - 1); + const auto previous_component_it = std::find(component_ids.begin(), component_ids.end(), previous_component_id); + if (previous_component_it != component_ids.end()) + previous_component_idx = size_t(previous_component_it - component_ids.begin()); + } std::ostringstream layer_key_stream; layer_key_stream << component_key_prefix << "|seamL" << layer->id(); @@ -8199,10 +8355,11 @@ std::optional GCode::texture_mapping_seam_hiding_point(const ExtrusionLoo layer, layer_height_mm, active_component_id, - size_t(active_component_it - component_ids.begin()), + active_component_idx, &weight_field, overhang_filament_strength_factor_for_gcode(*zone, active_component_id), overhang_filament_minimum_offset_factor_for_gcode(*zone, active_component_id), + transmission_distance_width_factor_for_gcode(td_calibration_context, active_component_idx, previous_component_idx), signed_fade_factor, fade_factor }; @@ -8300,7 +8457,8 @@ std::optional GCode::texture_mapping_seam_hiding_point(const ExtrusionLoo variable_width_delta_for_overhang_range_for_gcode(inset_strength, max_width_delta_limit_mm, state.active_component_minimum_offset_factor, - state.active_component_strength_factor); + state.active_component_strength_factor, + state.active_component_td_width_factor); const float width_delta_mm = std::clamp(variable_width_delta_mm, 0.f, max_width_delta_limit_mm); if (!std::isfinite(width_delta_mm)) return std::numeric_limits::quiet_NaN(); @@ -9027,6 +9185,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, float max_width_delta_mm { 0.f }; float active_component_strength_factor { 1.f }; float active_component_minimum_offset_factor { 0.f }; + float active_component_td_width_factor { 1.f }; float base_outer_width_mm { 0.4f }; float flow_reference_width_mm { 0.4f }; float base_centerline_shift_mm { 0.f }; @@ -9220,32 +9379,51 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, const float texture_sagging_ratio = std::isfinite(zone->sagging_ratio) ? std::clamp(zone->sagging_ratio, 0.f, 6.f) : 0.f; + const bool raw_texture_mapping_mode = + zone->texture_mapping_mode == int(TextureMappingZone::TextureMappingRawValues); + std::vector> component_colors; + component_colors.reserve(component_ids.size()); + bool missing_component_color = false; + for (const unsigned int id : component_ids) { + if (id < 1 || id > m_config.filament_colour.values.size()) { + if (raw_texture_mapping_mode) + component_colors.push_back({ 0.f, 0.f, 0.f }); + else + missing_component_color = true; + continue; + } + ColorRGB decoded; + if (!decode_color(m_config.filament_colour.get_at(size_t(id - 1)), decoded)) { + if (raw_texture_mapping_mode) + component_colors.push_back({ 0.f, 0.f, 0.f }); + else + missing_component_color = true; + continue; + } + component_colors.push_back({ decoded.r(), decoded.g(), decoded.b() }); + } + const TransmissionDistanceCalibrationContextForGCode td_calibration_context = + transmission_distance_calibration_context_for_gcode(*zone, + component_ids, + component_colors, + texture_filament_color_mode); + size_t previous_component_idx = size_t(-1); + if (current_layer_index > 0) { + const unsigned int previous_component_id = + texture_mgr.resolve_zone_component(texture_zone_id, num_physical, current_layer_index - 1); + const auto previous_component_it = + std::find(component_ids.begin(), component_ids.end(), previous_component_id); + if (previous_component_it != component_ids.end()) + previous_component_idx = size_t(previous_component_it - component_ids.begin()); + } + const float active_component_td_width_factor = + transmission_distance_width_factor_for_gcode(td_calibration_context, + active_component_idx, + previous_component_idx); + const VertexColorOverhangWeightField *vertex_color_weight_field = nullptr; auto *generic_mix_candidate_cache = &m_generic_solver_mix_candidate_cache; if (vertex_color_match_mode && layer_object != nullptr) { - const bool raw_texture_mapping_mode = - zone->texture_mapping_mode == int(TextureMappingZone::TextureMappingRawValues); - std::vector> component_colors; - component_colors.reserve(component_ids.size()); - bool missing_component_color = false; - for (const unsigned int id : component_ids) { - if (id < 1 || id > m_config.filament_colour.values.size()) { - if (raw_texture_mapping_mode) - component_colors.push_back({ 0.f, 0.f, 0.f }); - else - missing_component_color = true; - continue; - } - ColorRGB decoded; - if (!decode_color(m_config.filament_colour.get_at(size_t(id - 1)), decoded)) { - if (raw_texture_mapping_mode) - component_colors.push_back({ 0.f, 0.f, 0.f }); - else - missing_component_color = true; - continue; - } - component_colors.push_back({ decoded.r(), decoded.g(), decoded.b() }); - } if (!missing_component_color && component_colors.size() == component_ids.size() && !component_colors.empty()) { std::ostringstream component_key_stream; for (size_t idx = 0; idx < component_ids.size(); ++idx) { @@ -9321,6 +9499,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, outer_wall_gradient_dynamic_ctx.max_width_delta_mm = effective_max_width_delta_mm; outer_wall_gradient_dynamic_ctx.active_component_strength_factor = active_component_strength_factor; outer_wall_gradient_dynamic_ctx.active_component_minimum_offset_factor = active_component_minimum_offset_factor; + outer_wall_gradient_dynamic_ctx.active_component_td_width_factor = active_component_td_width_factor; outer_wall_gradient_dynamic_ctx.base_outer_width_mm = base_outer_width_mm; outer_wall_gradient_dynamic_ctx.flow_reference_width_mm = flow_reference_width_mm; outer_wall_gradient_dynamic_ctx.base_centerline_shift_mm = base_centerline_shift_mm; @@ -9435,6 +9614,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, max_width_delta_limit_mm, active_component_minimum_offset_factor, active_component_strength_factor, + active_component_td_width_factor, nonlinear_offset_adjustment, layer_height_mm, stair_step_mm, @@ -10151,6 +10331,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, max_width_delta_limit_mm, outer_wall_gradient_dynamic_ctx.active_component_minimum_offset_factor, outer_wall_gradient_dynamic_ctx.active_component_strength_factor, + outer_wall_gradient_dynamic_ctx.active_component_td_width_factor, outer_wall_gradient_dynamic_ctx.nonlinear_offset_adjustment, outer_wall_gradient_dynamic_ctx.layer_height_mm, stair_step_mm, diff --git a/src/libslic3r/TextureMapping.cpp b/src/libslic3r/TextureMapping.cpp index 4b06547c1a4..a9aba777f0e 100644 --- a/src/libslic3r/TextureMapping.cpp +++ b/src/libslic3r/TextureMapping.cpp @@ -352,14 +352,27 @@ static std::vector normalize_minimum_offsets(const std::vector &va return out; } +static std::vector normalize_transmission_distances(const std::vector &values) +{ + std::vector out; + out.reserve(std::min(values.size(), 9)); + for (size_t i = 0; i < std::min(values.size(), 9); ++i) { + const float value = finite_or(values[i], 0.f); + out.emplace_back(value > 0.f ? std::clamp(value, 0.01f, 50.f) : 0.f); + } + while (!out.empty() && std::abs(out.back()) <= 1e-6f) + out.pop_back(); + return out; +} + static float normalize_tone_gamma(float value) { return (!std::isfinite(value) || value <= 0.f) ? 1.f : std::clamp(value, 0.5f, 3.f); } -static float normalize_sagging_ratio(float value) +static float normalize_sagging_ratio(float) { - return std::isfinite(value) ? std::clamp(value, 0.f, 6.f) : 0.f; + return 0.f; } static RGB filament_color(unsigned int id, const std::vector &filament_colours) @@ -553,6 +566,52 @@ static int generic_solver_mix_model_from_name(std::string) return TextureMappingZone::DefaultGenericSolverMixModel; } +static std::string transmission_distance_calibration_mode_name(int mode) +{ + switch (clamp_int(mode, + int(TextureMappingZone::TDCalibrationNone), + int(TextureMappingZone::TDCalibrationNeighbor))) { + case int(TextureMappingZone::TDCalibrationNone): return "none"; + case int(TextureMappingZone::TDCalibrationNeighbor): return "neighbor"; + 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)); }); + if (name == "none" || name == "off" || name == "disabled") + return int(TextureMappingZone::TDCalibrationNone); + if (name == "neighbor" || name == "neighbour") + return int(TextureMappingZone::TDCalibrationNeighbor); + return int(TextureMappingZone::TDCalibrationAbsolute); +} + +static int transmission_distance_calibration_mode_from_json(const nlohmann::json &texture) +{ + const auto mode_it = texture.find("transmission_distance_calibration"); + if (mode_it != texture.end()) { + if (mode_it->is_string()) + return transmission_distance_calibration_mode_from_name(mode_it->get()); + if (mode_it->is_number_integer() || mode_it->is_number_unsigned()) + return clamp_int(mode_it->get(), + int(TextureMappingZone::TDCalibrationNone), + int(TextureMappingZone::TDCalibrationNeighbor)); + if (mode_it->is_boolean()) + return mode_it->get() ? + int(TextureMappingZone::TDCalibrationAbsolute) : + int(TextureMappingZone::TDCalibrationNone); + } + + const auto legacy_it = texture.find("transmission_distance_calibration_enabled"); + if (legacy_it != texture.end() && legacy_it->is_boolean()) + return legacy_it->get() ? + int(TextureMappingZone::TDCalibrationAbsolute) : + int(TextureMappingZone::TDCalibrationNone); + + return TextureMappingZone::DefaultTransmissionDistanceCalibrationMode; +} + static std::string normalized_prime_tower_color_mode_name(std::string name) { std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return char(std::tolower(c)); }); @@ -708,12 +767,14 @@ bool TextureMappingZone::operator==(const TextureMappingZone &rhs) const high_resolution_sampling == rhs.high_resolution_sampling && std::abs(tone_gamma - rhs.tone_gamma) <= eps && std::abs(sagging_ratio - rhs.sagging_ratio) <= 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); + floats_equal(filament_minimum_offsets_pct, rhs.filament_minimum_offsets_pct) && + floats_equal(filament_transmission_distances_mm, rhs.filament_transmission_distances_mm); } uint64_t TextureMappingManager::allocate_stable_id() @@ -796,6 +857,7 @@ void TextureMappingManager::remove_physical_filament(unsigned int deleted_filame }; remove_index(zone.filament_strengths_pct); remove_index(zone.filament_minimum_offsets_pct); + remove_index(zone.filament_transmission_distances_mm); } normalize_zone_ids(new_physical_count); } @@ -920,6 +982,8 @@ std::string TextureMappingManager::serialize_entries() const std::string normalized_angles = normalize_offset_angles(zone.offset_angles, component_ids.size()); const std::vector normalized_strengths = normalize_strengths(zone.filament_strengths_pct); const std::vector normalized_min_offsets = normalize_minimum_offsets(zone.filament_minimum_offsets_pct); + const std::vector normalized_transmission_distances = + normalize_transmission_distances(zone.filament_transmission_distances_mm); nlohmann::json entry; entry["schema"] = 2; @@ -950,12 +1014,16 @@ std::string TextureMappingManager::serialize_entries() texture["high_resolution_sampling"] = zone.high_resolution_sampling; texture["tone_gamma"] = normalize_tone_gamma(zone.tone_gamma); texture["sagging_ratio"] = normalize_sagging_ratio(zone.sagging_ratio); - texture["preview_opacity_pct"] = std::clamp(finite_or(zone.preview_opacity_pct, TextureMappingZone::DefaultPreviewOpacityPct), 0.f, 100.f); + 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); + texture["transmission_distance_mm"] = floats_to_json(normalized_transmission_distances); entry["texture_options"] = std::move(texture); nlohmann::json offset; @@ -1083,12 +1151,17 @@ void TextureMappingManager::load_entries(const std::string &serialized, zone.high_resolution_sampling = texture.value("high_resolution_sampling", true); zone.tone_gamma = normalize_tone_gamma(texture.value("tone_gamma", 1.f)); zone.sagging_ratio = normalize_sagging_ratio(texture.value("sagging_ratio", 0.f)); - zone.preview_opacity_pct = std::clamp(texture.value("preview_opacity_pct", TextureMappingZone::DefaultPreviewOpacityPct), 0.f, 100.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", false); 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 = normalize_minimum_offsets(floats_from_json(texture.value("minimum_offset_pct", nlohmann::json::array()))); + zone.filament_minimum_offsets_pct = + normalize_minimum_offsets(floats_from_json(texture.value("minimum_offset_pct", nlohmann::json::array()))); + zone.filament_transmission_distances_mm = + normalize_transmission_distances(floats_from_json(texture.value("transmission_distance_mm", nlohmann::json::array()))); const nlohmann::json offset = entry.value("surface_offset", nlohmann::json::object()); zone.offset_distances = diff --git a/src/libslic3r/TextureMapping.hpp b/src/libslic3r/TextureMapping.hpp index 74094b6b225..befaa96a0de 100644 --- a/src/libslic3r/TextureMapping.hpp +++ b/src/libslic3r/TextureMapping.hpp @@ -69,6 +69,12 @@ struct TextureMappingZone GenericSolverPigmentPainter = 0 }; + enum TransmissionDistanceCalibrationMode : uint8_t { + TDCalibrationNone = 0, + TDCalibrationAbsolute = 1, + TDCalibrationNeighbor = 2 + }; + static constexpr int DefaultSurfacePattern = int(ImageTexture); static constexpr int DefaultOffsetMode = int(OffsetBasic); static constexpr bool DefaultOffsetRotationEnabled = true; @@ -93,6 +99,7 @@ struct TextureMappingZone static constexpr bool DefaultHighResolutionSampling = true; static constexpr float DefaultToneGamma = 1.f; static constexpr float DefaultSaggingRatio = 0.f; + static constexpr int DefaultTransmissionDistanceCalibrationMode = int(TDCalibrationAbsolute); static constexpr bool DefaultPreviewSimulateColors = false; static constexpr bool DefaultPreviewLimitResolution = true; static constexpr bool DefaultAutoAdjustFilamentSelection = true; @@ -134,12 +141,14 @@ struct TextureMappingZone bool high_resolution_sampling = DefaultHighResolutionSampling; float tone_gamma = DefaultToneGamma; float sagging_ratio = DefaultSaggingRatio; + 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; + std::vector filament_transmission_distances_mm; bool is_image_texture() const { return surface_pattern == int(ImageTexture); } bool is_2d_gradient() const { return surface_pattern == int(Gradient2D); } @@ -175,12 +184,14 @@ struct TextureMappingZone high_resolution_sampling = DefaultHighResolutionSampling; tone_gamma = DefaultToneGamma; sagging_ratio = DefaultSaggingRatio; + transmission_distance_calibration_mode = DefaultTransmissionDistanceCalibrationMode; preview_opacity_pct = DefaultPreviewOpacityPct; preview_simulate_colors = DefaultPreviewSimulateColors; preview_limit_resolution = DefaultPreviewLimitResolution; auto_adjust_filament_selection = DefaultAutoAdjustFilamentSelection; filament_strengths_pct.clear(); filament_minimum_offsets_pct.clear(); + filament_transmission_distances_mm.clear(); } bool has_custom_offset_settings() const diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 20b9833f3bc..a49c0b0eb99 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1023,8 +1023,9 @@ public: const std::vector &component_ids, const std::vector &component_strengths_pct, const std::vector &component_minimum_offsets_pct, + const std::vector &component_transmission_distances_mm, + int transmission_distance_calibration_mode, float tone_gamma, - float sagging_ratio, float preview_opacity_pct, bool force_sequential_filaments, bool auto_adjust_filament_selection, @@ -1099,19 +1100,6 @@ public: tone_gamma_row->Add(new wxStaticText(image_page, wxID_ANY, _L("x")), 0, wxALIGN_CENTER_VERTICAL); image_root->Add(tone_gamma_row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); - auto *sagging_row = new wxBoxSizer(wxHORIZONTAL); - sagging_row->Add(new wxStaticText(filament_page, wxID_ANY, _L("Sagging ratio limit")), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); - m_sagging_ratio_spin = new wxSpinCtrlDouble(filament_page, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(84), -1), - wxSP_ARROW_KEYS | wxALIGN_RIGHT, 0.0, 6.0, std::clamp(double(sagging_ratio), 0.0, 6.0), 0.1); - m_sagging_ratio_spin->SetDigits(2); - sagging_row->Add(m_sagging_ratio_spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); - sagging_row->Add(new wxStaticText(filament_page, wxID_ANY, _L("x h")), 0, wxALIGN_CENTER_VERTICAL); - filament_root->Add(sagging_row, 0, wxEXPAND | wxALL, gap); - - m_force_sequential_filaments_checkbox = new wxCheckBox(filament_page, wxID_ANY, _L("Force sequential order for filaments")); - m_force_sequential_filaments_checkbox->SetValue(force_sequential_filaments); - filament_root->Add(m_force_sequential_filaments_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); - auto *minimum_offsets_box = new wxStaticBoxSizer(wxVERTICAL, filament_page, _L("Per-filament minimum offset")); auto *strengths_box = new wxStaticBoxSizer(wxVERTICAL, filament_page, _L("Per-filament strength")); const std::vector channel_labels = texture_mapping_channel_labels(filament_color_mode); @@ -1147,11 +1135,15 @@ public: box->Add(row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); }; for (size_t i = 0; i < component_ids.size(); ++i) { - const int offset_value = i < component_minimum_offsets_pct.size() ? std::clamp(int(std::lround(component_minimum_offsets_pct[i])), 0, 100) : 0; + const int offset_value = i < component_minimum_offsets_pct.size() ? + std::clamp(int(std::lround(component_minimum_offsets_pct[i])), 0, 100) : + 0; add_percent_row(minimum_offsets_box, i, offset_value, m_minimum_offset_sliders, m_minimum_offset_spins); } for (size_t i = 0; i < component_ids.size(); ++i) { - const int strength_value = i < component_strengths_pct.size() ? std::clamp(int(std::lround(component_strengths_pct[i])), 0, 100) : 100; + const int strength_value = i < component_strengths_pct.size() ? + std::clamp(int(std::lround(component_strengths_pct[i])), 0, 100) : + 100; add_percent_row(strengths_box, i, strength_value, m_strength_sliders, m_strength_spins); } filament_root->Add(minimum_offsets_box, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); @@ -1160,9 +1152,59 @@ public: reset_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { reset_strengths_and_offsets(); }); filament_root->Add(reset_btn, 0, wxALIGN_RIGHT | wxLEFT | wxRIGHT | wxBOTTOM, gap); + 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")), + 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")); + 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))); + 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) { + auto *row = new wxBoxSizer(wxHORIZONTAL); + row->Add(new wxStaticText(filament_page, wxID_ANY, component_label(i)), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap); + const double value = i < component_transmission_distances_mm.size() ? + std::clamp(double(component_transmission_distances_mm[i]), 0.0, 50.0) : + 0.0; + auto *spin = new wxSpinCtrlDouble(filament_page, + wxID_ANY, + wxEmptyString, + wxDefaultPosition, + wxSize(FromDIP(84), -1), + wxSP_ARROW_KEYS | wxALIGN_RIGHT, + 0.0, + 50.0, + value, + 0.1); + spin->SetDigits(2); + row->Add(spin, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, gap / 2); + row->Add(new wxStaticText(filament_page, wxID_ANY, _L("mm")), 0, wxALIGN_CENTER_VERTICAL); + m_transmission_distance_spins.emplace_back(spin); + td_box->Add(row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, gap); + } + filament_root->Add(td_box, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); + + m_force_sequential_filaments_checkbox = + new wxCheckBox(filament_page, wxID_ANY, _L("Force sequential order for filaments")); + m_force_sequential_filaments_checkbox->SetValue(force_sequential_filaments); + filament_root->Add(m_force_sequential_filaments_checkbox, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, gap); + 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); + 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); 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), @@ -1391,7 +1433,15 @@ public: } float tone_gamma() const { return float(std::clamp(m_tone_gamma_spin ? m_tone_gamma_spin->GetValue() : 1.0, 0.5, 3.0)); } - float sagging_ratio() const { return float(std::clamp(m_sagging_ratio_spin ? m_sagging_ratio_spin->GetValue() : 0.0, 0.0, 6.0)); } + int transmission_distance_calibration_mode() const + { + return m_transmission_distance_calibration_mode_choice ? + std::clamp(m_transmission_distance_calibration_mode_choice->GetSelection(), + int(TextureMappingZone::TDCalibrationNone), + int(TextureMappingZone::TDCalibrationNeighbor)) : + TextureMappingZone::DefaultTransmissionDistanceCalibrationMode; + } + float preview_opacity_pct() const { return float(std::clamp(m_preview_opacity_spin ? m_preview_opacity_spin->GetValue() : 100, 0, 100)); } 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(); } @@ -1482,6 +1532,17 @@ public: return out; } + std::vector component_transmission_distances_mm() const + { + std::vector out; + out.reserve(m_transmission_distance_spins.size()); + for (wxSpinCtrlDouble *spin : m_transmission_distance_spins) { + const double value = spin ? spin->GetValue() : 0.0; + out.emplace_back(value > 0.0 ? float(std::clamp(value, 0.01, 50.0)) : 0.f); + } + return out; + } + private: static std::string prime_tower_color_mode_name(int selection) { @@ -1612,7 +1673,7 @@ private: wxSimplebook *m_options_book {nullptr}; wxChoice *m_texture_mapping_mode_choice {nullptr}; wxSpinCtrlDouble *m_tone_gamma_spin {nullptr}; - wxSpinCtrlDouble *m_sagging_ratio_spin {nullptr}; + wxChoice *m_transmission_distance_calibration_mode_choice {nullptr}; wxSlider *m_preview_opacity_slider {nullptr}; wxSpinCtrl *m_preview_opacity_spin {nullptr}; wxCheckBox *m_force_sequential_filaments_checkbox {nullptr}; @@ -1640,6 +1701,7 @@ private: std::vector m_minimum_offset_spins; std::vector m_strength_sliders; std::vector m_strength_spins; + std::vector m_transmission_distance_spins; }; } // namespace @@ -5551,19 +5613,35 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) apply_zone(std::move(updated)); update_texture_mapping_panel(false); }); - advanced_btn->Bind(wxEVT_BUTTON, [this, zone_index, mgr_ptr, palette, apply_zone, bundle, set_config_string](wxCommandEvent &) { + advanced_btn->Bind(wxEVT_BUTTON, [this, + zone_index, + mgr_ptr, + palette, + physical_colors, + apply_zone, + bundle, + set_config_string](wxCommandEvent &) { if (zone_index >= mgr_ptr->zones().size()) return; TextureMappingZone updated = mgr_ptr->zones()[zone_index]; - const std::vector ids = texture_mapping_selected_ids(updated, palette.size()); + std::vector ids = updated.is_image_texture() ? + TextureMappingManager::effective_texture_component_ids(updated, palette.size(), physical_colors) : + texture_mapping_selected_ids(updated, palette.size()); + if (ids.empty()) + ids = texture_mapping_selected_ids(updated, palette.size()); std::vector strengths; std::vector offsets; + std::vector transmission_distances; strengths.reserve(ids.size()); offsets.reserve(ids.size()); + transmission_distances.reserve(ids.size()); for (const unsigned int id : ids) { const size_t idx = id > 0 ? size_t(id - 1) : size_t(0); strengths.emplace_back(idx < updated.filament_strengths_pct.size() ? updated.filament_strengths_pct[idx] : 100.f); offsets.emplace_back(idx < updated.filament_minimum_offsets_pct.size() ? updated.filament_minimum_offsets_pct[idx] : 0.f); + transmission_distances.emplace_back(idx < updated.filament_transmission_distances_mm.size() ? + updated.filament_transmission_distances_mm[idx] : + 0.f); } TextureMappingAdvancedOptionsDialog dlg(this, updated.texture_mapping_mode, @@ -5571,8 +5649,9 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) ids, strengths, offsets, + transmission_distances, + updated.transmission_distance_calibration_mode, updated.tone_gamma, - updated.sagging_ratio, updated.preview_opacity_pct, updated.force_sequential_filaments, updated.auto_adjust_filament_selection, @@ -5596,7 +5675,8 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) return; updated.texture_mapping_mode = dlg.texture_mapping_mode(); updated.tone_gamma = dlg.tone_gamma(); - updated.sagging_ratio = dlg.sagging_ratio(); + updated.sagging_ratio = TextureMappingZone::DefaultSaggingRatio; + 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(); updated.auto_adjust_filament_selection = dlg.auto_adjust_filament_selection(); @@ -5634,6 +5714,15 @@ void Sidebar::update_texture_mapping_panel(bool sync_manager) updated.filament_minimum_offsets_pct[size_t(ids[i] - 1)] = dlg_offsets[i]; while (!updated.filament_minimum_offsets_pct.empty() && std::abs(updated.filament_minimum_offsets_pct.back()) <= 1e-6f) updated.filament_minimum_offsets_pct.pop_back(); + if (updated.filament_transmission_distances_mm.size() < palette.size()) + updated.filament_transmission_distances_mm.resize(palette.size(), 0.f); + const std::vector dlg_transmission_distances = dlg.component_transmission_distances_mm(); + for (size_t i = 0; i < ids.size() && i < dlg_transmission_distances.size(); ++i) + if (ids[i] > 0 && size_t(ids[i] - 1) < updated.filament_transmission_distances_mm.size()) + updated.filament_transmission_distances_mm[size_t(ids[i] - 1)] = dlg_transmission_distances[i]; + while (!updated.filament_transmission_distances_mm.empty() && + std::abs(updated.filament_transmission_distances_mm.back()) <= 1e-6f) + updated.filament_transmission_distances_mm.pop_back(); apply_zone(std::move(updated)); if (p->plater != nullptr && !p->plater->is_preview_shown()) { if (GLCanvas3D *canvas = p->plater->get_view3D_canvas3D())